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.

438 lines
16 KiB
Go

package dtos
import (
"fmt"
"strings"
"knowfoolery/backend/shared/types"
"knowfoolery/backend/shared/errors"
"knowfoolery/backend/services/game-session-service/internal/domain/valueobjects"
)
// Request DTOs
// StartSessionRequest represents a request to start a new session
type StartSessionRequest struct {
UserID types.UserID `json:"user_id" validate:"required"`
PlayerName string `json:"player_name,omitempty" validate:"omitempty,min=2,max=50"`
}
// Validate validates the start session request
func (r *StartSessionRequest) Validate() error {
if r.UserID.IsEmpty() {
return errors.ErrValidationFailed("user_id", "user ID is required")
}
if r.PlayerName != "" {
playerName := strings.TrimSpace(r.PlayerName)
if len(playerName) < 2 {
return errors.ErrValidationFailed("player_name", "player name must be at least 2 characters")
}
if len(playerName) > 50 {
return errors.ErrValidationFailed("player_name", "player name must be at most 50 characters")
}
}
return nil
}
// ResumeSessionRequest represents a request to resume a session
type ResumeSessionRequest struct {
SessionID types.GameSessionID `json:"session_id" validate:"required"`
}
// Validate validates the resume session request
func (r *ResumeSessionRequest) Validate() error {
if r.SessionID.IsEmpty() {
return errors.ErrValidationFailed("session_id", "session ID is required")
}
return nil
}
// SubmitAnswerRequest represents a request to submit an answer
type SubmitAnswerRequest struct {
SessionID types.GameSessionID `json:"session_id" validate:"required"`
QuestionID types.QuestionID `json:"question_id" validate:"required"`
PlayerAnswer string `json:"player_answer" validate:"required,max=500"`
HintUsed bool `json:"hint_used"`
}
// Validate validates the submit answer request
func (r *SubmitAnswerRequest) Validate() error {
if r.SessionID.IsEmpty() {
return errors.ErrValidationFailed("session_id", "session ID is required")
}
if r.QuestionID.IsEmpty() {
return errors.ErrValidationFailed("question_id", "question ID is required")
}
playerAnswer := strings.TrimSpace(r.PlayerAnswer)
if playerAnswer == "" {
return errors.ErrValidationFailed("player_answer", "player answer is required")
}
if len(playerAnswer) > types.MaxAnswerLength {
return errors.ErrValidationFailed("player_answer",
fmt.Sprintf("player answer must be at most %d characters", types.MaxAnswerLength))
}
return nil
}
// RequestHintRequest represents a request for a hint
type RequestHintRequest struct {
SessionID types.GameSessionID `json:"session_id" validate:"required"`
QuestionID types.QuestionID `json:"question_id" validate:"required"`
}
// Validate validates the request hint request
func (r *RequestHintRequest) Validate() error {
if r.SessionID.IsEmpty() {
return errors.ErrValidationFailed("session_id", "session ID is required")
}
if r.QuestionID.IsEmpty() {
return errors.ErrValidationFailed("question_id", "question ID is required")
}
return nil
}
// CompleteSessionRequest represents a request to complete a session
type CompleteSessionRequest struct {
SessionID types.GameSessionID `json:"session_id" validate:"required"`
Reason string `json:"reason" validate:"required,max=100"`
}
// Validate validates the complete session request
func (r *CompleteSessionRequest) Validate() error {
if r.SessionID.IsEmpty() {
return errors.ErrValidationFailed("session_id", "session ID is required")
}
reason := strings.TrimSpace(r.Reason)
if reason == "" {
return errors.ErrValidationFailed("reason", "completion reason is required")
}
if len(reason) > 100 {
return errors.ErrValidationFailed("reason", "completion reason must be at most 100 characters")
}
return nil
}
// AbandonSessionRequest represents a request to abandon a session
type AbandonSessionRequest struct {
SessionID types.GameSessionID `json:"session_id" validate:"required"`
}
// Validate validates the abandon session request
func (r *AbandonSessionRequest) Validate() error {
if r.SessionID.IsEmpty() {
return errors.ErrValidationFailed("session_id", "session ID is required")
}
return nil
}
// GetSessionStatusRequest represents a request to get session status
type GetSessionStatusRequest struct {
SessionID types.GameSessionID `json:"session_id" validate:"required"`
}
// Validate validates the get session status request
func (r *GetSessionStatusRequest) Validate() error {
if r.SessionID.IsEmpty() {
return errors.ErrValidationFailed("session_id", "session ID is required")
}
return nil
}
// GetUserSessionsRequest represents a request to get user sessions
type GetUserSessionsRequest struct {
UserID types.UserID `json:"user_id" validate:"required"`
Limit int `json:"limit" validate:"min=1,max=100"`
Offset int `json:"offset" validate:"min=0"`
}
// Validate validates the get user sessions request
func (r *GetUserSessionsRequest) Validate() error {
if r.UserID.IsEmpty() {
return errors.ErrValidationFailed("user_id", "user ID is required")
}
if r.Limit <= 0 {
r.Limit = 20 // Default limit
}
if r.Limit > 100 {
return errors.ErrValidationFailed("limit", "limit must be at most 100")
}
if r.Offset < 0 {
return errors.ErrValidationFailed("offset", "offset must be non-negative")
}
return nil
}
// GetActiveSessionRequest represents a request to get active session
type GetActiveSessionRequest struct {
UserID types.UserID `json:"user_id" validate:"required"`
}
// Validate validates the get active session request
func (r *GetActiveSessionRequest) Validate() error {
if r.UserID.IsEmpty() {
return errors.ErrValidationFailed("user_id", "user ID is required")
}
return nil
}
// Response DTOs
// StartSessionResponse represents the response to starting a session
type StartSessionResponse struct {
SessionID types.GameSessionID `json:"session_id"`
Status string `json:"status"`
CreatedAt types.Timestamp `json:"created_at"`
PlayerName string `json:"player_name,omitempty"`
MaxDuration types.Duration `json:"max_duration"`
RemainingTime types.Duration `json:"remaining_time"`
}
// ResumeSessionResponse represents the response to resuming a session
type ResumeSessionResponse struct {
SessionID types.GameSessionID `json:"session_id"`
CanResume bool `json:"can_resume"`
TimeoutOccurred bool `json:"timeout_occurred"`
Status string `json:"status"`
RemainingTime *types.Duration `json:"remaining_time,omitempty"`
CurrentScore *ScoreDTO `json:"current_score,omitempty"`
QuestionsProgress map[string]QuestionProgressDTO `json:"questions_progress,omitempty"`
CompletionResult *SessionCompletionResultDTO `json:"completion_result,omitempty"`
}
// SubmitAnswerResponse represents the response to submitting an answer
type SubmitAnswerResponse struct {
Accepted bool `json:"accepted"`
ProcessingResult *AnswerProcessingResult `json:"processing_result,omitempty"`
AntiCheatViolations []string `json:"anti_cheat_violations,omitempty"`
UpdatedScore *ScoreDTO `json:"updated_score,omitempty"`
RemainingTime types.Duration `json:"remaining_time"`
TimeoutOccurred bool `json:"timeout_occurred"`
CompletionResult *SessionCompletionResultDTO `json:"completion_result,omitempty"`
RejectionReason *string `json:"rejection_reason,omitempty"`
}
// RequestHintResponse represents the response to requesting a hint
type RequestHintResponse struct {
HintText string `json:"hint_text"`
QuestionID types.QuestionID `json:"question_id"`
RequestedAt types.Timestamp `json:"requested_at"`
}
// CompleteSessionResponse represents the response to completing a session
type CompleteSessionResponse struct {
SessionID types.GameSessionID `json:"session_id"`
FinalScore int `json:"final_score"`
CompletedAt types.Timestamp `json:"completed_at"`
Reason string `json:"reason"`
Duration types.Duration `json:"duration"`
CompletionResult *SessionCompletionResultDTO `json:"completion_result"`
}
// AbandonSessionResponse represents the response to abandoning a session
type AbandonSessionResponse struct {
SessionID types.GameSessionID `json:"session_id"`
AbandonedAt types.Timestamp `json:"abandoned_at"`
FinalScore int `json:"final_score"`
}
// GetSessionStatusResponse represents the response to getting session status
type GetSessionStatusResponse struct {
SessionID types.GameSessionID `json:"session_id"`
Status string `json:"status"`
CurrentScore *ScoreDTO `json:"current_score"`
TimerStatus *TimerStatusDTO `json:"timer_status"`
QuestionsProgress map[string]QuestionProgressDTO `json:"questions_progress"`
TimeoutOccurred bool `json:"timeout_occurred"`
CompletionResult *SessionCompletionResultDTO `json:"completion_result,omitempty"`
}
// GetUserSessionsResponse represents the response to getting user sessions
type GetUserSessionsResponse struct {
Sessions []SessionSummary `json:"sessions"`
TotalCount int `json:"total_count"`
Offset int `json:"offset"`
Limit int `json:"limit"`
}
// GetActiveSessionResponse represents the response to getting active session
type GetActiveSessionResponse struct {
HasActiveSession bool `json:"has_active_session"`
SessionID types.GameSessionID `json:"session_id,omitempty"`
Status string `json:"status,omitempty"`
CreatedAt types.Timestamp `json:"created_at,omitempty"`
PlayerName *string `json:"player_name,omitempty"`
CurrentScore *ScoreDTO `json:"current_score,omitempty"`
RemainingTime types.Duration `json:"remaining_time,omitempty"`
QuestionsProgress map[string]QuestionProgressDTO `json:"questions_progress,omitempty"`
}
// Nested DTOs
// ScoreDTO represents score information
type ScoreDTO struct {
Total int `json:"total"`
QuestionsAttempted int `json:"questions_attempted"`
QuestionsCorrect int `json:"questions_correct"`
SuccessRate float64 `json:"success_rate"`
PointsFromHints int `json:"points_from_hints"`
PointsWithoutHints int `json:"points_without_hints"`
HintsUsed int `json:"hints_used"`
AveragePointsPerQuestion float64 `json:"average_points_per_question"`
}
// TimerStatusDTO represents timer status information
type TimerStatusDTO struct {
IsActive bool `json:"is_active"`
IsExpired bool `json:"is_expired"`
IsInWarningPeriod bool `json:"is_in_warning_period"`
IsInCriticalPeriod bool `json:"is_in_critical_period"`
StartTime types.Timestamp `json:"start_time"`
EndTime *types.Timestamp `json:"end_time,omitempty"`
ElapsedTime types.Duration `json:"elapsed_time"`
RemainingTime types.Duration `json:"remaining_time"`
ElapsedFormatted string `json:"elapsed_formatted"`
RemainingFormatted string `json:"remaining_formatted"`
ElapsedPercentage float64 `json:"elapsed_percentage"`
RemainingPercentage float64 `json:"remaining_percentage"`
WarningsSent []valueobjects.TimerWarning `json:"warnings_sent"`
}
// QuestionProgressDTO represents progress on a single question
type QuestionProgressDTO struct {
QuestionID types.QuestionID `json:"question_id"`
PresentedAt types.Timestamp `json:"presented_at"`
HintUsed bool `json:"hint_used"`
IsCompleted bool `json:"is_completed"`
Attempts []AttemptDTO `json:"attempts"`
}
// AttemptDTO represents a single attempt
type AttemptDTO struct {
ID types.AttemptID `json:"id"`
AttemptNumber int `json:"attempt_number"`
PlayerAnswer string `json:"player_answer"`
IsCorrect bool `json:"is_correct"`
IsTimeout bool `json:"is_timeout"`
PointsAwarded int `json:"points_awarded"`
HintUsed bool `json:"hint_used"`
MatchType string `json:"match_type"`
Similarity float64 `json:"similarity"`
AttemptedAt types.Timestamp `json:"attempted_at"`
}
// AnswerProcessingResult represents the result of processing an answer
type AnswerProcessingResult struct {
IsCorrect bool `json:"is_correct"`
Score int `json:"score"`
AttemptNumber int `json:"attempt_number"`
AttemptsRemaining int `json:"attempts_remaining"`
Similarity float64 `json:"similarity"`
MatchType string `json:"match_type"`
IsQuestionComplete bool `json:"is_question_complete"`
}
// SessionCompletionResultDTO represents the result of session completion
type SessionCompletionResultDTO struct {
SessionID types.GameSessionID `json:"session_id"`
FinalScore int `json:"final_score"`
CompletedAt types.Timestamp `json:"completed_at"`
Reason string `json:"reason"`
Duration types.Duration `json:"duration"`
}
// SessionSummary represents a summary of a session
type SessionSummary struct {
SessionID types.GameSessionID `json:"session_id"`
Status string `json:"status"`
CreatedAt types.Timestamp `json:"created_at"`
UpdatedAt types.Timestamp `json:"updated_at"`
Duration types.Duration `json:"duration"`
PlayerName *string `json:"player_name,omitempty"`
Score *ScoreDTO `json:"score"`
QuestionsCount int `json:"questions_count"`
}
// Helper methods for DTOs
// GetScoreSummary returns a human-readable score summary
func (s *ScoreDTO) GetScoreSummary() string {
return fmt.Sprintf("%d points (%d/%d correct, %.1f%% success rate)",
s.Total, s.QuestionsCorrect, s.QuestionsAttempted, s.SuccessRate)
}
// GetTimerSummary returns a human-readable timer summary
func (t *TimerStatusDTO) GetTimerSummary() string {
if !t.IsActive {
return fmt.Sprintf("Inactive (Duration: %s)", t.ElapsedFormatted)
}
if t.IsExpired {
return "Session expired"
}
status := "Active"
if t.IsInCriticalPeriod {
status = "Critical"
} else if t.IsInWarningPeriod {
status = "Warning"
}
return fmt.Sprintf("%s (%s remaining)", status, t.RemainingFormatted)
}
// GetProgressSummary returns a summary of question progress
func (q *QuestionProgressDTO) GetProgressSummary() string {
if q.IsCompleted {
return fmt.Sprintf("Completed (%d attempts)", len(q.Attempts))
}
return fmt.Sprintf("In progress (%d attempts)", len(q.Attempts))
}
// GetAttemptSummary returns a summary of an attempt
func (a *AttemptDTO) GetAttemptSummary() string {
status := "incorrect"
if a.IsTimeout {
status = "timeout"
} else if a.IsCorrect {
status = "correct"
}
return fmt.Sprintf("Attempt %d: %s (%d points)",
a.AttemptNumber, status, a.PointsAwarded)
}
// GetSessionSummary returns a summary of the session
func (s *SessionSummary) GetSessionSummary() string {
playerInfo := ""
if s.PlayerName != nil {
playerInfo = fmt.Sprintf(" - %s", *s.PlayerName)
}
return fmt.Sprintf("Session %s: %s%s (%s, %d questions)",
s.SessionID, s.Status, playerInfo, s.Duration.String(), s.QuestionsCount)
}
// IsSuccessful returns true if the answer processing was successful
func (apr *AnswerProcessingResult) IsSuccessful() bool {
return apr.IsCorrect && apr.Score > 0
}
// GetCompletionSummary returns a summary of the completion result
func (scr *SessionCompletionResultDTO) GetCompletionSummary() string {
return fmt.Sprintf("Session completed: %d points in %s (reason: %s)",
scr.FinalScore, scr.Duration.String(), scr.Reason)
}