From 960dd25bbd560dc118dd8ab749df399fd0c1a4fb Mon Sep 17 00:00:00 2001 From: Hendrik Schlehlein Date: Mon, 27 May 2019 23:46:09 +0200 Subject: [PATCH] logic done --- config.yaml | 7 +++ config/config.go | 3 ++ data/game.go | 8 ++++ data/group.go | 14 ++++++ features/game/handler.go | 58 +++++++++++++++++++++++ features/game/page.go | 79 ++++++++++++++++++++++++++++++++ features/game/routes.go | 23 ++++++++++ features/group/handler.go | 58 +++++++++++++++++++++++ features/group/page.go | 62 +++++++++++++++++++++++++ features/group/routes.go | 23 ++++++++++ features/handler.go | 69 ++++++++++++++++++++++++++++ features/index/handler.go | 17 +++++++ features/index/page.go | 20 ++++++++ features/index/routes.go | 16 +++++++ features/service.go | 16 +++++++ features/weberror/page.go | 24 ++++++++++ gui/template.go | 35 ++++++++++++++ ioutil/reader.go | 45 ++++++++++++++++++ main.go | 47 ++++++++++--------- request/{ => meta}/meta.go | 0 response/{response.go => api.go} | 12 ++--- response/error.go | 12 +++-- response/web.go | 19 ++++++++ service/crud.go | 23 ++++++---- service/filter/filter.go | 40 ++++++++++++++++ service/id/id.go | 18 ++++++++ service/service.go | 5 +- static/styles.css | 22 +++++++++ templates/error.html | 11 +++++ templates/gameDetail.html | 24 ++++++++++ templates/gameMaster.html | 15 ++++++ templates/groupDetail.html | 15 ++++++ templates/groupMaster.html | 16 +++++++ templates/index.html | 11 +++++ 34 files changed, 822 insertions(+), 45 deletions(-) create mode 100644 config.yaml create mode 100644 data/game.go create mode 100644 data/group.go create mode 100644 features/game/handler.go create mode 100644 features/game/page.go create mode 100644 features/game/routes.go create mode 100644 features/group/handler.go create mode 100644 features/group/page.go create mode 100644 features/group/routes.go create mode 100644 features/handler.go create mode 100644 features/index/handler.go create mode 100644 features/index/page.go create mode 100644 features/index/routes.go create mode 100644 features/service.go create mode 100644 features/weberror/page.go create mode 100644 gui/template.go create mode 100644 ioutil/reader.go rename request/{ => meta}/meta.go (100%) rename response/{response.go => api.go} (78%) create mode 100644 response/web.go create mode 100644 service/filter/filter.go create mode 100644 service/id/id.go create mode 100755 static/styles.css create mode 100644 templates/error.html create mode 100644 templates/gameDetail.html create mode 100644 templates/gameMaster.html create mode 100644 templates/groupDetail.html create mode 100644 templates/groupMaster.html create mode 100644 templates/index.html diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..3478656 --- /dev/null +++ b/config.yaml @@ -0,0 +1,7 @@ +Port: 8080 +DataService: + IP: 127.0.0.1 + Port: 27017 + Username: haveachin + Password: vKz8VmTTB4HZx2 + Database: scoreboard \ No newline at end of file diff --git a/config/config.go b/config/config.go index 3231f73..a29186e 100644 --- a/config/config.go +++ b/config/config.go @@ -37,4 +37,7 @@ func setDefaults() { viper.SetDefault("Port", "8080") viper.SetDefault("DataService.IP", "127.0.0.1") viper.SetDefault("DataService.Port", "27017") + viper.SetDefault("DataService.Username", "haveachin") + viper.SetDefault("DataService.Password", "") + viper.SetDefault("DataService.Database", "scoreboard") } diff --git a/data/game.go b/data/game.go new file mode 100644 index 0000000..0b2fa8e --- /dev/null +++ b/data/game.go @@ -0,0 +1,8 @@ +package data + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type Game struct { + ID primitive.ObjectID `bson:"_id,omitempty"` + Name string `bson:"name,omitempty"` +} diff --git a/data/group.go b/data/group.go new file mode 100644 index 0000000..e3351d4 --- /dev/null +++ b/data/group.go @@ -0,0 +1,14 @@ +package data + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type Group struct { + ID primitive.ObjectID `bson:"_id,omitempty"` + Name string `bson:"name,omitempty"` + Scores []Score `bson:"scores,omitempty"` +} + +type Score struct { + GameID primitive.ObjectID `bson:"_gameId,omitempty"` + Points int `bson:"points,omitempty"` +} diff --git a/features/game/handler.go b/features/game/handler.go new file mode 100644 index 0000000..8a22749 --- /dev/null +++ b/features/game/handler.go @@ -0,0 +1,58 @@ +package game + +import ( + "errors" + "html/template" + "net/http" + + "git.cliffbreak.de/haveachin/scoreboard/data" + "git.cliffbreak.de/haveachin/scoreboard/features" + "git.cliffbreak.de/haveachin/scoreboard/features/weberror" + "git.cliffbreak.de/haveachin/scoreboard/response" + "git.cliffbreak.de/haveachin/scoreboard/service/filter" + "git.cliffbreak.de/haveachin/scoreboard/service/id" + "github.com/go-chi/chi" +) + +func queryOneHandler(s features.Service, t *template.Template) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response.WebHandler(w, response.WebHandlerFunc(func() (response.Page, int) { + if chi.URLParam(r, "id") == "" { + return weberror.NewPage(*t, errors.New("No id in request"), http.StatusBadRequest), http.StatusBadRequest + } + + id, err := id.ParseFromRequest(r) + if err != nil { + return weberror.NewPage(*t, err, http.StatusBadRequest), http.StatusBadRequest + } + + var game data.Game + + if err := s.QueryOne(&game, &filter.Filter{"_id": *id}); err != nil { + return weberror.NewPage(*t, err, http.StatusBadRequest), http.StatusBadRequest + } + + var groups []data.Group + + if err := s.Query(&groups, &filter.Filter{}); err != nil { + return weberror.NewPage(*t, err, http.StatusBadRequest), http.StatusBadRequest + } + + return newDetailPage(*t, game, groups), http.StatusOK + })) + }) +} + +func queryHandler(s features.Service, t *template.Template) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response.WebHandler(w, response.WebHandlerFunc(func() (response.Page, int) { + var games []data.Game + + if err := s.Query(&games, &filter.Filter{}); err != nil { + return weberror.NewPage(*t, err, http.StatusBadRequest), http.StatusBadRequest + } + + return newMasterPage(*t, games), http.StatusOK + })) + }) +} diff --git a/features/game/page.go b/features/game/page.go new file mode 100644 index 0000000..8ed71b1 --- /dev/null +++ b/features/game/page.go @@ -0,0 +1,79 @@ +package game + +import ( + "html/template" + "io" + + "git.cliffbreak.de/haveachin/scoreboard/data" +) + +type previewGame struct { + ID string + Name string +} + +type master struct { + Template template.Template + Games []previewGame +} + +type previewGroup struct { + ID string + Name string + Points int +} + +type detail struct { + Template template.Template + Game data.Game + Groups []previewGroup +} + +func newMasterPage(t template.Template, games []data.Game) *master { + previewGames := []previewGame{} + + for _, game := range games { + previewGames = append(previewGames, previewGame{ + ID: game.ID.Hex(), + Name: game.Name, + }) + } + + return &master{ + Template: *t.Lookup("gameMaster.html"), + Games: previewGames, + } +} + +func newDetailPage(t template.Template, game data.Game, groups []data.Group) *detail { + previewGroups := []previewGroup{} + + for _, group := range groups { + points := 0 + for _, score := range group.Scores { + if score.GameID == game.ID { + points = score.Points + break + } + } + previewGroups = append(previewGroups, previewGroup{ + ID: group.ID.Hex(), + Name: group.Name, + Points: points, + }) + } + + return &detail{ + Template: *t.Lookup("gameDetail.html"), + Game: game, + Groups: previewGroups, + } +} + +func (page master) Send(w io.Writer) { + page.Template.Execute(w, page) +} + +func (page detail) Send(w io.Writer) { + page.Template.Execute(w, page) +} diff --git a/features/game/routes.go b/features/game/routes.go new file mode 100644 index 0000000..05f69d6 --- /dev/null +++ b/features/game/routes.go @@ -0,0 +1,23 @@ +package game + +import ( + "html/template" + "reflect" + + "git.cliffbreak.de/haveachin/scoreboard/data" + "git.cliffbreak.de/haveachin/scoreboard/features" + "github.com/go-chi/chi" +) + +func Routes(s features.Service, t *template.Template) *chi.Mux { + router := chi.NewRouter() + s.RegisterCollection(reflect.TypeOf(data.Game{}), "game") + + router.Post("/", features.CreateHandler(&data.Game{}, s)) + router.Get("/{id}", queryOneHandler(s, t)) + router.Get("/", queryHandler(s, t)) + router.Put("/{id}", features.UpdateHandler(&data.Game{}, s)) + router.Delete("/{id}", features.DeleteHandler(&data.Game{}, s)) + + return router +} diff --git a/features/group/handler.go b/features/group/handler.go new file mode 100644 index 0000000..d3dfff9 --- /dev/null +++ b/features/group/handler.go @@ -0,0 +1,58 @@ +package group + +import ( + "errors" + "html/template" + "net/http" + + "git.cliffbreak.de/haveachin/scoreboard/data" + "git.cliffbreak.de/haveachin/scoreboard/features" + "git.cliffbreak.de/haveachin/scoreboard/features/weberror" + "git.cliffbreak.de/haveachin/scoreboard/response" + "git.cliffbreak.de/haveachin/scoreboard/service/filter" + "git.cliffbreak.de/haveachin/scoreboard/service/id" + "github.com/go-chi/chi" +) + +func queryOneHandler(s features.Service, t *template.Template) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response.WebHandler(w, response.WebHandlerFunc(func() (response.Page, int) { + if chi.URLParam(r, "id") == "" { + return weberror.NewPage(*t, errors.New("No id in request"), http.StatusBadRequest), http.StatusBadRequest + } + + id, err := id.ParseFromRequest(r) + if err != nil { + return weberror.NewPage(*t, err, http.StatusBadRequest), http.StatusBadRequest + } + + var group data.Group + + if err := s.QueryOne(&group, &filter.Filter{"_id": *id}); err != nil { + return weberror.NewPage(*t, err, http.StatusBadRequest), http.StatusBadRequest + } + + var games []data.Game + + if err := s.Query(&games, &filter.Filter{}); err != nil { + return weberror.NewPage(*t, err, http.StatusBadRequest), http.StatusBadRequest + } + + return newDetailPage(*t, group, games), http.StatusOK + })) + }) +} + +func queryHandler(s features.Service, t *template.Template) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response.WebHandler(w, response.WebHandlerFunc(func() (response.Page, int) { + var groups []data.Group + + if err := s.Query(&groups, &filter.Filter{}); err != nil { + return weberror.NewPage(*t, err, http.StatusBadRequest), http.StatusBadRequest + } + + return newMasterPage(*t, groups), http.StatusOK + })) + }) +} diff --git a/features/group/page.go b/features/group/page.go new file mode 100644 index 0000000..267b1f8 --- /dev/null +++ b/features/group/page.go @@ -0,0 +1,62 @@ +package group + +import ( + "html/template" + "io" + + "git.cliffbreak.de/haveachin/scoreboard/data" +) + +type previewGroup struct { + ID string + Name string + PointSum int +} + +type master struct { + Template template.Template + Groups []previewGroup +} + +type detail struct { + Template template.Template + Group data.Group + Games []data.Game +} + +func newMasterPage(t template.Template, groups []data.Group) *master { + previewGroups := []previewGroup{} + + for _, group := range groups { + sum := 0 + for _, score := range group.Scores { + sum += score.Points + } + previewGroups = append(previewGroups, previewGroup{ + ID: group.ID.Hex(), + Name: group.Name, + PointSum: sum, + }) + } + + return &master{ + Template: *t.Lookup("groupMaster.html"), + Groups: previewGroups, + } +} + +func newDetailPage(t template.Template, group data.Group, games []data.Game) *detail { + return &detail{ + Template: *t.Lookup("groupDetail.html"), + Group: group, + Games: games, + } +} + +func (page master) Send(w io.Writer) { + page.Template.Execute(w, page) +} + +func (page detail) Send(w io.Writer) { + page.Template.Execute(w, page) +} diff --git a/features/group/routes.go b/features/group/routes.go new file mode 100644 index 0000000..0b6e3d4 --- /dev/null +++ b/features/group/routes.go @@ -0,0 +1,23 @@ +package group + +import ( + "html/template" + "reflect" + + "git.cliffbreak.de/haveachin/scoreboard/data" + "git.cliffbreak.de/haveachin/scoreboard/features" + "github.com/go-chi/chi" +) + +func Routes(s features.Service, t *template.Template) *chi.Mux { + router := chi.NewRouter() + s.RegisterCollection(reflect.TypeOf(data.Group{}), "group") + + router.Post("/", features.CreateHandler(&data.Group{}, s)) + router.Get("/{id}", queryOneHandler(s, t)) + router.Get("/", queryHandler(s, t)) + router.Put("/{id}", features.UpdateHandler(&data.Group{}, s)) + router.Delete("/{id}", features.DeleteHandler(&data.Group{}, s)) + + return router +} diff --git a/features/handler.go b/features/handler.go new file mode 100644 index 0000000..a55990d --- /dev/null +++ b/features/handler.go @@ -0,0 +1,69 @@ +package features + +import ( + "net/http" + + "git.cliffbreak.de/haveachin/scoreboard/ioutil" + "git.cliffbreak.de/haveachin/scoreboard/request/meta" + "git.cliffbreak.de/haveachin/scoreboard/response" + "git.cliffbreak.de/haveachin/scoreboard/service/filter" + "git.cliffbreak.de/haveachin/scoreboard/service/id" +) + +func CreateHandler(v interface{}, s Service) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response.APIHandler(w, response.APIHandlerFunc(func() (int, error) { + if err := ioutil.ExtractAndValidateJSON(r.Body, v); err != nil { + return http.StatusBadRequest, err + } + + if err := s.Create(v); err != nil { + return http.StatusNotFound, err + } + + return response.New(v, meta.ParseFromRequest(r)).Send(w, http.StatusCreated) + })) + }) +} + +func UpdateHandler(v interface{}, s Service) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response.APIHandler(w, response.APIHandlerFunc(func() (int, error) { + if err := ioutil.ExtractAndValidateJSON(r.Body, v); err != nil { + return http.StatusBadRequest, err + } + + id, err := id.ParseFromRequest(r) + if err != nil { + return http.StatusBadRequest, err + } + + filter := &filter.Filter{"_id": *id} + + if err := s.Update(v, filter); err != nil { + return http.StatusNotFound, err + } + + return response.New(nil, meta.ParseFromRequest(r)).Send(w, http.StatusOK) + })) + }) +} + +func DeleteHandler(v interface{}, s Service) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response.APIHandler(w, response.APIHandlerFunc(func() (int, error) { + id, err := id.ParseFromRequest(r) + if err != nil { + return http.StatusBadRequest, err + } + + filter := &filter.Filter{"_id": *id} + + if err := s.Delete(v, filter); err != nil { + return http.StatusNotFound, err + } + + return response.New(nil, meta.ParseFromRequest(r)).Send(w, http.StatusNoContent) + })) + }) +} diff --git a/features/index/handler.go b/features/index/handler.go new file mode 100644 index 0000000..2c5b1da --- /dev/null +++ b/features/index/handler.go @@ -0,0 +1,17 @@ +package index + +import ( + "html/template" + "net/http" + + "git.cliffbreak.de/haveachin/scoreboard/features" + "git.cliffbreak.de/haveachin/scoreboard/response" +) + +func queryHandler(s features.Service, t *template.Template) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response.WebHandler(w, response.WebHandlerFunc(func() (response.Page, int) { + return newPage(*t), http.StatusOK + })) + }) +} diff --git a/features/index/page.go b/features/index/page.go new file mode 100644 index 0000000..cb50f29 --- /dev/null +++ b/features/index/page.go @@ -0,0 +1,20 @@ +package index + +import ( + "html/template" + "io" +) + +type index struct { + Template template.Template +} + +func newPage(t template.Template) *index { + return &index{ + Template: *t.Lookup("index.html"), + } +} + +func (page index) Send(w io.Writer) { + page.Template.Execute(w, page) +} diff --git a/features/index/routes.go b/features/index/routes.go new file mode 100644 index 0000000..cdc69f7 --- /dev/null +++ b/features/index/routes.go @@ -0,0 +1,16 @@ +package index + +import ( + "html/template" + + "git.cliffbreak.de/haveachin/scoreboard/features" + "github.com/go-chi/chi" +) + +func Routes(s features.Service, t *template.Template) *chi.Mux { + router := chi.NewRouter() + + router.Get("/", queryHandler(s, t)) + + return router +} diff --git a/features/service.go b/features/service.go new file mode 100644 index 0000000..22ac016 --- /dev/null +++ b/features/service.go @@ -0,0 +1,16 @@ +package features + +import ( + "reflect" + + "git.cliffbreak.de/haveachin/scoreboard/service/filter" +) + +type Service interface { + Create(v interface{}) error + Query(v interface{}, f *filter.Filter) error + QueryOne(v interface{}, f *filter.Filter) error + Update(v interface{}, f *filter.Filter) error + Delete(v interface{}, f *filter.Filter) error + RegisterCollection(t reflect.Type, collection string) +} diff --git a/features/weberror/page.go b/features/weberror/page.go new file mode 100644 index 0000000..8ee9fdb --- /dev/null +++ b/features/weberror/page.go @@ -0,0 +1,24 @@ +package weberror + +import ( + "html/template" + "io" +) + +type ErrorPage struct { + Template template.Template + Error error + StatusCode int +} + +func NewPage(t template.Template, err error, statusCode int) *ErrorPage { + return &ErrorPage{ + Template: *t.Lookup("error.html"), + Error: err, + StatusCode: statusCode, + } +} + +func (page ErrorPage) Send(w io.Writer) { + page.Template.Execute(w, page) +} diff --git a/gui/template.go b/gui/template.go new file mode 100644 index 0000000..17f6cd5 --- /dev/null +++ b/gui/template.go @@ -0,0 +1,35 @@ +package gui + +import ( + "fmt" + "html/template" + "io/ioutil" + "strings" +) + +func LoadTemplates() (*template.Template, error) { + return loadTemplates("templates/") +} + +func loadTemplates(path string) (*template.Template, error) { + dir, err := ioutil.ReadDir(path) + if err != nil { + return nil, err + } + + var ff []string + + for _, file := range dir { + filename := file.Name() + if strings.HasSuffix(filename, ".html") { + ff = append(ff, fmt.Sprintf("%s%s", path, filename)) + } + } + + templates, err := template.ParseFiles(ff...) + if err != nil { + return nil, err + } + + return templates, nil +} diff --git a/ioutil/reader.go b/ioutil/reader.go new file mode 100644 index 0000000..91b5a3a --- /dev/null +++ b/ioutil/reader.go @@ -0,0 +1,45 @@ +package ioutil + +import ( + "encoding/json" + "io" + "io/ioutil" + + validator "gopkg.in/go-playground/validator.v9" +) + +var Validate = validator.New() + +func ExtractJSON(reader io.Reader, vv ...interface{}) error { + data, err := ioutil.ReadAll(reader) + if err != nil { + return err + } + + for _, v := range vv { + if err := json.Unmarshal(data, v); err != nil { + return err + } + } + + return nil +} + +func ExtractAndValidateJSON(reader io.Reader, vv ...interface{}) error { + data, err := ioutil.ReadAll(reader) + if err != nil { + return err + } + + for _, v := range vv { + if err := json.Unmarshal(data, v); err != nil { + return err + } + + if err := Validate.Struct(v); err != nil { + return err + } + } + + return nil +} diff --git a/main.go b/main.go index f64b820..524ecc4 100644 --- a/main.go +++ b/main.go @@ -2,37 +2,35 @@ package main import ( "fmt" + "html/template" "log" "net/http" "time" - "git.cliffbreak.de/Cliffbreak/tsviewer/config" - "git.cliffbreak.de/Cliffbreak/tsviewer/service" + "git.cliffbreak.de/haveachin/scoreboard/config" + "git.cliffbreak.de/haveachin/scoreboard/features/game" + "git.cliffbreak.de/haveachin/scoreboard/features/group" + "git.cliffbreak.de/haveachin/scoreboard/features/index" + "git.cliffbreak.de/haveachin/scoreboard/gui" + "git.cliffbreak.de/haveachin/scoreboard/service" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" - "github.com/go-chi/cors" ) -func Routes(s *service.Service) *chi.Mux { +func Routes(s *service.DataService, t *template.Template) *chi.Mux { router := chi.NewRouter() - cors := cors.New(cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"Get"}, - }) - router.Use( middleware.Logger, middleware.Timeout(5*time.Second), - middleware.DefaultCompress, middleware.RedirectSlashes, middleware.Recoverer, - - cors.Handler, ) router.Route("/", func(r chi.Router) { - //r.Mount("/channels", channel.APIRoutes(s)) + r.Mount("/", index.Routes(s, t)) + r.Mount("/groups", group.Routes(s, t)) + r.Mount("/games", game.Routes(s, t)) }) return router @@ -43,21 +41,28 @@ func main() { config, err := config.New() if err != nil { log.Fatal(err) - return } log.Println("Configurations loaded!") log.Println("Starting query service...") - service, err := service.New(*config) + service, err := service.New(config.DataService) if err != nil { log.Fatal(err) - return } - defer service.TSClient.Close() - log.Println("Query service connected to", fmt.Sprintf("%s:%d!", config.ServerTS.IP, config.ServerTS.PortQuery)) + log.Println("Query service connected to", fmt.Sprintf("%s:%d!", config.DataService.IP, config.DataService.Port)) - router := Routes(service) + log.Println("Loading templates...") + templates, err := gui.LoadTemplates() + if err != nil { + log.Fatal(err) + } + log.Println("All templates loaded!") - log.Println("Starting the web server locally on port", config.ServerWeb.Port) - log.Fatal("Handler: ", http.ListenAndServe(fmt.Sprintf(":%d", config.ServerWeb.Port), router)) + router := Routes(service, templates) + router.Get("/static/*", func(w http.ResponseWriter, r *http.Request) { + http.StripPrefix("/static", http.FileServer(http.Dir("./static"))).ServeHTTP(w, r) + }) + + log.Println("Starting the web server locally on port", config.Port) + log.Fatal("Handler: ", http.ListenAndServe(fmt.Sprintf(":%d", config.Port), router)) } diff --git a/request/meta.go b/request/meta/meta.go similarity index 100% rename from request/meta.go rename to request/meta/meta.go diff --git a/response/response.go b/response/api.go similarity index 78% rename from response/response.go rename to response/api.go index a0a37d8..a267810 100755 --- a/response/response.go +++ b/response/api.go @@ -5,10 +5,10 @@ import ( "log" "net/http" - "git.cliffbreak.de/Cliffbreak/tsviewer/request/meta" + "git.cliffbreak.de/haveachin/scoreboard/request/meta" ) -type HandlerFunc func() (int, error) +type APIHandlerFunc func() (int, error) // Responder is a service for responses type Responder interface { @@ -26,11 +26,9 @@ type Envelope struct { } // New creates a new Response -func New(content interface{}, r *http.Request) *Response { +func New(content interface{}, meta *meta.Meta) *Response { var resp Response - meta := meta.NewFromRequest(r) - if meta.Envelope { resp = Response{ Content: Envelope{ @@ -74,14 +72,14 @@ func (resp Response) Send(w http.ResponseWriter, status int) (int, error) { return status, nil } -func Handler(w http.ResponseWriter, r *http.Request, hf HandlerFunc) { +func APIHandler(w http.ResponseWriter, hf APIHandlerFunc) { status, err := hf() if err == nil { return } log.Printf("HTTP %d: %q", status, err) - if status, err = New(NewError(status, err), r).Send(w, status); err != nil { + if status, err = NewError(status, err).Send(w, status); err != nil { http.Error(w, http.StatusText(status), status) } } diff --git a/response/error.go b/response/error.go index 1d495bb..ce1c42d 100755 --- a/response/error.go +++ b/response/error.go @@ -10,10 +10,12 @@ type Error struct { } // NewError creates a new Response fill with an error -func NewError(status int, err error) *Error { - return &Error{ - Status: status, - Error: err.Error(), - Timestamp: time.Now().Format("2006-01-02T15:04:05Z"), //.Format("02 Jan 2006, 15:04:05 MST"), +func NewError(status int, err error) *Response { + return &Response{ + Content: Error{ + Status: status, + Error: err.Error(), + Timestamp: time.Now().Format("2006-01-02T15:04:05Z"), //.Format("02 Jan 2006, 15:04:05 MST"), + }, } } diff --git a/response/web.go b/response/web.go new file mode 100644 index 0000000..68bf487 --- /dev/null +++ b/response/web.go @@ -0,0 +1,19 @@ +package response + +import ( + "io" + "net/http" +) + +type WebHandlerFunc func() (Page, int) + +type Page interface { + Send(w io.Writer) +} + +func WebHandler(w http.ResponseWriter, hf WebHandlerFunc) { + page, status := hf() + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(status) + page.Send(w) +} diff --git a/service/crud.go b/service/crud.go index 5723ab7..6b6db5c 100644 --- a/service/crud.go +++ b/service/crud.go @@ -5,7 +5,7 @@ import ( "reflect" "time" - "github.com/haveachin/sawtexapi/service/mongodb/filter" + "git.cliffbreak.de/haveachin/scoreboard/service/filter" "go.mongodb.org/mongo-driver/bson" ) @@ -25,18 +25,10 @@ func (s DataService) Create(v interface{}) error { return nil } -func (s DataService) Query(v interface{}, filter *filter.Filter, multiQuery bool) error { +func (s DataService) Query(v interface{}, filter *filter.Filter) error { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - if !multiQuery { - if err := s.collection(reflect.TypeOf(v)).FindOne(ctx, filter).Decode(v); err != nil { - return err - } - - return nil - } - cursor, err := s.collection(reflect.TypeOf(v)).Find(ctx, filter) if err != nil { return err @@ -45,6 +37,17 @@ func (s DataService) Query(v interface{}, filter *filter.Filter, multiQuery bool return cursor.All(ctx, v) } +func (s DataService) QueryOne(v interface{}, filter *filter.Filter) error { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + if err := s.collection(reflect.TypeOf(v)).FindOne(ctx, filter).Decode(v); err != nil { + return err + } + + return nil +} + func (s DataService) Update(v interface{}, filter *filter.Filter) error { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() diff --git a/service/filter/filter.go b/service/filter/filter.go new file mode 100644 index 0000000..5d1fa7a --- /dev/null +++ b/service/filter/filter.go @@ -0,0 +1,40 @@ +package filter + +import ( + "net/http" + + "github.com/go-chi/chi" + "go.mongodb.org/mongo-driver/bson" +) + +type Filter bson.M + +func ParseFromRequest(r *http.Request) (*Filter, error) { + var filter Filter + + if err := bson.UnmarshalExtJSON([]byte(chi.URLParam(r, "filter")), true, &filter); err != nil { + return nil, err + } + + return &filter, nil +} + +func (filter *Filter) Set(key string, value interface{}) { + if filter == nil { + filter = &Filter{} + } + + (*filter)[key] = value +} + +func (filter *Filter) Del(key string) { + if filter == nil { + return + } + + if _, ok := (*filter)[key]; !ok { + return + } + + delete(*filter, key) +} diff --git a/service/id/id.go b/service/id/id.go new file mode 100644 index 0000000..2fdd020 --- /dev/null +++ b/service/id/id.go @@ -0,0 +1,18 @@ +package id + +import ( + "net/http" + + "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/go-chi/chi" +) + +func ParseFromRequest(r *http.Request) (*primitive.ObjectID, error) { + id, err := primitive.ObjectIDFromHex(chi.URLParam(r, "id")) + if err != nil { + return nil, err + } + + return &id, nil +} diff --git a/service/service.go b/service/service.go index ee198aa..0d2548c 100644 --- a/service/service.go +++ b/service/service.go @@ -34,8 +34,9 @@ func New(ds config.DataService) (*DataService, error) { } return &DataService{ - client: client, - db: client.Database(ds.Database), + client: client, + db: client.Database(ds.Database), + collections: map[reflect.Type]*mongo.Collection{}, }, nil } diff --git a/static/styles.css b/static/styles.css new file mode 100755 index 0000000..70a16db --- /dev/null +++ b/static/styles.css @@ -0,0 +1,22 @@ +*{ + padding: 0; + margin: 0; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + +} +.content { + max-width: 500px; + margin: auto; +} + +h2{ + padding-top: 20px; +} + +small{ + margin-left: 10px; +} + +p.client{ + margin-left: 10px; +} \ No newline at end of file diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 0000000..5ff2593 --- /dev/null +++ b/templates/error.html @@ -0,0 +1,11 @@ + + + + Error + + +

Oops, an error occurred!

+ Error {{.StatusCode}} + {{.Error}} + + \ No newline at end of file diff --git a/templates/gameDetail.html b/templates/gameDetail.html new file mode 100644 index 0000000..f381ec1 --- /dev/null +++ b/templates/gameDetail.html @@ -0,0 +1,24 @@ + + + + Abistreiche + + + Gruppen +

{{.Group.Name}}

+ + + + \ No newline at end of file diff --git a/templates/gameMaster.html b/templates/gameMaster.html new file mode 100644 index 0000000..e70d52c --- /dev/null +++ b/templates/gameMaster.html @@ -0,0 +1,15 @@ + + + + Abistreiche + + + Abistreiche + + + + \ No newline at end of file diff --git a/templates/groupDetail.html b/templates/groupDetail.html new file mode 100644 index 0000000..8d993e2 --- /dev/null +++ b/templates/groupDetail.html @@ -0,0 +1,15 @@ + + + + Abistreiche + + + Gruppen +

{{.Group.Name}}

+ + + \ No newline at end of file diff --git a/templates/groupMaster.html b/templates/groupMaster.html new file mode 100644 index 0000000..5fd1c5f --- /dev/null +++ b/templates/groupMaster.html @@ -0,0 +1,16 @@ + + + + Abistreiche + + + Abistreiche +

Gruppen

+ + + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..afa0ae1 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,11 @@ + + + + Abistreiche + + +

Abistreiche

+ Spiele + Gruppen + + \ No newline at end of file