| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- package postgres
- import (
- "context"
- "encoding/json"
- "fmt"
- "github.com/jackc/pgx/v5"
- )
- type SessionResult struct {
- ID string
- SessionID string
- ResultStatus string
- SummaryJSON string
- FinalDurationSec *int
- FinalScore *int
- CompletedControls *int
- TotalControls *int
- DistanceMeters *float64
- AverageSpeedKmh *float64
- MaxHeartRateBpm *int
- }
- type UpsertSessionResultParams struct {
- SessionID string
- ResultStatus string
- Summary map[string]any
- FinalDurationSec *int
- FinalScore *int
- CompletedControls *int
- TotalControls *int
- DistanceMeters *float64
- AverageSpeedKmh *float64
- MaxHeartRateBpm *int
- }
- type SessionResultRecord struct {
- Session
- Result *SessionResult
- }
- func (s *Store) UpsertSessionResult(ctx context.Context, tx Tx, params UpsertSessionResultParams) (*SessionResult, error) {
- summaryJSON, err := json.Marshal(params.Summary)
- if err != nil {
- return nil, fmt.Errorf("marshal session summary: %w", err)
- }
- row := tx.QueryRow(ctx, `
- INSERT INTO session_results (
- session_id,
- result_status,
- summary_jsonb,
- final_duration_sec,
- final_score,
- completed_controls,
- total_controls,
- distance_meters,
- average_speed_kmh,
- max_heart_rate_bpm
- )
- VALUES ($1, $2, $3::jsonb, $4, $5, $6, $7, $8, $9, $10)
- ON CONFLICT (session_id) DO UPDATE SET
- result_status = EXCLUDED.result_status,
- summary_jsonb = EXCLUDED.summary_jsonb,
- final_duration_sec = EXCLUDED.final_duration_sec,
- final_score = EXCLUDED.final_score,
- completed_controls = EXCLUDED.completed_controls,
- total_controls = EXCLUDED.total_controls,
- distance_meters = EXCLUDED.distance_meters,
- average_speed_kmh = EXCLUDED.average_speed_kmh,
- max_heart_rate_bpm = EXCLUDED.max_heart_rate_bpm
- RETURNING
- id,
- session_id,
- result_status,
- summary_jsonb::text,
- final_duration_sec,
- final_score,
- completed_controls,
- total_controls,
- distance_meters::float8,
- average_speed_kmh::float8,
- max_heart_rate_bpm
- `, params.SessionID, params.ResultStatus, string(summaryJSON), params.FinalDurationSec, params.FinalScore, params.CompletedControls, params.TotalControls, params.DistanceMeters, params.AverageSpeedKmh, params.MaxHeartRateBpm)
- return scanSessionResult(row)
- }
- func (s *Store) GetSessionResultByPublicID(ctx context.Context, sessionPublicID string) (*SessionResultRecord, error) {
- row := s.pool.QueryRow(ctx, `
- SELECT
- gs.id,
- gs.session_public_id,
- gs.user_id,
- gs.event_id,
- gs.event_release_id,
- er.release_public_id,
- er.config_label,
- er.manifest_url,
- er.manifest_checksum_sha256,
- gs.device_key,
- gs.client_type,
- gs.assignment_mode,
- gs.variant_id,
- gs.variant_name,
- gs.route_code,
- gs.status,
- gs.session_token_hash,
- gs.session_token_expires_at,
- gs.launched_at,
- gs.started_at,
- gs.ended_at,
- e.event_public_id,
- e.display_name,
- sr.id,
- sr.session_id,
- sr.result_status,
- sr.summary_jsonb::text,
- sr.final_duration_sec,
- sr.final_score,
- sr.completed_controls,
- sr.total_controls,
- sr.distance_meters::float8,
- sr.average_speed_kmh::float8,
- sr.max_heart_rate_bpm
- FROM game_sessions gs
- JOIN events e ON e.id = gs.event_id
- JOIN event_releases er ON er.id = gs.event_release_id
- LEFT JOIN session_results sr ON sr.session_id = gs.id
- WHERE gs.session_public_id = $1
- LIMIT 1
- `, sessionPublicID)
- return scanSessionResultRecord(row)
- }
- func (s *Store) ListSessionResultsByUserID(ctx context.Context, userID string, limit int) ([]SessionResultRecord, error) {
- if limit <= 0 || limit > 100 {
- limit = 20
- }
- rows, err := s.pool.Query(ctx, `
- SELECT
- gs.id,
- gs.session_public_id,
- gs.user_id,
- gs.event_id,
- gs.event_release_id,
- er.release_public_id,
- er.config_label,
- er.manifest_url,
- er.manifest_checksum_sha256,
- gs.device_key,
- gs.client_type,
- gs.assignment_mode,
- gs.variant_id,
- gs.variant_name,
- gs.route_code,
- gs.status,
- gs.session_token_hash,
- gs.session_token_expires_at,
- gs.launched_at,
- gs.started_at,
- gs.ended_at,
- e.event_public_id,
- e.display_name,
- sr.id,
- sr.session_id,
- sr.result_status,
- sr.summary_jsonb::text,
- sr.final_duration_sec,
- sr.final_score,
- sr.completed_controls,
- sr.total_controls,
- sr.distance_meters::float8,
- sr.average_speed_kmh::float8,
- sr.max_heart_rate_bpm
- FROM game_sessions gs
- JOIN events e ON e.id = gs.event_id
- JOIN event_releases er ON er.id = gs.event_release_id
- LEFT JOIN session_results sr ON sr.session_id = gs.id
- WHERE gs.user_id = $1
- AND gs.status IN ('finished', 'failed', 'cancelled')
- ORDER BY COALESCE(gs.ended_at, gs.updated_at, gs.created_at) DESC
- LIMIT $2
- `, userID, limit)
- if err != nil {
- return nil, fmt.Errorf("list session results by user id: %w", err)
- }
- defer rows.Close()
- var items []SessionResultRecord
- for rows.Next() {
- item, err := scanSessionResultRecordFromRows(rows)
- if err != nil {
- return nil, err
- }
- items = append(items, *item)
- }
- if err := rows.Err(); err != nil {
- return nil, fmt.Errorf("iterate session results by user id: %w", err)
- }
- return items, nil
- }
- func scanSessionResult(row pgx.Row) (*SessionResult, error) {
- var result SessionResult
- err := row.Scan(
- &result.ID,
- &result.SessionID,
- &result.ResultStatus,
- &result.SummaryJSON,
- &result.FinalDurationSec,
- &result.FinalScore,
- &result.CompletedControls,
- &result.TotalControls,
- &result.DistanceMeters,
- &result.AverageSpeedKmh,
- &result.MaxHeartRateBpm,
- )
- if err != nil {
- return nil, fmt.Errorf("scan session result: %w", err)
- }
- return &result, nil
- }
- func scanSessionResultRecord(row pgx.Row) (*SessionResultRecord, error) {
- var record SessionResultRecord
- var resultID *string
- var resultSessionID *string
- var resultStatus *string
- var resultSummaryJSON *string
- var finalDurationSec *int
- var finalScore *int
- var completedControls *int
- var totalControls *int
- var distanceMeters *float64
- var averageSpeedKmh *float64
- var maxHeartRateBpm *int
- err := row.Scan(
- &record.ID,
- &record.SessionPublicID,
- &record.UserID,
- &record.EventID,
- &record.EventReleaseID,
- &record.ReleasePublicID,
- &record.ConfigLabel,
- &record.ManifestURL,
- &record.ManifestChecksum,
- &record.DeviceKey,
- &record.ClientType,
- &record.AssignmentMode,
- &record.VariantID,
- &record.VariantName,
- &record.RouteCode,
- &record.Status,
- &record.SessionTokenHash,
- &record.SessionTokenExpiresAt,
- &record.LaunchedAt,
- &record.StartedAt,
- &record.EndedAt,
- &record.EventPublicID,
- &record.EventDisplayName,
- &resultID,
- &resultSessionID,
- &resultStatus,
- &resultSummaryJSON,
- &finalDurationSec,
- &finalScore,
- &completedControls,
- &totalControls,
- &distanceMeters,
- &averageSpeedKmh,
- &maxHeartRateBpm,
- )
- if err != nil {
- if err == pgx.ErrNoRows {
- return nil, nil
- }
- return nil, fmt.Errorf("scan session result record: %w", err)
- }
- if resultID != nil {
- record.Result = &SessionResult{
- ID: *resultID,
- SessionID: derefString(resultSessionID),
- ResultStatus: derefString(resultStatus),
- SummaryJSON: derefString(resultSummaryJSON),
- FinalDurationSec: finalDurationSec,
- FinalScore: finalScore,
- CompletedControls: completedControls,
- TotalControls: totalControls,
- DistanceMeters: distanceMeters,
- AverageSpeedKmh: averageSpeedKmh,
- MaxHeartRateBpm: maxHeartRateBpm,
- }
- }
- return &record, nil
- }
- func scanSessionResultRecordFromRows(rows pgx.Rows) (*SessionResultRecord, error) {
- var record SessionResultRecord
- var resultID *string
- var resultSessionID *string
- var resultStatus *string
- var resultSummaryJSON *string
- var finalDurationSec *int
- var finalScore *int
- var completedControls *int
- var totalControls *int
- var distanceMeters *float64
- var averageSpeedKmh *float64
- var maxHeartRateBpm *int
- err := rows.Scan(
- &record.ID,
- &record.SessionPublicID,
- &record.UserID,
- &record.EventID,
- &record.EventReleaseID,
- &record.ReleasePublicID,
- &record.ConfigLabel,
- &record.ManifestURL,
- &record.ManifestChecksum,
- &record.DeviceKey,
- &record.ClientType,
- &record.AssignmentMode,
- &record.VariantID,
- &record.VariantName,
- &record.RouteCode,
- &record.Status,
- &record.SessionTokenHash,
- &record.SessionTokenExpiresAt,
- &record.LaunchedAt,
- &record.StartedAt,
- &record.EndedAt,
- &record.EventPublicID,
- &record.EventDisplayName,
- &resultID,
- &resultSessionID,
- &resultStatus,
- &resultSummaryJSON,
- &finalDurationSec,
- &finalScore,
- &completedControls,
- &totalControls,
- &distanceMeters,
- &averageSpeedKmh,
- &maxHeartRateBpm,
- )
- if err != nil {
- return nil, fmt.Errorf("scan session result row: %w", err)
- }
- if resultID != nil {
- record.Result = &SessionResult{
- ID: *resultID,
- SessionID: derefString(resultSessionID),
- ResultStatus: derefString(resultStatus),
- SummaryJSON: derefString(resultSummaryJSON),
- FinalDurationSec: finalDurationSec,
- FinalScore: finalScore,
- CompletedControls: completedControls,
- TotalControls: totalControls,
- DistanceMeters: distanceMeters,
- AverageSpeedKmh: averageSpeedKmh,
- MaxHeartRateBpm: maxHeartRateBpm,
- }
- }
- return &record, nil
- }
- func derefString(value *string) string {
- if value == nil {
- return ""
- }
- return *value
- }
|