mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 23:33:42 +00:00
d937cb153c
Add POST /v1/servers/{id}/register (and its /api/v1 contract alias),
authenticated by the same workload binding as the result route. A
game server reports its protocol version and image digest and asks
to advance ALLOCATING -> PROCESS_READY -> ASSIGNMENT_READY; the store
boundary (AdvanceServerRegistration) does this as one idempotent
SERIALIZABLE transaction that also advances every participant's queue
ticket, and gates the final transition on every participant having a
live, unexpired assignment.
Adversarial review of the surrounding routing turned up a pre-existing
bug: contractServerMutation rejected any path containing '/', so the
already-documented /api/v1/servers/{id}/result route (and this new
/register route) 404'd for every real caller despite being declared
in the OpenAPI contract. Fix it to delegate shape validation to
serverMutation, matching how contractQueueMutation handles its own
two-segment paths, and add a regression test covering both contract
routes end to end.
74 lines
2.8 KiB
Go
74 lines
2.8 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"time"
|
|
|
|
"github.com/cosmic-clash/cosmic-clash/server/domain"
|
|
"github.com/cosmic-clash/cosmic-clash/server/store"
|
|
)
|
|
|
|
// AssignmentProviderFromStore adapts the durable player-scoped assignment
|
|
// projection to the HTTP boundary. The store query filters expiry and binds
|
|
// both match and player; the API still performs its response-shape checks.
|
|
func AssignmentProviderFromStore(db *sql.DB) AssignmentProvider {
|
|
return func(ctx context.Context, playerID, matchID string, now time.Time) (AssignmentView, error) {
|
|
assignment, err := store.GetAssignment(ctx, db, playerID, matchID, now)
|
|
if err != nil {
|
|
return AssignmentView{}, err
|
|
}
|
|
return AssignmentView{
|
|
MatchID: assignment.MatchID,
|
|
ServerID: assignment.ServerID,
|
|
PlayerID: assignment.PlayerID,
|
|
Slot: assignment.Slot,
|
|
ExpiresAt: assignment.ExpiresAt,
|
|
ProtocolVersion: assignment.ProtocolVersion,
|
|
Transport: assignment.Transport,
|
|
JoinAuthorisation: assignment.JoinAuthorisation,
|
|
Endpoint: assignment.Endpoint,
|
|
Revision: assignment.Revision,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
type postgresProposalBackend struct{ db *sql.DB }
|
|
|
|
func (p postgresProposalBackend) Get(ctx context.Context, playerID, proposalID string, now time.Time) (domain.Proposal, error) {
|
|
return store.GetProposal(ctx, p.db, playerID, proposalID, now)
|
|
}
|
|
|
|
func (p postgresProposalBackend) Respond(ctx context.Context, playerID, proposalID, idempotencyKey string, accept bool, expectedRevision uint64, now time.Time) (domain.Proposal, error) {
|
|
return store.RespondToProposal(ctx, p.db, playerID, proposalID, idempotencyKey, accept, expectedRevision, now)
|
|
}
|
|
|
|
func ProposalProviderFromStore(db *sql.DB) ProposalBackend {
|
|
return postgresProposalBackend{db: db}
|
|
}
|
|
|
|
// ProposalPromoterFromStore turns a durably accepted proposal into its exact
|
|
// matcher-selected ALLOCATING match. The store chooses a deterministic match
|
|
// ID so an API retry after a transient failure cannot duplicate the match.
|
|
func ProposalPromoterFromStore(db *sql.DB) ProposalPromoter {
|
|
return ProposalPromoterFunc(func(ctx context.Context, proposal domain.Proposal, now time.Time) error {
|
|
if proposal.State != domain.Accepted {
|
|
return domain.ErrIllegalTransition
|
|
}
|
|
return store.PromoteStoredAcceptedProposal(ctx, db, proposal.ProposalID, now)
|
|
})
|
|
}
|
|
|
|
type postgresServerRegistrar struct{ db *sql.DB }
|
|
|
|
func (p postgresServerRegistrar) RegisterServer(ctx context.Context, binding domain.WorkloadBinding, protocol int, assignmentReady bool, idempotencyKey string, now time.Time) error {
|
|
return store.AdvanceServerRegistration(ctx, p.db, binding, protocol, assignmentReady, idempotencyKey, now)
|
|
}
|
|
|
|
func ServerRegistrarFromStore(db *sql.DB) ServerRegistrar {
|
|
if db == nil {
|
|
return nil
|
|
}
|
|
return postgresServerRegistrar{db: db}
|
|
}
|