package postgres import ( "context" "errors" "fmt" "time" "github.com/jackc/pgx/v5" ) type Event struct { ID string PublicID string Slug string DisplayName string Summary *string Status string CurrentReleaseID *string CurrentReleasePubID *string ConfigLabel *string ManifestURL *string ManifestChecksum *string RouteCode *string } type EventRelease struct { ID string PublicID string EventID string ReleaseNo int ConfigLabel string ManifestURL string ManifestChecksum *string RouteCode *string BuildID *string Status string PublishedAt time.Time } type CreateGameSessionParams struct { SessionPublicID string UserID string EventID string EventReleaseID string DeviceKey string ClientType string RouteCode *string SessionTokenHash string SessionTokenExpiresAt time.Time } type GameSession struct { ID string SessionPublicID string UserID string EventID string EventReleaseID string DeviceKey string ClientType string RouteCode *string Status string SessionTokenExpiresAt time.Time } func (s *Store) GetEventByPublicID(ctx context.Context, eventPublicID string) (*Event, error) { row := s.pool.QueryRow(ctx, ` SELECT e.id, e.event_public_id, e.slug, e.display_name, e.summary, e.status, e.current_release_id, er.release_public_id, er.config_label, er.manifest_url, er.manifest_checksum_sha256, er.route_code FROM events e LEFT JOIN event_releases er ON er.id = e.current_release_id WHERE e.event_public_id = $1 LIMIT 1 `, eventPublicID) var event Event err := row.Scan( &event.ID, &event.PublicID, &event.Slug, &event.DisplayName, &event.Summary, &event.Status, &event.CurrentReleaseID, &event.CurrentReleasePubID, &event.ConfigLabel, &event.ManifestURL, &event.ManifestChecksum, &event.RouteCode, ) if errors.Is(err, pgx.ErrNoRows) { return nil, nil } if err != nil { return nil, fmt.Errorf("get event by public id: %w", err) } return &event, nil } func (s *Store) GetEventByID(ctx context.Context, eventID string) (*Event, error) { row := s.pool.QueryRow(ctx, ` SELECT e.id, e.event_public_id, e.slug, e.display_name, e.summary, e.status, e.current_release_id, er.release_public_id, er.config_label, er.manifest_url, er.manifest_checksum_sha256, er.route_code FROM events e LEFT JOIN event_releases er ON er.id = e.current_release_id WHERE e.id = $1 LIMIT 1 `, eventID) var event Event err := row.Scan( &event.ID, &event.PublicID, &event.Slug, &event.DisplayName, &event.Summary, &event.Status, &event.CurrentReleaseID, &event.CurrentReleasePubID, &event.ConfigLabel, &event.ManifestURL, &event.ManifestChecksum, &event.RouteCode, ) if errors.Is(err, pgx.ErrNoRows) { return nil, nil } if err != nil { return nil, fmt.Errorf("get event by id: %w", err) } return &event, nil } func (s *Store) NextEventReleaseNo(ctx context.Context, eventID string) (int, error) { var next int if err := s.pool.QueryRow(ctx, ` SELECT COALESCE(MAX(release_no), 0) + 1 FROM event_releases WHERE event_id = $1 `, eventID).Scan(&next); err != nil { return 0, fmt.Errorf("next event release no: %w", err) } return next, nil } type CreateEventReleaseParams struct { PublicID string EventID string ReleaseNo int ConfigLabel string ManifestURL string ManifestChecksum *string RouteCode *string BuildID *string Status string PayloadJSON string } func (s *Store) CreateEventRelease(ctx context.Context, tx Tx, params CreateEventReleaseParams) (*EventRelease, error) { row := tx.QueryRow(ctx, ` INSERT INTO event_releases ( release_public_id, event_id, release_no, config_label, manifest_url, manifest_checksum_sha256, route_code, build_id, status, payload_jsonb ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb) RETURNING id, release_public_id, event_id, release_no, config_label, manifest_url, manifest_checksum_sha256, route_code, build_id, status, published_at `, params.PublicID, params.EventID, params.ReleaseNo, params.ConfigLabel, params.ManifestURL, params.ManifestChecksum, params.RouteCode, params.BuildID, params.Status, params.PayloadJSON) var item EventRelease if err := row.Scan( &item.ID, &item.PublicID, &item.EventID, &item.ReleaseNo, &item.ConfigLabel, &item.ManifestURL, &item.ManifestChecksum, &item.RouteCode, &item.BuildID, &item.Status, &item.PublishedAt, ); err != nil { return nil, fmt.Errorf("create event release: %w", err) } return &item, nil } func (s *Store) SetCurrentEventRelease(ctx context.Context, tx Tx, eventID, releaseID string) error { if _, err := tx.Exec(ctx, ` UPDATE events SET current_release_id = $2 WHERE id = $1 `, eventID, releaseID); err != nil { return fmt.Errorf("set current event release: %w", err) } return nil } func (s *Store) CreateGameSession(ctx context.Context, tx Tx, params CreateGameSessionParams) (*GameSession, error) { row := tx.QueryRow(ctx, ` INSERT INTO game_sessions ( session_public_id, user_id, event_id, event_release_id, device_key, client_type, route_code, session_token_hash, session_token_expires_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, session_public_id, user_id, event_id, event_release_id, device_key, client_type, route_code, status, session_token_expires_at `, params.SessionPublicID, params.UserID, params.EventID, params.EventReleaseID, params.DeviceKey, params.ClientType, params.RouteCode, params.SessionTokenHash, params.SessionTokenExpiresAt) var session GameSession err := row.Scan( &session.ID, &session.SessionPublicID, &session.UserID, &session.EventID, &session.EventReleaseID, &session.DeviceKey, &session.ClientType, &session.RouteCode, &session.Status, &session.SessionTokenExpiresAt, ) if err != nil { return nil, fmt.Errorf("create game session: %w", err) } return &session, nil } func (s *Store) ListEventReleasesByEventID(ctx context.Context, eventID string, limit int) ([]EventRelease, error) { if limit <= 0 || limit > 100 { limit = 20 } rows, err := s.pool.Query(ctx, ` SELECT id, release_public_id, event_id, release_no, config_label, manifest_url, manifest_checksum_sha256, route_code, build_id, status, published_at FROM event_releases WHERE event_id = $1 ORDER BY release_no DESC LIMIT $2 `, eventID, limit) if err != nil { return nil, fmt.Errorf("list event releases by event id: %w", err) } defer rows.Close() items := []EventRelease{} for rows.Next() { item, err := scanEventReleaseFromRows(rows) if err != nil { return nil, err } items = append(items, *item) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("iterate event releases by event id: %w", err) } return items, nil } func (s *Store) GetEventReleaseByPublicID(ctx context.Context, releasePublicID string) (*EventRelease, error) { row := s.pool.QueryRow(ctx, ` SELECT id, release_public_id, event_id, release_no, config_label, manifest_url, manifest_checksum_sha256, route_code, build_id, status, published_at FROM event_releases WHERE release_public_id = $1 LIMIT 1 `, releasePublicID) var item EventRelease err := row.Scan( &item.ID, &item.PublicID, &item.EventID, &item.ReleaseNo, &item.ConfigLabel, &item.ManifestURL, &item.ManifestChecksum, &item.RouteCode, &item.BuildID, &item.Status, &item.PublishedAt, ) if errors.Is(err, pgx.ErrNoRows) { return nil, nil } if err != nil { return nil, fmt.Errorf("get event release by public id: %w", err) } return &item, nil } func scanEventReleaseFromRows(rows pgx.Rows) (*EventRelease, error) { var item EventRelease err := rows.Scan( &item.ID, &item.PublicID, &item.EventID, &item.ReleaseNo, &item.ConfigLabel, &item.ManifestURL, &item.ManifestChecksum, &item.RouteCode, &item.BuildID, &item.Status, &item.PublishedAt, ) if err != nil { return nil, fmt.Errorf("scan event release row: %w", err) } return &item, nil }