logic done

This commit is contained in:
Hendrik Schlehlein 2019-05-27 23:46:09 +02:00
parent ef52fa1c58
commit 960dd25bbd
34 changed files with 822 additions and 45 deletions

7
config.yaml Normal file
View file

@ -0,0 +1,7 @@
Port: 8080
DataService:
IP: 127.0.0.1
Port: 27017
Username: haveachin
Password: vKz8VmTTB4HZx2
Database: scoreboard

View file

@ -37,4 +37,7 @@ func setDefaults() {
viper.SetDefault("Port", "8080") viper.SetDefault("Port", "8080")
viper.SetDefault("DataService.IP", "127.0.0.1") viper.SetDefault("DataService.IP", "127.0.0.1")
viper.SetDefault("DataService.Port", "27017") viper.SetDefault("DataService.Port", "27017")
viper.SetDefault("DataService.Username", "haveachin")
viper.SetDefault("DataService.Password", "<changeme>")
viper.SetDefault("DataService.Database", "scoreboard")
} }

8
data/game.go Normal file
View file

@ -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"`
}

14
data/group.go Normal file
View file

@ -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"`
}

58
features/game/handler.go Normal file
View file

@ -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
}))
})
}

79
features/game/page.go Normal file
View file

@ -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)
}

23
features/game/routes.go Normal file
View file

@ -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
}

58
features/group/handler.go Normal file
View file

@ -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
}))
})
}

62
features/group/page.go Normal file
View file

@ -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)
}

23
features/group/routes.go Normal file
View file

@ -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
}

69
features/handler.go Normal file
View file

@ -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)
}))
})
}

17
features/index/handler.go Normal file
View file

@ -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
}))
})
}

20
features/index/page.go Normal file
View file

@ -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)
}

16
features/index/routes.go Normal file
View file

@ -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
}

16
features/service.go Normal file
View file

@ -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)
}

24
features/weberror/page.go Normal file
View file

@ -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)
}

35
gui/template.go Normal file
View file

@ -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
}

45
ioutil/reader.go Normal file
View file

@ -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
}

47
main.go
View file

@ -2,37 +2,35 @@ package main
import ( import (
"fmt" "fmt"
"html/template"
"log" "log"
"net/http" "net/http"
"time" "time"
"git.cliffbreak.de/Cliffbreak/tsviewer/config" "git.cliffbreak.de/haveachin/scoreboard/config"
"git.cliffbreak.de/Cliffbreak/tsviewer/service" "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"
"github.com/go-chi/chi/middleware" "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() router := chi.NewRouter()
cors := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"Get"},
})
router.Use( router.Use(
middleware.Logger, middleware.Logger,
middleware.Timeout(5*time.Second), middleware.Timeout(5*time.Second),
middleware.DefaultCompress,
middleware.RedirectSlashes, middleware.RedirectSlashes,
middleware.Recoverer, middleware.Recoverer,
cors.Handler,
) )
router.Route("/", func(r chi.Router) { 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 return router
@ -43,21 +41,28 @@ func main() {
config, err := config.New() config, err := config.New()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
return
} }
log.Println("Configurations loaded!") log.Println("Configurations loaded!")
log.Println("Starting query service...") log.Println("Starting query service...")
service, err := service.New(*config) service, err := service.New(config.DataService)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
return
} }
defer service.TSClient.Close() log.Println("Query service connected to", fmt.Sprintf("%s:%d!", config.DataService.IP, config.DataService.Port))
log.Println("Query service connected to", fmt.Sprintf("%s:%d!", config.ServerTS.IP, config.ServerTS.PortQuery))
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) router := Routes(service, templates)
log.Fatal("Handler: ", http.ListenAndServe(fmt.Sprintf(":%d", config.ServerWeb.Port), router)) 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))
} }

View file

