feat: promote accepted proposals from API

This commit is contained in:
Josh Creek
2026-09-01 10:29:41 +01:00
parent e0ba6c6ead
commit 03ff8e485e
16 changed files with 270 additions and 49 deletions
+36 -4
View File
@@ -10,8 +10,8 @@ import (
)
const ProposalInsertSQL = `INSERT INTO proposals
(proposal_id, playlist, state, expires_at, revision)
VALUES ($1, $2, 'OPEN', $3, 0)`
(proposal_id, playlist, state, expires_at, revision, match_region, match_protocol)
VALUES ($1, $2, 'OPEN', $3, 0, NULLIF($4, ''), NULLIF($5, 0))`
// CreateProposal atomically claims the queue tickets and creates the proposal.
// Every statement runs inside the same SERIALIZABLE retry callback; callers
@@ -20,8 +20,11 @@ func CreateProposal(ctx context.Context, db *sql.DB, proposal domain.Proposal, t
if proposal.ProposalID == "" || len(proposal.Participants) == 0 {
return fmt.Errorf("invalid proposal transaction")
}
if !validProposalMatchPlan(proposal) {
return fmt.Errorf("invalid proposal match plan")
}
return RunSerializable(ctx, db, DefaultSerializableAttempts, func(ctx context.Context, tx *sql.Tx) error {
if _, err := tx.ExecContext(ctx, ProposalInsertSQL, proposal.ProposalID, proposal.Playlist, proposal.ExpiresAt); err != nil {
if _, err := tx.ExecContext(ctx, ProposalInsertSQL, proposal.ProposalID, proposal.Playlist, proposal.ExpiresAt, proposal.Region, proposal.Protocol); err != nil {
return err
}
for _, participant := range proposal.Participants {
@@ -29,7 +32,7 @@ func CreateProposal(ctx context.Context, db *sql.DB, proposal domain.Proposal, t
if participant.PlayerID == "" || ticketID == "" {
return fmt.Errorf("missing proposal ticket mapping")
}
if _, err := tx.ExecContext(ctx, ProposalParticipantInsertSQL, proposal.ProposalID, participant.PlayerID, ticketID); err != nil {
if _, err := tx.ExecContext(ctx, ProposalParticipantInsertSQL, proposal.ProposalID, participant.PlayerID, ticketID, nullablePlanField(proposal.Region != "", participant.Team), nullablePlanField(proposal.Region != "", participant.Slot)); err != nil {
return err
}
result, err := tx.ExecContext(ctx, QueueTicketProposeSQL, ticketID, participant.PlayerID, string(proposal.Playlist), now)
@@ -47,3 +50,32 @@ func CreateProposal(ctx context.Context, db *sql.DB, proposal domain.Proposal, t
return nil
})
}
func validProposalMatchPlan(proposal domain.Proposal) bool {
if proposal.Region == "" && proposal.Protocol == 0 {
return true // Legacy/direct callers have no matcher formation to persist.
}
if (proposal.Region != "EU" && proposal.Region != "NA") || proposal.Protocol < 1 {
return false
}
seenSlots := make(map[int]struct{}, len(proposal.Participants))
teams := [2]int{}
for _, participant := range proposal.Participants {
if participant.Team < 0 || participant.Team > 1 || participant.Slot < 0 || participant.Slot > 5 {
return false
}
if _, exists := seenSlots[participant.Slot]; exists {
return false
}
seenSlots[participant.Slot] = struct{}{}
teams[participant.Team]++
}
return teams[0] > 0 && teams[1] > 0
}
func nullablePlanField(enabled bool, value int) any {
if !enabled {
return nil
}
return value
}