812 lines
25 KiB
Go
812 lines
25 KiB
Go
package app
|
|
|
|
import (
|
|
"database/sql"
|
|
"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"
|
|
pgapplicationstore "galaxy/lobby/internal/adapters/postgres/applicationstore"
|
|
pggamestore "galaxy/lobby/internal/adapters/postgres/gamestore"
|
|
pginvitestore "galaxy/lobby/internal/adapters/postgres/invitestore"
|
|
pgmembershipstore "galaxy/lobby/internal/adapters/postgres/membershipstore"
|
|
pgracenamedir "galaxy/lobby/internal/adapters/postgres/racenamedir"
|
|
"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,
|
|
pgPool *sql.DB,
|
|
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()
|
|
}
|
|
|
|
if redisClient == nil {
|
|
return nil, errors.New("new lobby wiring: nil redis client")
|
|
}
|
|
if pgPool == nil {
|
|
return nil, errors.New("new lobby wiring: nil postgres pool")
|
|
}
|
|
|
|
rawDirectory, err := buildRaceNameDirectory(cfg, pgPool, policy, clock)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
|
}
|
|
directory := metricsracenamedir.New(rawDirectory, telemetryRuntime)
|
|
|
|
pgStoreCfg := struct {
|
|
DB *sql.DB
|
|
OperationTimeout time.Duration
|
|
}{
|
|
DB: pgPool,
|
|
OperationTimeout: cfg.Postgres.Conn.OperationTimeout,
|
|
}
|
|
gameStore, err := pggamestore.New(pggamestore.Config{
|
|
DB: pgStoreCfg.DB, OperationTimeout: pgStoreCfg.OperationTimeout,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
|
}
|
|
applicationStore, err := pgapplicationstore.New(pgapplicationstore.Config{
|
|
DB: pgStoreCfg.DB, OperationTimeout: pgStoreCfg.OperationTimeout,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
|
}
|
|
inviteStore, err := pginvitestore.New(pginvitestore.Config{
|
|
DB: pgStoreCfg.DB, OperationTimeout: pgStoreCfg.OperationTimeout,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("new lobby wiring: %w", err)
|
|
}
|
|
membershipStore, err := pgmembershipstore.New(pgmembershipstore.Config{
|
|
DB: pgStoreCfg.DB, OperationTimeout: pgStoreCfg.OperationTimeout,
|
|
})
|
|
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,
|
|
pgPool *sql.DB,
|
|
policy *racename.Policy,
|
|
clock func() time.Time,
|
|
) (ports.RaceNameDirectory, error) {
|
|
switch cfg.RaceNameDirectory.Backend {
|
|
case config.RaceNameDirectoryBackendPostgres:
|
|
if pgPool == nil {
|
|
return nil, errors.New("postgres race name directory backend requires a Postgres pool")
|
|
}
|
|
return pgracenamedir.New(pgracenamedir.Config{
|
|
DB: pgPool,
|
|
OperationTimeout: cfg.Postgres.Conn.OperationTimeout,
|
|
Policy: policy,
|
|
Clock: clock,
|
|
})
|
|
case config.RaceNameDirectoryBackendStub:
|
|
return racenamestub.NewDirectory(racenamestub.WithClock(clock))
|
|
default:
|
|
return nil, fmt.Errorf("unsupported race name directory backend %q", cfg.RaceNameDirectory.Backend)
|
|
}
|
|
}
|