You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

406 lines
11 KiB
Go

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