mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 00:14:00 +00:00
feat: persist player-scoped assignments
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DurableAssignment is the persistence form of a verified assignment-ready
|
||||
// projection. It deliberately keeps player ownership in the primary key and
|
||||
// query predicate so another participant cannot recover its join material.
|
||||
type DurableAssignment struct {
|
||||
MatchID string
|
||||
PlayerID string
|
||||
AllocationID string
|
||||
ServerID string
|
||||
Slot int
|
||||
Region string
|
||||
ClientBuild string
|
||||
ProtocolVersion int
|
||||
Transport string
|
||||
Endpoint string
|
||||
JoinAuthorisation string
|
||||
ManifestDigest []byte
|
||||
ExpiresAt time.Time
|
||||
Revision uint64
|
||||
}
|
||||
|
||||
const AssignmentUpsertSQL = `INSERT INTO assignments
|
||||
(match_id, player_id, allocation_id, server_id, slot, region, client_build,
|
||||
protocol_version, transport, endpoint, join_authorisation, manifest_digest,
|
||||
expires_at, revision)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
||||
ON CONFLICT (match_id, player_id) DO UPDATE SET
|
||||
allocation_id = EXCLUDED.allocation_id, server_id = EXCLUDED.server_id,
|
||||
slot = EXCLUDED.slot, region = EXCLUDED.region, client_build = EXCLUDED.client_build,
|
||||
protocol_version = EXCLUDED.protocol_version, transport = EXCLUDED.transport,
|
||||
endpoint = EXCLUDED.endpoint, join_authorisation = EXCLUDED.join_authorisation,
|
||||
manifest_digest = EXCLUDED.manifest_digest, expires_at = EXCLUDED.expires_at,
|
||||
revision = EXCLUDED.revision
|
||||
WHERE assignments.allocation_id = EXCLUDED.allocation_id
|
||||
AND assignments.server_id = EXCLUDED.server_id
|
||||
AND assignments.slot = EXCLUDED.slot
|
||||
AND assignments.region = EXCLUDED.region
|
||||
AND assignments.client_build = EXCLUDED.client_build
|
||||
AND assignments.protocol_version = EXCLUDED.protocol_version
|
||||
AND assignments.transport = EXCLUDED.transport
|
||||
AND assignments.endpoint = EXCLUDED.endpoint
|
||||
AND assignments.join_authorisation = EXCLUDED.join_authorisation
|
||||
AND assignments.manifest_digest = EXCLUDED.manifest_digest
|
||||
AND assignments.expires_at = EXCLUDED.expires_at
|
||||
AND assignments.revision = EXCLUDED.revision`
|
||||
|
||||
const AssignmentSelectSQL = `SELECT match_id, player_id, allocation_id, server_id,
|
||||
slot, region, client_build, protocol_version, transport, endpoint,
|
||||
join_authorisation, manifest_digest, expires_at, revision
|
||||
FROM assignments
|
||||
WHERE match_id = $1 AND player_id = $2 AND expires_at > $3`
|
||||
|
||||
func validateDurableAssignment(assignment DurableAssignment) error {
|
||||
if assignment.MatchID == "" || assignment.PlayerID == "" || assignment.AllocationID == "" || assignment.ServerID == "" || assignment.Slot < 0 || assignment.Slot > 5 || (assignment.Region != "EU" && assignment.Region != "NA") || assignment.ClientBuild == "" || assignment.ProtocolVersion < 1 || (assignment.Transport != "enet" && assignment.Transport != "steam_sdr") || assignment.Endpoint == "" || assignment.JoinAuthorisation == "" || len(assignment.ManifestDigest) == 0 || assignment.ExpiresAt.IsZero() || assignment.Revision < 0 {
|
||||
return fmt.Errorf("invalid durable assignment")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveAssignment(ctx context.Context, db *sql.DB, assignment DurableAssignment) error {
|
||||
if db == nil {
|
||||
return fmt.Errorf("invalid assignment database")
|
||||
}
|
||||
if err := validateDurableAssignment(assignment); err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := db.ExecContext(ctx, AssignmentUpsertSQL, assignment.MatchID, assignment.PlayerID, assignment.AllocationID, assignment.ServerID, assignment.Slot, assignment.Region, assignment.ClientBuild, assignment.ProtocolVersion, assignment.Transport, assignment.Endpoint, assignment.JoinAuthorisation, assignment.ManifestDigest, assignment.ExpiresAt, assignment.Revision)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changed, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if changed != 1 {
|
||||
return fmt.Errorf("assignment persistence conflict")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetAssignment(ctx context.Context, db *sql.DB, playerID, matchID string, now time.Time) (DurableAssignment, error) {
|
||||
if db == nil || playerID == "" || matchID == "" || now.IsZero() {
|
||||
return DurableAssignment{}, fmt.Errorf("invalid assignment recovery arguments")
|
||||
}
|
||||
var assignment DurableAssignment
|
||||
err := db.QueryRowContext(ctx, AssignmentSelectSQL, matchID, playerID, now).Scan(&assignment.MatchID, &assignment.PlayerID, &assignment.AllocationID, &assignment.ServerID, &assignment.Slot, &assignment.Region, &assignment.ClientBuild, &assignment.ProtocolVersion, &assignment.Transport, &assignment.Endpoint, &assignment.JoinAuthorisation, &assignment.ManifestDigest, &assignment.ExpiresAt, &assignment.Revision)
|
||||
if err != nil {
|
||||
return DurableAssignment{}, err
|
||||
}
|
||||
if err := validateDurableAssignment(assignment); err != nil {
|
||||
return DurableAssignment{}, err
|
||||
}
|
||||
return assignment, nil
|
||||
}
|
||||
Reference in New Issue
Block a user