feat: game lobby service
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
package internalhttp
|
||||
|
||||
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"
|
||||
"galaxy/lobby/internal/service/shared"
|
||||
)
|
||||
|
||||
// Internal HTTP route patterns for the membership
|
||||
// operations.
|
||||
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 internal port. The actor is always admin (Admin
|
||||
// Service / Game Master are the trusted callers).
|
||||
func registerMembershipRoutes(mux *http.ServeMux, deps Dependencies, logger *slog.Logger) {
|
||||
h := &membershipHandlers{
|
||||
deps: deps,
|
||||
logger: logger.With("component", "internal_http.memberships"),
|
||||
}
|
||||
mux.HandleFunc("GET "+listMembershipsPath, h.handleList)
|
||||
mux.HandleFunc("GET "+internalGameMembershipPath, 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}
|
||||
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: shared.NewAdminActor(),
|
||||
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}
|
||||
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: shared.NewAdminActor(),
|
||||
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}
|
||||
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: shared.NewAdminActor(),
|
||||
GameID: gameID,
|
||||
MembershipID: membershipID,
|
||||
})
|
||||
if err != nil {
|
||||
writeErrorFromService(writer, h.logger, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(writer, http.StatusOK, encodeMembershipRecord(record))
|
||||
}
|
||||
Reference in New Issue
Block a user