feat: gamemaster
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
package ports
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:generate go run go.uber.org/mock/mockgen -destination=../adapters/mocks/mock_lobbyclient.go -package=mocks galaxy/gamemaster/internal/ports LobbyClient
|
||||
|
||||
// LobbyClient executes synchronous calls to Game Lobby. The port
|
||||
// surfaces two operations:
|
||||
//
|
||||
// - GetMemberships — used by the membership cache to authorise player
|
||||
// commands on the hot path.
|
||||
// - GetGameSummary — used by the turn-generation orchestrator to
|
||||
// resolve the human-readable `game_name` consumed by
|
||||
// `notification:intents` payloads (`game.turn.ready`,
|
||||
// `game.finished`, `game.generation_failed`). Failure is fail-soft:
|
||||
// callers fall back to `game_id` rather than block the runtime
|
||||
// mutation.
|
||||
//
|
||||
// Membership data and the game record are owned by Game Lobby; GM
|
||||
// treats them as remote projections. Consequently the Membership and
|
||||
// GameSummary types live on the port file rather than as domain types,
|
||||
// mirroring rtmanager's `LobbyGameRecord` precedent.
|
||||
type LobbyClient interface {
|
||||
// GetMemberships returns every membership of gameID, in any
|
||||
// status. The cache layer filters to `active` for authorisation.
|
||||
// Implementations wrap any non-success outcome (transport error,
|
||||
// timeout, non-2xx response) with ErrLobbyUnavailable so callers
|
||||
// can branch with errors.Is.
|
||||
GetMemberships(ctx context.Context, gameID string) ([]Membership, error)
|
||||
|
||||
// GetGameSummary returns the narrow projection of Lobby's
|
||||
// `GameRecord` GM needs to populate notification payloads with a
|
||||
// human-readable `game_name`. Implementations wrap any non-success
|
||||
// outcome (transport error, timeout, non-2xx response, malformed
|
||||
// payload) with ErrLobbyUnavailable.
|
||||
GetGameSummary(ctx context.Context, gameID string) (GameSummary, error)
|
||||
}
|
||||
|
||||
// Membership stores one row of the membership projection returned by
|
||||
// `Lobby /api/v1/internal/games/{game_id}/memberships`. The shape
|
||||
// mirrors `MembershipRecord` in
|
||||
// `galaxy/lobby/api/internal-openapi.yaml`.
|
||||
type Membership struct {
|
||||
// UserID identifies the platform user.
|
||||
UserID string
|
||||
|
||||
// RaceName stores the in-game race reserved for the user.
|
||||
RaceName string
|
||||
|
||||
// Status reports `active`, `removed`, or `blocked`. GM authorises
|
||||
// only `active` callers on the hot path.
|
||||
Status string
|
||||
|
||||
// JoinedAt stores the wall-clock at which the membership entered
|
||||
// active.
|
||||
JoinedAt time.Time
|
||||
|
||||
// RemovedAt stores the wall-clock at which the membership left
|
||||
// active. Nil while the membership is still active.
|
||||
RemovedAt *time.Time
|
||||
}
|
||||
|
||||
// GameSummary stores the narrow projection of Lobby's `GameRecord` GM
|
||||
// consumes today: the platform game id, the human-readable
|
||||
// `game_name`, and the platform-level lifecycle status. Additional
|
||||
// fields can be added without breaking consumers because every caller
|
||||
// reads through the typed fields directly.
|
||||
type GameSummary struct {
|
||||
// GameID identifies the platform game. Echoed back from Lobby as a
|
||||
// sanity check.
|
||||
GameID string
|
||||
|
||||
// GameName stores the human-readable game name maintained by
|
||||
// Lobby. Used by the turn-generation orchestrator to populate
|
||||
// `game_name` on `notification:intents` payloads.
|
||||
GameName string
|
||||
|
||||
// Status stores Lobby's platform-level lifecycle status (`draft`,
|
||||
// `enrollment_open`, `running`, `finished`, etc.). GM does not act
|
||||
// on the value today; it is captured for future audit/log use.
|
||||
Status string
|
||||
}
|
||||
|
||||
// ErrLobbyUnavailable signals that a Lobby call could not be completed
|
||||
// because the upstream service was unreachable, returned an error
|
||||
// response, or timed out. GM's hot-path callers treat any non-success
|
||||
// outcome uniformly: the player command is rejected with
|
||||
// `service_unavailable` and the cache TTL eventually retries.
|
||||
var ErrLobbyUnavailable = errors.New("lobby unavailable")
|
||||
Reference in New Issue
Block a user