feat: game lobby service
This commit is contained in:
@@ -0,0 +1,785 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"galaxy/lobby/internal/adapters/gmclient"
|
||||
"galaxy/lobby/internal/adapters/idgen"
|
||||
"galaxy/lobby/internal/adapters/metricsintentpub"
|
||||
"galaxy/lobby/internal/adapters/metricsracenamedir"
|
||||
"galaxy/lobby/internal/adapters/racenameintents"
|
||||
"galaxy/lobby/internal/adapters/racenamestub"
|
||||
"galaxy/lobby/internal/adapters/redisstate"
|
||||
"galaxy/lobby/internal/adapters/runtimemanager"
|
||||
"galaxy/lobby/internal/adapters/userlifecycle"
|
||||
"galaxy/lobby/internal/adapters/userservice"
|
||||
"galaxy/lobby/internal/config"
|
||||
"galaxy/lobby/internal/domain/racename"
|
||||
"galaxy/lobby/internal/ports"
|
||||
"galaxy/lobby/internal/telemetry"
|
||||
"galaxy/lobby/internal/service/approveapplication"
|
||||
"galaxy/lobby/internal/service/blockmember"
|
||||
"galaxy/lobby/internal/service/cancelgame"
|
||||
"galaxy/lobby/internal/service/createinvite"
|
||||
"galaxy/lobby/internal/service/creategame"
|
||||
"galaxy/lobby/internal/service/declineinvite"
|
||||
"galaxy/lobby/internal/service/getgame"
|
||||
"galaxy/lobby/internal/service/listgames"
|
||||
"galaxy/lobby/internal/service/listmemberships"
|
||||
"galaxy/lobby/internal/service/listmyapplications"
|
||||
"galaxy/lobby/internal/service/listmygames"
|
||||
"galaxy/lobby/internal/service/listmyinvites"
|
||||
"galaxy/lobby/internal/service/listmyracenames"
|
||||
"galaxy/lobby/internal/service/manualreadytostart"
|
||||
"galaxy/lobby/internal/service/openenrollment"
|
||||
"galaxy/lobby/internal/service/pausegame"
|
||||
"galaxy/lobby/internal/service/redeeminvite"
|
||||
"galaxy/lobby/internal/service/registerracename"
|
||||
"galaxy/lobby/internal/service/rejectapplication"
|
||||
"galaxy/lobby/internal/service/removemember"
|
||||
"galaxy/lobby/internal/service/resumegame"
|
||||
"galaxy/lobby/internal/service/capabilityevaluation"
|
||||
"galaxy/lobby/internal/service/retrystartgame"
|
||||
"galaxy/lobby/internal/service/revokeinvite"
|
||||
"galaxy/lobby/internal/service/startgame"
|
||||
"galaxy/lobby/internal/service/submitapplication"
|
||||
"galaxy/lobby/internal/service/updategame"
|
||||
"galaxy/lobby/internal/worker/enrollmentautomation"
|
||||
"galaxy/lobby/internal/worker/gmevents"
|
||||
"galaxy/lobby/internal/worker/pendingregistration"
|
||||
"galaxy/lobby/internal/worker/runtimejobresult"
|
||||
userlifecycleworker "galaxy/lobby/internal/worker/userlifecycle"
|
||||
"galaxy/notificationintent"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// wiring owns the process-level singletons that downstream service
|
||||
// constructors resolve through their port interfaces. It is the single
|
||||
// place in the process where concrete adapter types are referenced, so
|
||||
// service code always depends on ports rather than on specific adapters.
|
||||
//
|
||||
// extends this struct with the application/membership stores,
|
||||
// the gap-activation store, the User Service client, the notification
|
||||
// intent publisher, and the three application services.
|
||||
type wiring struct {
|
||||
// policy is the lobby-owned Race Name Directory canonical-key
|
||||
// policy, shared between the RND adapter and any future service
|
||||
// that needs to call Canonicalize directly.
|
||||
policy *racename.Policy
|
||||
|
||||
// raceNameDirectory is the platform-wide in-game name uniqueness
|
||||
// arbiter.
|
||||
raceNameDirectory ports.RaceNameDirectory
|
||||
|
||||
// gameStore persists game records.
|
||||
gameStore ports.GameStore
|
||||
|
||||
// applicationStore persists application records.
|
||||
applicationStore ports.ApplicationStore
|
||||
|
||||
// inviteStore persists invite records.
|
||||
inviteStore ports.InviteStore
|
||||
|
||||
// membershipStore persists membership records.
|
||||
membershipStore ports.MembershipStore
|
||||
|
||||
// gapActivationStore records when a game's gap window opens
|
||||
//.
|
||||
gapActivationStore ports.GapActivationStore
|
||||
|
||||
// userService is the synchronous User Service eligibility client
|
||||
//.
|
||||
userService ports.UserService
|
||||
|
||||
// intentPublisher publishes notification intents to
|
||||
// notification:intents.
|
||||
intentPublisher ports.IntentPublisher
|
||||
|
||||
// idGenerator produces opaque identifiers for new records.
|
||||
idGenerator ports.IDGenerator
|
||||
|
||||
// createGame handles `lobby.game.create`.
|
||||
createGame *creategame.Service
|
||||
|
||||
// updateGame handles `lobby.game.update`.
|
||||
updateGame *updategame.Service
|
||||
|
||||
// openEnrollment handles `lobby.game.open_enrollment`.
|
||||
openEnrollment *openenrollment.Service
|
||||
|
||||
// cancelGame handles `lobby.game.cancel`.
|
||||
cancelGame *cancelgame.Service
|
||||
|
||||
// manualReadyToStart handles `lobby.game.ready_to_start`.
|
||||
manualReadyToStart *manualreadytostart.Service
|
||||
|
||||
// enrollmentAutomation drives the periodic auto-close worker
|
||||
//.
|
||||
enrollmentAutomation *enrollmentautomation.Worker
|
||||
|
||||
// submitApplication handles `lobby.application.submit`.
|
||||
submitApplication *submitapplication.Service
|
||||
|
||||
// approveApplication handles `lobby.application.approve`.
|
||||
approveApplication *approveapplication.Service
|
||||
|
||||
// rejectApplication handles `lobby.application.reject`.
|
||||
rejectApplication *rejectapplication.Service
|
||||
|
||||
// createInvite handles `lobby.invite.create`.
|
||||
createInvite *createinvite.Service
|
||||
|
||||
// redeemInvite handles `lobby.invite.redeem`.
|
||||
redeemInvite *redeeminvite.Service
|
||||
|
||||
// declineInvite handles `lobby.invite.decline`.
|
||||
declineInvite *declineinvite.Service
|
||||
|
||||
// revokeInvite handles `lobby.invite.revoke`.
|
||||
revokeInvite *revokeinvite.Service
|
||||
|
||||
// runtimeManager publishes start and stop jobs to Runtime Manager
|
||||
//.
|
||||
runtimeManager ports.RuntimeManager
|
||||
|
||||
// gmClient registers running games with Game Master.
|
||||
gmClient ports.GMClient
|
||||
|
||||
// streamOffsetStore persists Redis Streams consumer progress
|
||||
//.
|
||||
streamOffsetStore ports.StreamOffsetStore
|
||||
|
||||
// startGame handles `lobby.game.start`.
|
||||
startGame *startgame.Service
|
||||
|
||||
// retryStartGame handles `lobby.game.retry_start`.
|
||||
retryStartGame *retrystartgame.Service
|
||||
|
||||
// pauseGame handles `lobby.game.pause`.
|
||||
pauseGame *pausegame.Service
|
||||
|
||||
// resumeGame handles `lobby.game.resume`.
|
||||
resumeGame *resumegame.Service
|
||||
|
||||
// removeMember handles `lobby.membership.remove`.
|
||||
removeMember *removemember.Service
|
||||
|
||||
// blockMember handles `lobby.membership.block`.
|
||||
blockMember *blockmember.Service
|
||||
|
||||
// registerRaceName handles `lobby.race_name.register`.
|
||||
registerRaceName *registerracename.Service
|
||||
|
||||
// listMyRaceNames handles `lobby.race_names.list`.
|
||||
listMyRaceNames *listmyracenames.Service
|
||||
|
||||
// getGame handles `lobby.game.get`.
|
||||
getGame *getgame.Service
|
||||
|
||||
// listGames handles `lobby.games.list`.
|
||||
listGames *listgames.Service
|
||||
|
||||
// listMemberships handles `lobby.memberships.list`.
|
||||
listMemberships *listmemberships.Service
|
||||
|
||||
// listMyGames handles `lobby.my_games.list`.
|
||||
listMyGames *listmygames.Service
|
||||
|
||||
// listMyApplications handles `lobby.my_applications.list`.
|
||||
listMyApplications *listmyapplications.Service
|
||||
|
||||
// listMyInvites handles `lobby.my_invites.list`.
|
||||
listMyInvites *listmyinvites.Service
|
||||
|
||||
// runtimeJobResultConsumer consumes runtime:job_results and drives
|
||||
// the post-start sequence.
|
||||
runtimeJobResultConsumer *runtimejobresult.Consumer
|
||||
|
||||
// gameTurnStatsStore persists the per-game per-user stats aggregate
|
||||
// fed by every runtime_snapshot_update event.
|
||||
gameTurnStatsStore ports.GameTurnStatsStore
|
||||
|
||||
// evaluationGuardStore stores the per-game «already evaluated»
|
||||
// marker that keeps replayed game_finished events safe.
|
||||
evaluationGuardStore ports.EvaluationGuardStore
|
||||
|
||||
// capabilityEvaluation runs the capability evaluator at
|
||||
// game finish.
|
||||
capabilityEvaluation *capabilityevaluation.Service
|
||||
|
||||
// gmEventsConsumer consumes gm:lobby_events and applies snapshot
|
||||
// updates plus capability evaluation handoff.
|
||||
gmEventsConsumer *gmevents.Consumer
|
||||
|
||||
// pendingRegistration releases expired Race Name Directory
|
||||
// pending_registration entries on a periodic tick.
|
||||
pendingRegistration *pendingregistration.Worker
|
||||
|
||||
// userLifecycleConsumer reads `user:lifecycle_events` from User
|
||||
// Service and dispatches each entry to userLifecycleWorker. The
|
||||
// consumer is the long-lived Component registered with app.New;
|
||||
// the worker has no goroutine of its own.
|
||||
userLifecycleConsumer *userlifecycle.Consumer
|
||||
|
||||
// userLifecycleWorker runs the cascade triggered by each lifecycle
|
||||
// event.
|
||||
userLifecycleWorker *userlifecycleworker.Worker
|
||||
}
|
||||
|
||||
// newWiring constructs the process-level dependency set.
|
||||
func newWiring(
|
||||
cfg config.Config,
|
||||
redisClient *redis.Client,
|
||||
clock func() time.Time,
|
||||
logger *slog.Logger,
|
||||
telemetryRuntime *telemetry.Runtime,
|
||||
) (*wiring, error) {
|
||||
policy, err := racename.NewPolicy()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
if clock == nil {
|
||||
clock = time.Now
|
||||
}
|
||||
if logger == nil {
|
||||
logger = slog.Default()
|
||||
}
|
||||
|
||||
rawDirectory, err := buildRaceNameDirectory(cfg, redisClient, policy, clock)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
directory := metricsracenamedir.New(rawDirectory, telemetryRuntime)
|
||||
|
||||
if redisClient == nil {
|
||||
return nil, errors.New("new lobby wiring: nil redis client")
|
||||
}
|
||||
|
||||
gameStore, err := redisstate.NewGameStore(redisClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
applicationStore, err := redisstate.NewApplicationStore(redisClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
inviteStore, err := redisstate.NewInviteStore(redisClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
membershipStore, err := redisstate.NewMembershipStore(redisClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
gapActivationStore, err := redisstate.NewGapActivationStore(redisClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
|
||||
userServiceClient, err := userservice.NewClient(userservice.Config{
|
||||
BaseURL: cfg.UserService.BaseURL,
|
||||
Timeout: cfg.UserService.Timeout,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
|
||||
rawIntentPublisher, err := notificationintent.NewPublisher(notificationintent.PublisherConfig{
|
||||
Client: redisClient,
|
||||
Stream: cfg.Redis.NotificationIntentsStream,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
intentPublisher := metricsintentpub.New(rawIntentPublisher, telemetryRuntime)
|
||||
|
||||
ids := idgen.NewGenerator()
|
||||
|
||||
createSvc, err := creategame.NewService(creategame.Dependencies{
|
||||
Games: gameStore,
|
||||
IDs: ids,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
updateSvc, err := updategame.NewService(updategame.Dependencies{
|
||||
Games: gameStore,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
openSvc, err := openenrollment.NewService(openenrollment.Dependencies{
|
||||
Games: gameStore,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
cancelSvc, err := cancelgame.NewService(cancelgame.Dependencies{
|
||||
Games: gameStore,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
manualReadySvc, err := manualreadytostart.NewService(manualreadytostart.Dependencies{
|
||||
Games: gameStore,
|
||||
Memberships: membershipStore,
|
||||
Invites: inviteStore,
|
||||
Intents: intentPublisher,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
|
||||
submitSvc, err := submitapplication.NewService(submitapplication.Dependencies{
|
||||
Games: gameStore,
|
||||
Memberships: membershipStore,
|
||||
Applications: applicationStore,
|
||||
Users: userServiceClient,
|
||||
Directory: directory,
|
||||
Intents: intentPublisher,
|
||||
IDs: ids,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
approveSvc, err := approveapplication.NewService(approveapplication.Dependencies{
|
||||
Games: gameStore,
|
||||
Memberships: membershipStore,
|
||||
Applications: applicationStore,
|
||||
Directory: directory,
|
||||
GapStore: gapActivationStore,
|
||||
Intents: intentPublisher,
|
||||
IDs: ids,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
rejectSvc, err := rejectapplication.NewService(rejectapplication.Dependencies{
|
||||
Games: gameStore,
|
||||
Applications: applicationStore,
|
||||
Directory: directory,
|
||||
Intents: intentPublisher,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
|
||||
createInviteSvc, err := createinvite.NewService(createinvite.Dependencies{
|
||||
Games: gameStore,
|
||||
Invites: inviteStore,
|
||||
Memberships: membershipStore,
|
||||
Intents: intentPublisher,
|
||||
IDs: ids,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
redeemInviteSvc, err := redeeminvite.NewService(redeeminvite.Dependencies{
|
||||
Games: gameStore,
|
||||
Invites: inviteStore,
|
||||
Memberships: membershipStore,
|
||||
Directory: directory,
|
||||
Users: userServiceClient,
|
||||
GapStore: gapActivationStore,
|
||||
Intents: intentPublisher,
|
||||
IDs: ids,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
declineInviteSvc, err := declineinvite.NewService(declineinvite.Dependencies{
|
||||
Invites: inviteStore,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
revokeInviteSvc, err := revokeinvite.NewService(revokeinvite.Dependencies{
|
||||
Games: gameStore,
|
||||
Invites: inviteStore,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
|
||||
enrollmentWorker, err := enrollmentautomation.NewWorker(enrollmentautomation.Dependencies{
|
||||
Games: gameStore,
|
||||
Memberships: membershipStore,
|
||||
Invites: inviteStore,
|
||||
Intents: intentPublisher,
|
||||
GapStore: gapActivationStore,
|
||||
Interval: cfg.EnrollmentAutomation.Interval,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
|
||||
gmClientImpl, err := gmclient.NewClient(gmclient.Config{
|
||||
BaseURL: cfg.GM.BaseURL,
|
||||
Timeout: cfg.GM.Timeout,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
|
||||
runtimePublisher, err := runtimemanager.NewPublisher(runtimemanager.Config{
|
||||
Client: redisClient,
|
||||
StartJobsStream: cfg.Redis.RuntimeStartJobsStream,
|
||||
StopJobsStream: cfg.Redis.RuntimeStopJobsStream,
|
||||
Clock: clock,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
|
||||
streamOffsets, err := redisstate.NewStreamOffsetStore(redisClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
|
||||
startSvc, err := startgame.NewService(startgame.Dependencies{
|
||||
Games: gameStore,
|
||||
RuntimeManager: runtimePublisher,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
retrySvc, err := retrystartgame.NewService(retrystartgame.Dependencies{
|
||||
Games: gameStore,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
pauseSvc, err := pausegame.NewService(pausegame.Dependencies{
|
||||
Games: gameStore,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
resumeSvc, err := resumegame.NewService(resumegame.Dependencies{
|
||||
Games: gameStore,
|
||||
GM: gmClientImpl,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
removeMemberSvc, err := removemember.NewService(removemember.Dependencies{
|
||||
Games: gameStore,
|
||||
Memberships: membershipStore,
|
||||
Directory: directory,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
blockMemberSvc, err := blockmember.NewService(blockmember.Dependencies{
|
||||
Games: gameStore,
|
||||
Memberships: membershipStore,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
registerRaceNameSvc, err := registerracename.NewService(registerracename.Dependencies{
|
||||
Directory: directory,
|
||||
Users: userServiceClient,
|
||||
Intents: intentPublisher,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
listMyRaceNamesSvc, err := listmyracenames.NewService(listmyracenames.Dependencies{
|
||||
Directory: directory,
|
||||
Games: gameStore,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
getGameSvc, err := getgame.NewService(getgame.Dependencies{
|
||||
Games: gameStore,
|
||||
Memberships: membershipStore,
|
||||
Invites: inviteStore,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
listGamesSvc, err := listgames.NewService(listgames.Dependencies{
|
||||
Games: gameStore,
|
||||
Memberships: membershipStore,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
listMembershipsSvc, err := listmemberships.NewService(listmemberships.Dependencies{
|
||||
Games: gameStore,
|
||||
Memberships: membershipStore,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
listMyGamesSvc, err := listmygames.NewService(listmygames.Dependencies{
|
||||
Games: gameStore,
|
||||
Memberships: membershipStore,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
listMyApplicationsSvc, err := listmyapplications.NewService(listmyapplications.Dependencies{
|
||||
Games: gameStore,
|
||||
Applications: applicationStore,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
listMyInvitesSvc, err := listmyinvites.NewService(listmyinvites.Dependencies{
|
||||
Games: gameStore,
|
||||
Invites: inviteStore,
|
||||
Memberships: membershipStore,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
|
||||
runtimeConsumer, err := runtimejobresult.NewConsumer(runtimejobresult.Config{
|
||||
Client: redisClient,
|
||||
Stream: cfg.Redis.RuntimeJobResultsStream,
|
||||
BlockTimeout: cfg.Redis.RuntimeJobResultsReadBlockTimeout,
|
||||
Games: gameStore,
|
||||
RuntimeManager: runtimePublisher,
|
||||
GMClient: gmClientImpl,
|
||||
Intents: intentPublisher,
|
||||
OffsetStore: streamOffsets,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
|
||||
gameTurnStatsStore, err := redisstate.NewGameTurnStatsStore(redisClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
evaluationGuardStore, err := redisstate.NewEvaluationGuardStore(redisClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
raceNameIntents, err := racenameintents.NewPublisher(racenameintents.Config{
|
||||
Publisher: intentPublisher,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
capabilityService, err := capabilityevaluation.NewService(capabilityevaluation.Dependencies{
|
||||
Games: gameStore,
|
||||
Memberships: membershipStore,
|
||||
Stats: gameTurnStatsStore,
|
||||
Directory: directory,
|
||||
Intents: raceNameIntents,
|
||||
Guard: evaluationGuardStore,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
gmConsumer, err := gmevents.NewConsumer(gmevents.Config{
|
||||
Client: redisClient,
|
||||
Stream: cfg.Redis.GMEventsStream,
|
||||
BlockTimeout: cfg.Redis.GMEventsReadBlockTimeout,
|
||||
Games: gameStore,
|
||||
Stats: gameTurnStatsStore,
|
||||
Capability: capabilityService,
|
||||
OffsetStore: streamOffsets,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
|
||||
pendingRegistrationWorker, err := pendingregistration.NewWorker(pendingregistration.Dependencies{
|
||||
Directory: directory,
|
||||
Interval: cfg.PendingRegistration.Interval,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
|
||||
userLifecycleWorker, err := userlifecycleworker.NewWorker(userlifecycleworker.Dependencies{
|
||||
Directory: directory,
|
||||
Memberships: membershipStore,
|
||||
Applications: applicationStore,
|
||||
Invites: inviteStore,
|
||||
Games: gameStore,
|
||||
RuntimeManager: runtimePublisher,
|
||||
Intents: intentPublisher,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
userLifecycleConsumer, err := userlifecycle.NewConsumer(userlifecycle.Config{
|
||||
Client: redisClient,
|
||||
Stream: cfg.Redis.UserLifecycleStream,
|
||||
BlockTimeout: cfg.Redis.UserLifecycleReadBlockTimeout,
|
||||
OffsetStore: streamOffsets,
|
||||
Clock: clock,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
||||
}
|
||||
userLifecycleConsumer.OnEvent(userLifecycleWorker.Handle)
|
||||
|
||||
return &wiring{
|
||||
policy: policy,
|
||||
raceNameDirectory: directory,
|
||||
gameStore: gameStore,
|
||||
applicationStore: applicationStore,
|
||||
inviteStore: inviteStore,
|
||||
membershipStore: membershipStore,
|
||||
gapActivationStore: gapActivationStore,
|
||||
userService: userServiceClient,
|
||||
intentPublisher: intentPublisher,
|
||||
idGenerator: ids,
|
||||
createGame: createSvc,
|
||||
updateGame: updateSvc,
|
||||
openEnrollment: openSvc,
|
||||
cancelGame: cancelSvc,
|
||||
manualReadyToStart: manualReadySvc,
|
||||
submitApplication: submitSvc,
|
||||
approveApplication: approveSvc,
|
||||
rejectApplication: rejectSvc,
|
||||
createInvite: createInviteSvc,
|
||||
redeemInvite: redeemInviteSvc,
|
||||
declineInvite: declineInviteSvc,
|
||||
revokeInvite: revokeInviteSvc,
|
||||
enrollmentAutomation: enrollmentWorker,
|
||||
runtimeManager: runtimePublisher,
|
||||
gmClient: gmClientImpl,
|
||||
streamOffsetStore: streamOffsets,
|
||||
startGame: startSvc,
|
||||
retryStartGame: retrySvc,
|
||||
pauseGame: pauseSvc,
|
||||
resumeGame: resumeSvc,
|
||||
removeMember: removeMemberSvc,
|
||||
blockMember: blockMemberSvc,
|
||||
registerRaceName: registerRaceNameSvc,
|
||||
listMyRaceNames: listMyRaceNamesSvc,
|
||||
getGame: getGameSvc,
|
||||
listGames: listGamesSvc,
|
||||
listMemberships: listMembershipsSvc,
|
||||
listMyGames: listMyGamesSvc,
|
||||
listMyApplications: listMyApplicationsSvc,
|
||||
listMyInvites: listMyInvitesSvc,
|
||||
runtimeJobResultConsumer: runtimeConsumer,
|
||||
gameTurnStatsStore: gameTurnStatsStore,
|
||||
evaluationGuardStore: evaluationGuardStore,
|
||||
capabilityEvaluation: capabilityService,
|
||||
gmEventsConsumer: gmConsumer,
|
||||
pendingRegistration: pendingRegistrationWorker,
|
||||
userLifecycleConsumer: userLifecycleConsumer,
|
||||
userLifecycleWorker: userLifecycleWorker,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// buildRaceNameDirectory instantiates the Race Name Directory adapter
|
||||
// selected by cfg.RaceNameDirectory.Backend.
|
||||
func buildRaceNameDirectory(
|
||||
cfg config.Config,
|
||||
redisClient *redis.Client,
|
||||
policy *racename.Policy,
|
||||
clock func() time.Time,
|
||||
) (ports.RaceNameDirectory, error) {
|
||||
switch cfg.RaceNameDirectory.Backend {
|
||||
case config.RaceNameDirectoryBackendRedis:
|
||||
if redisClient == nil {
|
||||
return nil, errors.New("redis race name directory backend requires a Redis client")
|
||||
}
|
||||
return redisstate.NewRaceNameDirectory(
|
||||
redisClient,
|
||||
policy,
|
||||
redisstate.WithRaceNameDirectoryClock(clock),
|
||||
)
|
||||
case config.RaceNameDirectoryBackendStub:
|
||||
return racenamestub.NewDirectory(racenamestub.WithClock(clock))
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported race name directory backend %q", cfg.RaceNameDirectory.Backend)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user