This commit is contained in:
2026-10-11 12:08:25 +07:00
parent b733efe8e2
commit 26c65062e9
10 changed files with 1703 additions and 0 deletions
+152
View File
@@ -0,0 +1,152 @@
// Package game_results_repo reads consistent, private results snapshots.
package game_results_repo
import (
"context"
"errors"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
var ErrNotImported = errors.New("игра не найдена или не импортирована")
// Snapshot contains only explicitly selected data; account credentials and mail jobs are excluded.
type Snapshot struct {
ScenarioID int
ImportedScenarioID int
ImportID string
PackageID string
SourceServer string
SHA256 string
OriginalData []byte
Manifest []byte
Story []byte
Data []byte
}
type GameResultsRepo struct{ pool *pgxpool.Pool }
func NewGameResultsRepo(pool *pgxpool.Pool) *GameResultsRepo { return &GameResultsRepo{pool: pool} }
// Capture uses one read-only snapshot, including metadata and all gameplay relations.
func (r *GameResultsRepo) Capture(ctx context.Context, gameID int) (*Snapshot, error) {
tx, err := r.pool.BeginTx(ctx, pgx.TxOptions{IsoLevel: pgx.RepeatableRead, AccessMode: pgx.ReadOnly})
if err != nil {
return nil, err
}
defer func() { _ = tx.Rollback(ctx) }()
snapshot := &Snapshot{}
err = tx.QueryRow(
ctx,
`SELECT
i.id,
i.package_id,
i.source_server,
i.sha256,
i.original_data,
i.manifest,
s.scenario,
g.scenario_id,
(i.mappings->>'scenario_id')::integer,
jsonb_build_object(
'game', jsonb_build_object(
'id', g.id, 'status', g.status, 'format', g.format,
'started_at', g.started_at AT TIME ZONE 'UTC',
'ended_at', g.ended_at AT TIME ZONE 'UTC'
),
'teams', COALESCE((
SELECT
jsonb_agg(jsonb_build_object(
'id', t.id,
'game_id', t.game_id,
'name', t.name,
'status', t.status,
'password', t.password,
'creator_id', t.creator_id,
'captain_email', t.captain_email,
'participants_count', t.participants_count,
'participation_status', t.participation_status,
'participation_source', t.participation_source,
'participation_answered_at', t.participation_answered_at,
'registration_delivery_id', t.registration_delivery_id,
'created_at', t.created_at AT TIME ZONE 'UTC'
) ORDER BY t.id)
FROM teams t
WHERE t.game_id = g.id
), '[]'::jsonb),
'tickets', COALESCE((
SELECT
jsonb_agg(jsonb_build_object(
'id', t.id,
'game_id', t.game_id,
'token_hash', encode(t.token_hash, 'base64'),
'status', t.status,
'used_at', t.used_at,
'used_by', t.used_by,
'created_at', t.created_at
) ORDER BY t.id)
FROM tickets t
WHERE t.game_id = g.id
), '[]'::jsonb),
'actions', COALESCE((
SELECT
jsonb_agg(jsonb_build_object(
'id', a.id,
'team_id', a.team_id,
'code', COALESCE(a.code, ''),
'via_mcp', a.via_mcp,
'created_at', a.created_at AT TIME ZONE 'UTC'
) ORDER BY a.team_id, a.created_at, a.id)
FROM actions a
JOIN teams t ON t.id = a.team_id
WHERE t.game_id = g.id
), '[]'::jsonb),
'applications', COALESCE((
SELECT
jsonb_agg(jsonb_build_object(
'id', a.id,
'team_id', a.team_id,
'name', COALESCE(a.name, ''),
'image', COALESCE(a.image, ''),
'file_type', COALESCE(a.file_type, ''),
'status', a.status,
'created_at', a.created_at AT TIME ZONE 'UTC'
) ORDER BY a.team_id, a.created_at, a.id)
FROM applications a
JOIN teams t ON t.id = a.team_id
WHERE t.game_id = g.id
), '[]'::jsonb),
'answers', COALESCE((
SELECT
jsonb_agg(jsonb_build_object(
'team_id', a.team_id,
'question_code', a.question_code,
'answer', a.answer,
'score', a.score,
'created_at', a.created_at AT TIME ZONE 'UTC',
'updated_at', a.updated_at AT TIME ZONE 'UTC'
) ORDER BY a.team_id, a.question_code)
FROM team_answers a
JOIN teams t ON t.id = a.team_id
WHERE t.game_id = g.id
), '[]'::jsonb)
)
FROM games g
JOIN scenarios s ON s.id = g.scenario_id
JOIN game_imports i ON i.source_game_id = g.id AND i.state = 'ready'
WHERE g.id = $1`,
gameID,
).Scan(&snapshot.ImportID, &snapshot.PackageID, &snapshot.SourceServer,
&snapshot.SHA256, &snapshot.OriginalData, &snapshot.Manifest, &snapshot.Story, &snapshot.ScenarioID, &snapshot.ImportedScenarioID, &snapshot.Data)
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrNotImported
}
if err != nil {
return nil, err
}
if err := tx.Commit(ctx); err != nil {
return nil, err
}
return snapshot, nil
}