Files
galaxy-game/gamemaster/internal/ports/lobbyclient.go
T
2026-05-03 07:59:03 +02:00

94 lines
3.7 KiB
Go

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")