feat: game lobby service
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
package publichttp
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"galaxy/lobby/internal/service/listmyapplications"
|
||||
"galaxy/lobby/internal/service/listmygames"
|
||||
"galaxy/lobby/internal/service/listmyinvites"
|
||||
)
|
||||
|
||||
// Public HTTP route patterns for the user-facing list routes.
|
||||
const (
|
||||
myGamesPath = "/api/v1/lobby/my/games"
|
||||
myApplicationsPath = "/api/v1/lobby/my/applications"
|
||||
myInvitesPath = "/api/v1/lobby/my/invites"
|
||||
)
|
||||
|
||||
// registerMyListRoutes binds the three «my» routes on the
|
||||
// public port. Every route requires the X-User-ID header and rejects
|
||||
// admin actors at the service layer with shared.ErrForbidden.
|
||||
func registerMyListRoutes(mux *http.ServeMux, deps Dependencies, logger *slog.Logger) {
|
||||
h := &myListHandlers{
|
||||
deps: deps,
|
||||
logger: logger.With("component", "public_http.mylists"),
|
||||
}
|
||||
mux.HandleFunc("GET "+myGamesPath, h.handleListGames)
|
||||
mux.HandleFunc("GET "+myApplicationsPath, h.handleListApplications)
|
||||
mux.HandleFunc("GET "+myInvitesPath, h.handleListInvites)
|
||||
}
|
||||
|
||||
type myListHandlers struct {
|
||||
deps Dependencies
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// myApplicationItem mirrors the OpenAPI MyApplicationItem schema. It
|
||||
// embeds every field of the canonical ApplicationRecord plus the
|
||||
// game-display fields the personal list needs.
|
||||
type myApplicationItem struct {
|
||||
ApplicationID string `json:"application_id"`
|
||||
GameID string `json:"game_id"`
|
||||
ApplicantUserID string `json:"applicant_user_id"`
|
||||
RaceName string `json:"race_name"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
DecidedAt *int64 `json:"decided_at,omitempty"`
|
||||
GameName string `json:"game_name"`
|
||||
GameType string `json:"game_type"`
|
||||
}
|
||||
|
||||
// myApplicationListResponse mirrors MyApplicationListResponse.
|
||||
type myApplicationListResponse struct {
|
||||
Items []myApplicationItem `json:"items"`
|
||||
NextPageToken string `json:"next_page_token,omitempty"`
|
||||
}
|
||||
|
||||
func encodeMyApplicationList(out listmyapplications.Output) myApplicationListResponse {
|
||||
resp := myApplicationListResponse{
|
||||
Items: make([]myApplicationItem, 0, len(out.Items)),
|
||||
NextPageToken: out.NextPageToken,
|
||||
}
|
||||
for _, item := range out.Items {
|
||||
entry := myApplicationItem{
|
||||
ApplicationID: item.Application.ApplicationID.String(),
|
||||
GameID: item.Application.GameID.String(),
|
||||
ApplicantUserID: item.Application.ApplicantUserID,
|
||||
RaceName: item.Application.RaceName,
|
||||
Status: string(item.Application.Status),
|
||||
CreatedAt: item.Application.CreatedAt.UTC().UnixMilli(),
|
||||
GameName: item.GameName,
|
||||
GameType: string(item.GameType),
|
||||
}
|
||||
if item.Application.DecidedAt != nil {
|
||||
decided := item.Application.DecidedAt.UTC().UnixMilli()
|
||||
entry.DecidedAt = &decided
|
||||
}
|
||||
resp.Items = append(resp.Items, entry)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
// myInviteItem mirrors the OpenAPI MyInviteItem schema. It embeds
|
||||
// every field of the canonical InviteRecord plus the game-display
|
||||
// fields the personal list needs.
|
||||
type myInviteItem struct {
|
||||
InviteID string `json:"invite_id"`
|
||||
GameID string `json:"game_id"`
|
||||
InviterUserID string `json:"inviter_user_id"`
|
||||
InviteeUserID string `json:"invitee_user_id"`
|
||||
RaceName string `json:"race_name,omitempty"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
DecidedAt *int64 `json:"decided_at,omitempty"`
|
||||
GameName string `json:"game_name"`
|
||||
InviterName string `json:"inviter_name"`
|
||||
}
|
||||
|
||||
// myInviteListResponse mirrors MyInviteListResponse.
|
||||
type myInviteListResponse struct {
|
||||
Items []myInviteItem `json:"items"`
|
||||
NextPageToken string `json:"next_page_token,omitempty"`
|
||||
}
|
||||
|
||||
func encodeMyInviteList(out listmyinvites.Output) myInviteListResponse {
|
||||
resp := myInviteListResponse{
|
||||
Items: make([]myInviteItem, 0, len(out.Items)),
|
||||
NextPageToken: out.NextPageToken,
|
||||
}
|
||||
for _, item := range out.Items {
|
||||
entry := myInviteItem{
|
||||
InviteID: item.Invite.InviteID.String(),
|
||||
GameID: item.Invite.GameID.String(),
|
||||
InviterUserID: item.Invite.InviterUserID,
|
||||
InviteeUserID: item.Invite.InviteeUserID,
|
||||
RaceName: item.Invite.RaceName,
|
||||
Status: string(item.Invite.Status),
|
||||
CreatedAt: item.Invite.CreatedAt.UTC().UnixMilli(),
|
||||
ExpiresAt: item.Invite.ExpiresAt.UTC().UnixMilli(),
|
||||
GameName: item.GameName,
|
||||
InviterName: item.InviterName,
|
||||
}
|
||||
if item.Invite.DecidedAt != nil {
|
||||
decided := item.Invite.DecidedAt.UTC().UnixMilli()
|
||||
entry.DecidedAt = &decided
|
||||
}
|
||||
resp.Items = append(resp.Items, entry)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func (h *myListHandlers) handleListGames(writer http.ResponseWriter, request *http.Request) {
|
||||
if h.deps.ListMyGames == nil {
|
||||
writeError(writer, http.StatusInternalServerError, "internal_error", "list my games service is not wired")
|
||||
return
|
||||
}
|
||||
games := &gameHandlers{deps: h.deps, logger: h.logger}
|
||||
actor, ok := games.requireUserActor(writer, request)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
page, ok := parsePage(writer, request)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
out, err := h.deps.ListMyGames.Handle(request.Context(), listmygames.Input{
|
||||
Actor: actor,
|
||||
Page: page,
|
||||
})
|
||||
if err != nil {
|
||||
writeErrorFromService(writer, h.logger, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(writer, http.StatusOK, encodeGameList(out.Items, out.NextPageToken))
|
||||
}
|
||||
|
||||
func (h *myListHandlers) handleListApplications(writer http.ResponseWriter, request *http.Request) {
|
||||
if h.deps.ListMyApplications == nil {
|
||||
writeError(writer, http.StatusInternalServerError, "internal_error",
|
||||
"list my applications service is not wired")
|
||||
return
|
||||
}
|
||||
games := &gameHandlers{deps: h.deps, logger: h.logger}
|
||||
actor, ok := games.requireUserActor(writer, request)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
page, ok := parsePage(writer, request)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
out, err := h.deps.ListMyApplications.Handle(request.Context(), listmyapplications.Input{
|
||||
Actor: actor,
|
||||
Page: page,
|
||||
})
|
||||
if err != nil {
|
||||
writeErrorFromService(writer, h.logger, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(writer, http.StatusOK, encodeMyApplicationList(out))
|
||||
}
|
||||
|
||||
func (h *myListHandlers) handleListInvites(writer http.ResponseWriter, request *http.Request) {
|
||||
if h.deps.ListMyInvites == nil {
|
||||
writeError(writer, http.StatusInternalServerError, "internal_error",
|
||||
"list my invites service is not wired")
|
||||
return
|
||||
}
|
||||
games := &gameHandlers{deps: h.deps, logger: h.logger}
|
||||
actor, ok := games.requireUserActor(writer, request)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
page, ok := parsePage(writer, request)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
out, err := h.deps.ListMyInvites.Handle(request.Context(), listmyinvites.Input{
|
||||
Actor: actor,
|
||||
Page: page,
|
||||
})
|
||||
if err != nil {
|
||||
writeErrorFromService(writer, h.logger, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(writer, http.StatusOK, encodeMyInviteList(out))
|
||||
}
|
||||
Reference in New Issue
Block a user