Files
galaxy-game/lobby/internal/api/publichttp/memberships.go
T
2026-04-25 23:20:55 +02:00

166 lines
4.8 KiB
Go

package publichttp
import (
"log/slog"
"net/http"
"strings"
"galaxy/lobby/internal/domain/common"
"galaxy/lobby/internal/domain/membership"
"galaxy/lobby/internal/service/blockmember"
"galaxy/lobby/internal/service/listmemberships"
"galaxy/lobby/internal/service/removemember"
)
// Public HTTP route patterns for the membership routes.
const (
listMembershipsPath = "/api/v1/lobby/games/{game_id}/memberships"
removeMemberPath = "/api/v1/lobby/games/{game_id}/memberships/{membership_id}/remove"
blockMemberPath = "/api/v1/lobby/games/{game_id}/memberships/{membership_id}/block"
membershipIDPathParamValue = "membership_id"
)
// registerMembershipRoutes binds the membership
// routes on the public port. The X-User-ID header is required on every
// route; admins use the internal port equivalents.
func registerMembershipRoutes(mux *http.ServeMux, deps Dependencies, logger *slog.Logger) {
h := &membershipHandlers{
deps: deps,
logger: logger.With("component", "public_http.memberships"),
}
mux.HandleFunc("GET "+listMembershipsPath, h.handleList)
mux.HandleFunc("POST "+removeMemberPath, h.handleRemove)
mux.HandleFunc("POST "+blockMemberPath, h.handleBlock)
}
type membershipHandlers struct {
deps Dependencies
logger *slog.Logger
}
func (h *membershipHandlers) extractMembershipID(writer http.ResponseWriter, request *http.Request) (common.MembershipID, bool) {
raw := request.PathValue(membershipIDPathParamValue)
if strings.TrimSpace(raw) == "" {
writeError(writer, http.StatusBadRequest, "invalid_request", "membership id is required")
return "", false
}
return common.MembershipID(raw), true
}
func (h *membershipHandlers) handleRemove(writer http.ResponseWriter, request *http.Request) {
if h.deps.RemoveMember == nil {
writeError(writer, http.StatusInternalServerError, "internal_error", "remove member service is not wired")
return
}
games := &gameHandlers{deps: h.deps, logger: h.logger}
actor, ok := games.requireUserActor(writer, request)
if !ok {
return
}
gameID, ok := games.extractGameID(writer, request)
if !ok {
return
}
membershipID, ok := h.extractMembershipID(writer, request)
if !ok {
return
}
record, err := h.deps.RemoveMember.Handle(request.Context(), removemember.Input{
Actor: actor,
GameID: gameID,
MembershipID: membershipID,
})
if err != nil {
writeErrorFromService(writer, h.logger, err)
return
}
writeJSON(writer, http.StatusOK, encodeMembershipRecord(record))
}
// membershipListResponse mirrors the OpenAPI MembershipListResponse
// schema. Items are always non-nil so the JSON form carries `[]` rather
// than `null` for empty pages.
type membershipListResponse struct {
Items []membershipRecordResponse `json:"items"`
NextPageToken string `json:"next_page_token,omitempty"`
}
func encodeMembershipList(items []membership.Membership, nextPageToken string) membershipListResponse {
resp := membershipListResponse{
Items: make([]membershipRecordResponse, 0, len(items)),
NextPageToken: nextPageToken,
}
for _, item := range items {
resp.Items = append(resp.Items, encodeMembershipRecord(item))
}
return resp
}
func (h *membershipHandlers) handleList(writer http.ResponseWriter, request *http.Request) {
if h.deps.ListMemberships == nil {
writeError(writer, http.StatusInternalServerError, "internal_error", "list memberships service is not wired")
return
}
games := &gameHandlers{deps: h.deps, logger: h.logger}
actor, ok := games.requireUserActor(writer, request)
if !ok {
return
}
gameID, ok := games.extractGameID(writer, request)
if !ok {
return
}
page, ok := parsePage(writer, request)
if !ok {
return
}
out, err := h.deps.ListMemberships.Handle(request.Context(), listmemberships.Input{
Actor: actor,
GameID: gameID,
Page: page,
})
if err != nil {
writeErrorFromService(writer, h.logger, err)
return
}
writeJSON(writer, http.StatusOK, encodeMembershipList(out.Items, out.NextPageToken))
}
func (h *membershipHandlers) handleBlock(writer http.ResponseWriter, request *http.Request) {
if h.deps.BlockMember == nil {
writeError(writer, http.StatusInternalServerError, "internal_error", "block member service is not wired")
return
}
games := &gameHandlers{deps: h.deps, logger: h.logger}
actor, ok := games.requireUserActor(writer, request)
if !ok {
return
}
gameID, ok := games.extractGameID(writer, request)
if !ok {
return
}
membershipID, ok := h.extractMembershipID(writer, request)
if !ok {
return
}
record, err := h.deps.BlockMember.Handle(request.Context(), blockmember.Input{
Actor: actor,
GameID: gameID,
MembershipID: membershipID,
})
if err != nil {
writeErrorFromService(writer, h.logger, err)
return
}
writeJSON(writer, http.StatusOK, encodeMembershipRecord(record))
}