package handlers import ( "context" "strconv" "github.com/gofiber/fiber/v3" "knowfoolery/backend/shared/types" "knowfoolery/backend/services/game-session-service/internal/application/services" "knowfoolery/backend/services/game-session-service/internal/application/dtos" ) // GameSessionHandler handles HTTP requests for game sessions type GameSessionHandler struct { applicationService *services.GameSessionApplicationService } // NewGameSessionHandler creates a new game session handler func NewGameSessionHandler(applicationService *services.GameSessionApplicationService) *GameSessionHandler { return &GameSessionHandler{ applicationService: applicationService, } } // StartNewSession handles POST /sessions func (h *GameSessionHandler) StartNewSession(c fiber.Ctx) error { var request dtos.StartSessionRequest if err := c.Bind().JSON(&request); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "invalid_request", "message": "Failed to parse request body", "details": err.Error(), }) } // Get user ID from context (set by auth middleware) userID := c.Locals("userID").(types.UserID) request.UserID = userID ctx := context.Background() response, err := h.applicationService.StartNewSession(ctx, &request) if err != nil { return h.handleError(c, err) } return c.Status(fiber.StatusCreated).JSON(response) } // ResumeSession handles GET /sessions/:sessionId/resume func (h *GameSessionHandler) ResumeSession(c fiber.Ctx) error { sessionIDStr := c.Params("sessionId") sessionID := types.GameSessionID(sessionIDStr) request := &dtos.ResumeSessionRequest{ SessionID: sessionID, } ctx := context.Background() response, err := h.applicationService.ResumeSession(ctx, request) if err != nil { return h.handleError(c, err) } return c.JSON(response) } // SubmitAnswer handles POST /sessions/:sessionId/questions/:questionId/answers func (h *GameSessionHandler) SubmitAnswer(c fiber.Ctx) error { sessionIDStr := c.Params("sessionId") questionIDStr := c.Params("questionId") sessionID := types.GameSessionID(sessionIDStr) questionID := types.QuestionID(questionIDStr) var requestBody struct { PlayerAnswer string `json:"player_answer"` HintUsed bool `json:"hint_used"` } if err := c.Bind().JSON(&requestBody); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "invalid_request", "message": "Failed to parse request body", "details": err.Error(), }) } request := &dtos.SubmitAnswerRequest{ SessionID: sessionID, QuestionID: questionID, PlayerAnswer: requestBody.PlayerAnswer, HintUsed: requestBody.HintUsed, } ctx := context.Background() response, err := h.applicationService.SubmitAnswer(ctx, request) if err != nil { return h.handleError(c, err) } if !response.Accepted { return c.Status(fiber.StatusBadRequest).JSON(response) } return c.JSON(response) } // RequestHint handles POST /sessions/:sessionId/questions/:questionId/hint func (h *GameSessionHandler) RequestHint(c fiber.Ctx) error { sessionIDStr := c.Params("sessionId") questionIDStr := c.Params("questionId") sessionID := types.GameSessionID(sessionIDStr) questionID := types.QuestionID(questionIDStr) request := &dtos.RequestHintRequest{ SessionID: sessionID, QuestionID: questionID, } ctx := context.Background() response, err := h.applicationService.RequestHint(ctx, request) if err != nil { return h.handleError(c, err) } return c.JSON(response) } // CompleteSession handles POST /sessions/:sessionId/complete func (h *GameSessionHandler) CompleteSession(c fiber.Ctx) error { sessionIDStr := c.Params("sessionId") sessionID := types.GameSessionID(sessionIDStr) var requestBody struct { Reason string `json:"reason"` } if err := c.Bind().JSON(&requestBody); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "invalid_request", "message": "Failed to parse request body", "details": err.Error(), }) } request := &dtos.CompleteSessionRequest{ SessionID: sessionID, Reason: requestBody.Reason, } ctx := context.Background() response, err := h.applicationService.CompleteSession(ctx, request) if err != nil { return h.handleError(c, err) } return c.JSON(response) } // AbandonSession handles POST /sessions/:sessionId/abandon func (h *GameSessionHandler) AbandonSession(c fiber.Ctx) error { sessionIDStr := c.Params("sessionId") sessionID := types.GameSessionID(sessionIDStr) request := &dtos.AbandonSessionRequest{ SessionID: sessionID, } ctx := context.Background() response, err := h.applicationService.AbandonSession(ctx, request) if err != nil { return h.handleError(c, err) } return c.JSON(response) } // GetSessionStatus handles GET /sessions/:sessionId func (h *GameSessionHandler) GetSessionStatus(c fiber.Ctx) error { sessionIDStr := c.Params("sessionId") sessionID := types.GameSessionID(sessionIDStr) request := &dtos.GetSessionStatusRequest{ SessionID: sessionID, } ctx := context.Background() response, err := h.applicationService.GetSessionStatus(ctx, request) if err != nil { return h.handleError(c, err) } return c.JSON(response) } // GetUserSessions handles GET /users/:userId/sessions func (h *GameSessionHandler) GetUserSessions(c fiber.Ctx) error { userIDStr := c.Params("userId") userID := types.UserID(userIDStr) // Parse query parameters limitStr := c.Query("limit", "20") offsetStr := c.Query("offset", "0") limit, err := strconv.Atoi(limitStr) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "invalid_parameter", "message": "Invalid limit parameter", }) } offset, err := strconv.Atoi(offsetStr) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "invalid_parameter", "message": "Invalid offset parameter", }) } request := &dtos.GetUserSessionsRequest{ UserID: userID, Limit: limit, Offset: offset, } ctx := context.Background() response, err := h.applicationService.GetUserSessions(ctx, request) if err != nil { return h.handleError(c, err) } return c.JSON(response) } // GetActiveSession handles GET /users/:userId/sessions/active func (h *GameSessionHandler) GetActiveSession(c fiber.Ctx) error { userIDStr := c.Params("userId") userID := types.UserID(userIDStr) request := &dtos.GetActiveSessionRequest{ UserID: userID, } ctx := context.Background() response, err := h.applicationService.GetActiveSession(ctx, request) if err != nil { return h.handleError(c, err) } return c.JSON(response) } // GetMyActiveSession handles GET /sessions/active (for authenticated user) func (h *GameSessionHandler) GetMyActiveSession(c fiber.Ctx) error { // Get user ID from context (set by auth middleware) userID := c.Locals("userID").(types.UserID) request := &dtos.GetActiveSessionRequest{ UserID: userID, } ctx := context.Background() response, err := h.applicationService.GetActiveSession(ctx, request) if err != nil { return h.handleError(c, err) } return c.JSON(response) } // GetMySessions handles GET /sessions (for authenticated user) func (h *GameSessionHandler) GetMySessions(c fiber.Ctx) error { // Get user ID from context (set by auth middleware) userID := c.Locals("userID").(types.UserID) // Parse query parameters limitStr := c.Query("limit", "20") offsetStr := c.Query("offset", "0") limit, err := strconv.Atoi(limitStr) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "invalid_parameter", "message": "Invalid limit parameter", }) } offset, err := strconv.Atoi(offsetStr) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "invalid_parameter", "message": "Invalid offset parameter", }) } request := &dtos.GetUserSessionsRequest{ UserID: userID, Limit: limit, Offset: offset, } ctx := context.Background() response, err := h.applicationService.GetUserSessions(ctx, request) if err != nil { return h.handleError(c, err) } return c.JSON(response) } // handleError handles application errors and converts them to HTTP responses func (h *GameSessionHandler) handleError(c fiber.Ctx, err error) error { switch { case isValidationError(err): return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "validation_failed", "message": err.Error(), }) case isNotFoundError(err): return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ "error": "not_found", "message": err.Error(), }) case isConflictError(err): return c.Status(fiber.StatusConflict).JSON(fiber.Map{ "error": "conflict", "message": err.Error(), }) case isOperationNotAllowedError(err): return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "operation_not_allowed", "message": err.Error(), }) default: // Log the internal error // logger.Error("Internal server error", "error", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "internal_server_error", "message": "An unexpected error occurred", }) } } // Error type checking helper functions func isValidationError(err error) bool { // Check if error is a validation error // This would depend on your error types implementation return false // Placeholder } func isNotFoundError(err error) bool { // Check if error is a not found error return false // Placeholder } func isConflictError(err error) bool { // Check if error is a conflict error (e.g., duplicate session) return false // Placeholder } func isOperationNotAllowedError(err error) bool { // Check if error is an operation not allowed error return false // Placeholder } // RegisterRoutes registers all the game session routes func (h *GameSessionHandler) RegisterRoutes(app fiber.Router) { // Session management routes app.Post("/sessions", h.StartNewSession) app.Get("/sessions/active", h.GetMyActiveSession) app.Get("/sessions", h.GetMySessions) app.Get("/sessions/:sessionId", h.GetSessionStatus) app.Get("/sessions/:sessionId/resume", h.ResumeSession) app.Post("/sessions/:sessionId/complete", h.CompleteSession) app.Post("/sessions/:sessionId/abandon", h.AbandonSession) // Answer and hint routes app.Post("/sessions/:sessionId/questions/:questionId/answers", h.SubmitAnswer) app.Post("/sessions/:sessionId/questions/:questionId/hint", h.RequestHint) // User-specific session routes (admin/public access) app.Get("/users/:userId/sessions", h.GetUserSessions) app.Get("/users/:userId/sessions/active", h.GetActiveSession) } // Health check handler func (h *GameSessionHandler) HealthCheck(c fiber.Ctx) error { return c.JSON(fiber.Map{ "service": "game-session-service", "status": "healthy", "timestamp": types.NewTimestamp(), }) } // RegisterHealthRoutes registers health check routes func (h *GameSessionHandler) RegisterHealthRoutes(app fiber.Router) { app.Get("/health", h.HealthCheck) }