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