This commit is contained in:
2026-10-04 13:23:26 +07:00
parent 88307ed402
commit c663fbb504
20 changed files with 2125 additions and 502 deletions
@@ -0,0 +1,95 @@
// Package game_screen_plans_repo persists game screen plans.
package game_screen_plans_repo
import (
"context"
"encoding/json"
"errors"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
var ErrPlanNotFound = errors.New("план экранов не найден")
// Step is a stored screen plan step.
type Step struct {
Mode int32 `json:"mode"`
Text string `json:"text,omitempty"`
TimerLabel string `json:"timerLabel,omitempty"`
TimerDurationSeconds int64 `json:"timerDurationSeconds,omitempty"`
}
// Plan is a stored game screen plan.
type Plan struct {
GameID int
Steps []Step
CurrentStep int
}
// GameScreenPlansRepo persists game screen plans.
type GameScreenPlansRepo struct {
pool *pgxpool.Pool
}
// NewGameScreenPlansRepo creates a game screen plans repository.
func NewGameScreenPlansRepo(pool *pgxpool.Pool) *GameScreenPlansRepo {
return &GameScreenPlansRepo{pool: pool}
}
func (r *GameScreenPlansRepo) Get(ctx context.Context, gameID int) (*Plan, error) {
plan := &Plan{GameID: gameID}
var raw []byte
err := r.pool.QueryRow(
ctx,
`
SELECT steps, current_step
FROM game_screen_plans
WHERE game_id = $1`,
gameID,
).Scan(&raw, &plan.CurrentStep)
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrPlanNotFound
}
if err != nil {
return nil, err
}
if err := json.Unmarshal(raw, &plan.Steps); err != nil {
return nil, err
}
return plan, nil
}
func (r *GameScreenPlansRepo) Save(ctx context.Context, gameID int, steps []Step, currentStep int) (*Plan, error) {
raw, err := json.Marshal(steps)
if err != nil {
return nil, err
}
_, err = r.pool.Exec(
ctx,
`
INSERT INTO game_screen_plans (game_id, steps, current_step)
VALUES ($1, $2::jsonb, $3)
ON CONFLICT (game_id) DO UPDATE SET
steps = EXCLUDED.steps,
current_step = EXCLUDED.current_step,
updated_at = NOW()`,
gameID,
raw,
currentStep,
)
if err != nil {
return nil, err
}
return &Plan{GameID: gameID, Steps: steps, CurrentStep: currentStep}, nil
}
func (r *GameScreenPlansRepo) Delete(ctx context.Context, gameID int) error {
_, err := r.pool.Exec(
ctx,
`DELETE FROM game_screen_plans
WHERE game_id = $1`,
gameID,
)
return err
}
+36 -5
View File
@@ -29,6 +29,7 @@ type ScreenSession struct {
TimerRemainingSeconds int64
TimerState int32
TimerEndsAt *time.Time
Text string
}
// ScreenSessionsRepo persists screen sessions.
@@ -56,11 +57,11 @@ func (r *ScreenSessionsRepo) Get(ctx context.Context, id int, tokenHash string)
session := &ScreenSession{ID: id}
err := r.pool.QueryRow(ctx, `
SELECT game_id, sound_enabled, last_seen_at, mode, state_version, position_ms, command, volume,
timer_label, timer_duration_seconds, timer_remaining_seconds, timer_state, timer_ends_at
timer_label, timer_duration_seconds, timer_remaining_seconds, timer_state, timer_ends_at, text
FROM screen_sessions
WHERE id = $1 AND access_token_hash = $2`, id, tokenHash).
Scan(&session.GameID, &session.Sound, &session.LastSeen, &session.Mode, &session.Version, &session.Position, &session.Command, &session.Volume,
&session.TimerLabel, &session.TimerDurationSeconds, &session.TimerRemainingSeconds, &session.TimerState, &session.TimerEndsAt)
&session.TimerLabel, &session.TimerDurationSeconds, &session.TimerRemainingSeconds, &session.TimerState, &session.TimerEndsAt, &session.Text)
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrScreenNotFound
}
@@ -79,7 +80,7 @@ func (r *ScreenSessionsRepo) Get(ctx context.Context, id int, tokenHash string)
func (r *ScreenSessionsRepo) ListByGame(ctx context.Context, gameID int) ([]*ScreenSession, error) {
rows, err := r.pool.Query(ctx, `
SELECT id, game_id, sound_enabled, last_seen_at, mode, state_version, position_ms, command, volume,
timer_label, timer_duration_seconds, timer_remaining_seconds, timer_state, timer_ends_at
timer_label, timer_duration_seconds, timer_remaining_seconds, timer_state, timer_ends_at, text
FROM screen_sessions
WHERE game_id = $1
ORDER BY id`, gameID)
@@ -92,7 +93,7 @@ func (r *ScreenSessionsRepo) ListByGame(ctx context.Context, gameID int) ([]*Scr
for rows.Next() {
session := &ScreenSession{}
if err := rows.Scan(&session.ID, &session.GameID, &session.Sound, &session.LastSeen, &session.Mode, &session.Version, &session.Position, &session.Command, &session.Volume,
&session.TimerLabel, &session.TimerDurationSeconds, &session.TimerRemainingSeconds, &session.TimerState, &session.TimerEndsAt); err != nil {
&session.TimerLabel, &session.TimerDurationSeconds, &session.TimerRemainingSeconds, &session.TimerState, &session.TimerEndsAt, &session.Text); err != nil {
return nil, err
}
sessions = append(sessions, session)
@@ -102,7 +103,35 @@ func (r *ScreenSessionsRepo) ListByGame(ctx context.Context, gameID int) ([]*Scr
// SetMode updates the display mode for a game.
func (r *ScreenSessionsRepo) SetMode(ctx context.Context, gameID int, mode int32) error {
_, err := r.pool.Exec(ctx, `UPDATE screen_sessions SET mode = $2, command = 0, state_version = state_version + 1, updated_at = NOW() WHERE game_id = $1`, gameID, mode)
_, err := r.pool.Exec(
ctx,
`UPDATE screen_sessions
SET mode = $2,
text = '',
command = 0,
state_version = state_version + 1,
updated_at = NOW()
WHERE game_id = $1`,
gameID,
mode,
)
return err
}
// SetText updates the text screen for a game.
func (r *ScreenSessionsRepo) SetText(ctx context.Context, gameID int, text string) error {
_, err := r.pool.Exec(
ctx,
`UPDATE screen_sessions
SET mode = 8,
text = $2,
command = 0,
state_version = state_version + 1,
updated_at = NOW()
WHERE game_id = $1`,
gameID,
text,
)
return err
}
@@ -130,6 +159,7 @@ func (r *ScreenSessionsRepo) SetTimer(ctx context.Context, gameID int, duration
timer_ends_at = NULL,
command = 0,
state_version = state_version + 1,
text = '',
updated_at = NOW()
WHERE game_id = $1`, gameID, label, duration)
return err
@@ -194,6 +224,7 @@ func (r *ScreenSessionsRepo) Unbind(ctx context.Context, id int) error {
timer_remaining_seconds = 600,
timer_state = 1,
timer_ends_at = NULL,
text = '',
state_version = state_version + 1,
updated_at = NOW()
WHERE id = $1`, id)