package service import ( "context" "encoding/json" "net/http" "cmr-backend/internal/apperr" "cmr-backend/internal/store/postgres" ) type ResultService struct { store *postgres.Store } type SessionResultView struct { Session EntrySessionSummary `json:"session"` Result ResultSummaryPayload `json:"result"` } type ResultSummaryPayload struct { Status string `json:"status"` FinalDurationSec *int `json:"finalDurationSec,omitempty"` FinalScore *int `json:"finalScore,omitempty"` CompletedControls *int `json:"completedControls,omitempty"` TotalControls *int `json:"totalControls,omitempty"` DistanceMeters *float64 `json:"distanceMeters,omitempty"` AverageSpeedKmh *float64 `json:"averageSpeedKmh,omitempty"` MaxHeartRateBpm *int `json:"maxHeartRateBpm,omitempty"` Summary map[string]any `json:"summary,omitempty"` } func NewResultService(store *postgres.Store) *ResultService { return &ResultService{store: store} } func (s *ResultService) GetSessionResult(ctx context.Context, sessionPublicID, userID string) (*SessionResultView, error) { record, err := s.store.GetSessionResultByPublicID(ctx, sessionPublicID) if err != nil { return nil, err } if record == nil { return nil, apperr.New(http.StatusNotFound, "session_not_found", "session not found") } if userID != "" && record.UserID != userID { return nil, apperr.New(http.StatusForbidden, "session_forbidden", "session does not belong to current user") } return buildSessionResultView(record), nil } func (s *ResultService) ListMyResults(ctx context.Context, userID string, limit int) ([]SessionResultView, error) { if userID == "" { return nil, apperr.New(http.StatusUnauthorized, "unauthorized", "user is required") } records, err := s.store.ListSessionResultsByUserID(ctx, userID, limit) if err != nil { return nil, err } results := make([]SessionResultView, 0, len(records)) for i := range records { results = append(results, *buildSessionResultView(&records[i])) } return results, nil } func buildSessionResultView(record *postgres.SessionResultRecord) *SessionResultView { view := &SessionResultView{ Session: buildEntrySessionSummary(&record.Session), Result: ResultSummaryPayload{ Status: record.Status, }, } if record.Result != nil { view.Result.Status = record.Result.ResultStatus view.Result.FinalDurationSec = record.Result.FinalDurationSec view.Result.FinalScore = record.Result.FinalScore view.Result.CompletedControls = record.Result.CompletedControls view.Result.TotalControls = record.Result.TotalControls view.Result.DistanceMeters = record.Result.DistanceMeters view.Result.AverageSpeedKmh = record.Result.AverageSpeedKmh view.Result.MaxHeartRateBpm = record.Result.MaxHeartRateBpm if record.Result.SummaryJSON != "" { summary := map[string]any{} if err := json.Unmarshal([]byte(record.Result.SummaryJSON), &summary); err == nil && len(summary) > 0 { view.Result.Summary = summary } } } return view }