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)) }