181 lines
5.3 KiB
Go
181 lines
5.3 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"galaxy/backend/internal/lobby"
|
|
"galaxy/backend/internal/server/handlers"
|
|
"galaxy/backend/internal/server/httperr"
|
|
"galaxy/backend/internal/server/middleware/userid"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// UserLobbyInvitesHandlers groups the invite-lifecycle handlers under
|
|
// `/api/v1/user/lobby/games/{game_id}/invites/*`. The current implementation ships real
|
|
// implementations backed by `*lobby.Service`.
|
|
type UserLobbyInvitesHandlers struct {
|
|
svc *lobby.Service
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewUserLobbyInvitesHandlers constructs the handler set. svc may be
|
|
// nil — in that case every handler returns 501 not_implemented.
|
|
func NewUserLobbyInvitesHandlers(svc *lobby.Service, logger *zap.Logger) *UserLobbyInvitesHandlers {
|
|
if logger == nil {
|
|
logger = zap.NewNop()
|
|
}
|
|
return &UserLobbyInvitesHandlers{svc: svc, logger: logger.Named("http.user.lobby.invites")}
|
|
}
|
|
|
|
// Issue handles POST /api/v1/user/lobby/games/{game_id}/invites.
|
|
func (h *UserLobbyInvitesHandlers) Issue() gin.HandlerFunc {
|
|
if h.svc == nil {
|
|
return handlers.NotImplemented("userLobbyInvitesIssue")
|
|
}
|
|
return func(c *gin.Context) {
|
|
userID, ok := userid.FromContext(c.Request.Context())
|
|
if !ok {
|
|
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "X-User-ID header is required")
|
|
return
|
|
}
|
|
gameID, ok := parseGameIDParam(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var req lobbyInviteIssueRequestWire
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be valid JSON")
|
|
return
|
|
}
|
|
var invitedID *uuid.UUID
|
|
if req.InvitedUserID != nil && *req.InvitedUserID != "" {
|
|
parsed, err := uuid.Parse(*req.InvitedUserID)
|
|
if err != nil {
|
|
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "invited_user_id must be a valid UUID")
|
|
return
|
|
}
|
|
invitedID = &parsed
|
|
}
|
|
expires, err := parseTimePtrField(req.ExpiresAt, "expires_at")
|
|
if err != nil {
|
|
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, err.Error())
|
|
return
|
|
}
|
|
ctx := c.Request.Context()
|
|
raceName := ""
|
|
if req.RaceName != nil {
|
|
raceName = *req.RaceName
|
|
}
|
|
invite, err := h.svc.IssueInvite(ctx, lobby.IssueInviteInput{
|
|
GameID: gameID,
|
|
InviterUserID: userID,
|
|
InvitedUserID: invitedID,
|
|
RaceName: raceName,
|
|
ExpiresAt: expires,
|
|
})
|
|
if err != nil {
|
|
respondLobbyError(c, h.logger, "user lobby invites issue", ctx, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, lobbyInviteDetailToWire(invite))
|
|
}
|
|
}
|
|
|
|
// Redeem handles POST /api/v1/user/lobby/games/{game_id}/invites/{invite_id}/redeem.
|
|
func (h *UserLobbyInvitesHandlers) Redeem() gin.HandlerFunc {
|
|
if h.svc == nil {
|
|
return handlers.NotImplemented("userLobbyInvitesRedeem")
|
|
}
|
|
return func(c *gin.Context) {
|
|
userID, ok := userid.FromContext(c.Request.Context())
|
|
if !ok {
|
|
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "X-User-ID header is required")
|
|
return
|
|
}
|
|
gameID, ok := parseGameIDParam(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
inviteID, ok := parseInviteIDParam(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
ctx := c.Request.Context()
|
|
invite, err := h.svc.RedeemInvite(ctx, userID, gameID, inviteID)
|
|
if err != nil {
|
|
respondLobbyError(c, h.logger, "user lobby invites redeem", ctx, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, lobbyInviteDetailToWire(invite))
|
|
}
|
|
}
|
|
|
|
// Decline handles POST /api/v1/user/lobby/games/{game_id}/invites/{invite_id}/decline.
|
|
func (h *UserLobbyInvitesHandlers) Decline() gin.HandlerFunc {
|
|
if h.svc == nil {
|
|
return handlers.NotImplemented("userLobbyInvitesDecline")
|
|
}
|
|
return func(c *gin.Context) {
|
|
userID, ok := userid.FromContext(c.Request.Context())
|
|
if !ok {
|
|
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "X-User-ID header is required")
|
|
return
|
|
}
|
|
gameID, ok := parseGameIDParam(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
inviteID, ok := parseInviteIDParam(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
ctx := c.Request.Context()
|
|
invite, err := h.svc.DeclineInvite(ctx, userID, gameID, inviteID)
|
|
if err != nil {
|
|
respondLobbyError(c, h.logger, "user lobby invites decline", ctx, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, lobbyInviteDetailToWire(invite))
|
|
}
|
|
}
|
|
|
|
// Revoke handles POST /api/v1/user/lobby/games/{game_id}/invites/{invite_id}/revoke.
|
|
func (h *UserLobbyInvitesHandlers) Revoke() gin.HandlerFunc {
|
|
if h.svc == nil {
|
|
return handlers.NotImplemented("userLobbyInvitesRevoke")
|
|
}
|
|
return func(c *gin.Context) {
|
|
userID, ok := userid.FromContext(c.Request.Context())
|
|
if !ok {
|
|
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "X-User-ID header is required")
|
|
return
|
|
}
|
|
gameID, ok := parseGameIDParam(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
inviteID, ok := parseInviteIDParam(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
ctx := c.Request.Context()
|
|
caller := userID
|
|
invite, err := h.svc.RevokeInvite(ctx, &caller, false, gameID, inviteID)
|
|
if err != nil {
|
|
respondLobbyError(c, h.logger, "user lobby invites revoke", ctx, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, lobbyInviteDetailToWire(invite))
|
|
}
|
|
}
|
|
|
|
// lobbyInviteIssueRequestWire mirrors `LobbyInviteIssueRequest`.
|
|
type lobbyInviteIssueRequestWire struct {
|
|
InvitedUserID *string `json:"invited_user_id,omitempty"`
|
|
RaceName *string `json:"race_name,omitempty"`
|
|
ExpiresAt *string `json:"expires_at,omitempty"`
|
|
}
|