-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.go
More file actions
100 lines (87 loc) · 3.04 KB
/
server.go
File metadata and controls
100 lines (87 loc) · 3.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package api
import (
"bytes"
"database/sql"
"embed"
"fmt"
"html/template"
"io/fs"
"net/http"
"github.com/go-chi/chi/v5"
chimiddleware "github.com/go-chi/chi/v5/middleware"
"github.com/gameshelf/gameshelf/internal/config"
"github.com/gameshelf/gameshelf/internal/leaderboard"
"github.com/gameshelf/gameshelf/internal/sdk"
)
// Server holds all application dependencies.
type Server struct {
db *sql.DB
lb *leaderboard.Client
sdk *sdk.Client
tmpls map[string]*template.Template
staticFS fs.FS
cfg config.Config
}
// pageNames lists the templates that can be rendered.
var pageNames = []string{"index.html", "game.html", "leaderboard.html", "admin.html"}
// NewServer constructs a Server, parsing each page template together with base.html.
func NewServer(db *sql.DB, lb *leaderboard.Client, sdkClient *sdk.Client, templatesFS embed.FS, staticFS embed.FS, cfg config.Config) (*Server, error) {
tmpls := make(map[string]*template.Template, len(pageNames))
for _, page := range pageNames {
t, err := template.ParseFS(templatesFS, "templates/base.html", "templates/"+page)
if err != nil {
return nil, fmt.Errorf("parsing template %s: %w", page, err)
}
tmpls[page] = t
}
stripped, err := fs.Sub(staticFS, "static")
if err != nil {
return nil, fmt.Errorf("sub static fs: %w", err)
}
return &Server{db: db, lb: lb, sdk: sdkClient, tmpls: tmpls, staticFS: stripped, cfg: cfg}, nil
}
// render executes a named template with buffering to prevent partial responses.
func (s *Server) render(w http.ResponseWriter, name string, data PageData) {
t, ok := s.tmpls[name]
if !ok {
http.Error(w, "unknown template: "+name, http.StatusInternalServerError)
return
}
var buf bytes.Buffer
if err := t.ExecuteTemplate(&buf, name, data); err != nil {
http.Error(w, "template error: "+err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
buf.WriteTo(w) //nolint:errcheck
}
// Handler builds and returns the root http.Handler.
func (s *Server) Handler() http.Handler {
r := chi.NewRouter()
r.Use(chimiddleware.Logger)
r.Use(chimiddleware.Recoverer)
// Static files served at /static/*
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.FS(s.staticFS))))
// Public pages
r.Get("/", s.indexHandler)
r.Get("/games/{slug}", s.gameHandler)
r.Get("/leaderboard/{slug}", s.leaderboardHandler)
r.Get("/logo", s.logoHandler)
// Score API
r.Post("/api/scores", s.submitScoreHandler)
r.Get("/api/scores/{slug}", s.getScoresHandler)
// Admin (SDK entitlement gate runs first, then auth)
r.Group(func(r chi.Router) {
r.Use(s.sdkAdminGateMiddleware)
r.Use(s.adminAuthMiddleware)
r.Get("/admin", s.adminHandler)
r.Post("/admin/games/{slug}/toggle", s.toggleGameHandler)
r.Post("/admin/branding", s.updateBrandingHandler)
r.Post("/admin/logo", s.uploadLogoHandler)
r.Post("/admin/identity/regenerate", s.regenerateIdentitySecretHandler)
r.Post("/admin/support-bundle", s.supportBundleHandler)
})
// Health
r.Get("/healthz", s.healthHandler)
return r
}