@ -5,10 +5,10 @@ import (
"log" "log"
"net/http" "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 // Responder is a service for responses
type Responder interface { type Responder interface {
@ -26,11 +26,9 @@ type Envelope struct {
} }
// New creates a new Response // New creates a new Response
func New(content interface{}, r *http.Request) *Response { func New(content interface{}, meta *meta.Meta) *Response {
var resp Response var resp Response
meta := meta.NewFromRequest(r)
if meta.Envelope { if meta.Envelope {
resp = Response{ resp = Response{
Content: Envelope{ Content: Envelope{
@ -74,14 +72,14 @@ func (resp Response) Send(w http.ResponseWriter, status int) (int, error) {
return status, nil return status, nil
} }
func Handler(w http.ResponseWriter, r *http.Request, hf HandlerFunc) { func APIHandler(w http.ResponseWriter, hf APIHandlerFunc) {
status, err := hf() status, err := hf()
if err == nil { if err == nil {
return return
} }
log.Printf("HTTP %d: %q", status, err) 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) http.Error(w, http.StatusText(status), status)
} }
} }

View file

@ -10,10 +10,12 @@ type Error struct {
} }
// NewError creates a new Response fill with an error // NewError creates a new Response fill with an error
func NewError(status int, err error) *Error { func NewError(status int, err error) *Response {
return &Error{ return &Response{
Content: Error{
Status: status, Status: status,
Error: err.Error(), Error: err.Error(),
Timestamp: time.Now().Format("2006-01-02T15:04:05Z"), //.Format("02 Jan 2006, 15:04:05 MST"), Timestamp: time.Now().Format("2006-01-02T15:04:05Z"), //.Format("02 Jan 2006, 15:04:05 MST"),
},
} }
} }

19
response/web.go Normal file
View file

@ -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)
}

View file

@ -5,7 +5,7 @@ import (
"reflect" "reflect"
"time" "time"
"github.com/haveachin/sawtexapi/service/mongodb/filter" "git.cliffbreak.de/haveachin/scoreboard/service/filter"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
) )
@ -25,18 +25,10 @@ func (s DataService) Create(v interface{}) error {
return nil 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) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() 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) cursor, err := s.collection(reflect.TypeOf(v)).Find(ctx, filter)
if err != nil { if err != nil {
return err return err
@ -45,6 +37,17 @@ func (s DataService) Query(v interface{}, filter *filter.Filter, multiQuery bool
return cursor.All(ctx, v) 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 { func (s DataService) Update(v interface{}, filter *filter.Filter) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()

40
service/filter/filter.go Normal file
View file

@ -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)
}

18
service/id/id.go Normal file
View file

@ -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
}

View file

@ -36,6 +36,7 @@ func New(ds config.DataService) (*DataService, error) {
return &DataService{ return &DataService{
client: client, client: client,
db: client.Database(ds.Database), db: client.Database(ds.Database),
collections: map[reflect.Type]*mongo.Collection{},
}, nil }, nil
} }

22
static/styles.css Executable file
View file

@ -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;
}

11
templates/error.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
</head>
<body>
<h1>Oops, an error occurred!</h1>
<p1>Error {{.StatusCode}}</p1>
<p1>{{.Error}}</p1>
</body>
</html>

24
templates/gameDetail.html Normal file
View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>Abistreiche</title>
</head>
<body>
<a href="/groups">Gruppen</a>
<h1>{{.Group.Name}}</h1>
<input type="text" placeholder="Search...">
<ul>
{{range .Group.Scores}}
<li>
{{$gameID := .GameID}}
{{range $.Games}}
{{if eq .ID $gameID}}
<a href="/games/{{$gameID}}">{{.Name}}</a>
{{end}}
{{end}}
<p>{{.Points}}</p>
</li>
{{end}}
</ul>
</body>
</html>

15
templates/gameMaster.html Normal file
View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<title>Abistreiche</title>
</head>
<body>
<a href="/">Abistreiche</a>
<input type="text" placeholder="Search...">
<ul>
{{range .Games}}
<li><a href="/games/{{.ID}}">{{.Name}}</a></li>
{{end}}
</ul>
</body>
</html>

View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<title>Abistreiche</title>
</head>
<body>
<a href="/groups">Gruppen</a>
<h1>{{.Group.Name}}</h1>
<ul>
{{range .Group}}
<li><a href="/groups/{{.ID}}">{{.Name}}</a><span>{{.Points}}</span>/li>
{{end}}
</ul>
</body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<title>Abistreiche</title>
</head>
<body>
<a href="/">Abistreiche</a>
<h1>Gruppen</h1>
<input type="text" placeholder="Search...">
<ul>
{{range .Groups}}
<li><a href="/groups/{{.ID}}">{{.Name}}</a><span>{{.PointSum}}</span></li>
{{end}}
</ul>
</body>
</html>

11
templates/index.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Abistreiche</title>
</head>
<body>
<h1>Abistreiche</h1>
<a href="/games">Spiele</a>
<a href="/groups">Gruppen</a>
</body>
</html>