You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

131 lines
3.6 KiB

package httpapi
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"net/http"
"slices"
"strings"
"juegoland/games2/contracts/shared/go"
"juegoland/games2/internal/domain/catalog"
"juegoland/games2/internal/server"
)
type Handler struct {
service *server.Service
}
type gameView = shared.GameCard
type response[T any] struct {
RequestID string `json:"requestId"`
Data T `json:"data"`
}
type catalogData struct {
Items []gameView `json:"items"`
NextCursor *string `json:"nextCursor"`
}
func NewHandler(service *server.Service) http.Handler {
h := &Handler{service: service}
mux := http.NewServeMux()
mux.HandleFunc("GET /live", h.live)
mux.HandleFunc("GET /ready", h.ready)
mux.HandleFunc("GET /api/v2/bootstrap", h.bootstrap)
mux.HandleFunc("GET /api/v2/catalog", h.catalog)
return mux
}
func (h *Handler) live(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "live"})
}
func (h *Handler) ready(w http.ResponseWriter, _ *http.Request) {
// This only asserts that the bootstrap server can serve its current
// in-memory catalog. PostgreSQL and games are not enabled in this phase.
writeJSON(w, http.StatusOK, map[string]string{"status": "ready", "phase": "foundation"})
}
func (h *Handler) bootstrap(w http.ResponseWriter, _ *http.Request) {
id, ok := newRequestID(w)
if !ok {
return
}
writeJSON(w, http.StatusOK, response[server.Bootstrap]{RequestID: id, Data: h.service.Bootstrap()})
}
func (h *Handler) catalog(w http.ResponseWriter, r *http.Request) {
id, ok := newRequestID(w)
if !ok {
return
}
locale := selectLocale(r.Header.Get("Accept-Language"))
games := h.service.PublishedGames()
items := make([]gameView, 0, len(games))
languages := make(map[string]bool)
for _, game := range games {
item := toGameView(game, locale)
items = append(items, item)
languages[string(item.Locale)] = true
}
if len(languages) == 0 {
languages[locale] = true
}
usedLanguages := make([]string, 0, len(languages))
for language := range languages {
usedLanguages = append(usedLanguages, language)
}
slices.Sort(usedLanguages)
w.Header().Set("Content-Language", strings.Join(usedLanguages, ", "))
w.Header().Set("Vary", "Accept-Language")
writeJSON(w, http.StatusOK, response[catalogData]{
RequestID: id,
Data: catalogData{Items: items, NextCursor: nil},
})
}
func toGameView(game catalog.Game, locale string) gameView {
copy, usedLocale := game.Text(locale)
return gameView{
ID: game.ID,
Version: game.Version,
Title: copy.Title,
Summary: copy.Summary,
Locale: shared.Locale(usedLocale),
MinPlayers: int64(game.MinPlayers),
MaxPlayers: int64(game.MaxPlayers),
}
}
func newRequestID(w http.ResponseWriter) (string, bool) {
var bytes [16]byte
if _, err := rand.Read(bytes[:]); err != nil {
http.Error(w, "Unable to create request ID", http.StatusInternalServerError)
return "", false
}
bytes[6] = (bytes[6] & 0x0f) | 0x40
bytes[8] = (bytes[8] & 0x3f) | 0x80
var encoded [36]byte
hex.Encode(encoded[0:8], bytes[0:4])
encoded[8] = '-'
hex.Encode(encoded[9:13], bytes[4:6])
encoded[13] = '-'
hex.Encode(encoded[14:18], bytes[6:8])
encoded[18] = '-'
hex.Encode(encoded[19:23], bytes[8:10])
encoded[23] = '-'
hex.Encode(encoded[24:36], bytes[10:16])
return string(encoded[:]), true
}
func writeJSON(w http.ResponseWriter, status int, body any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(body)
}

Powered by TurnKey Linux.