feat: game lobby service
This commit is contained in:
@@ -22,7 +22,7 @@ const (
|
||||
initialEntitlementActorType common.ActorType = "service"
|
||||
initialEntitlementActorID common.ActorID = "user-service"
|
||||
|
||||
ensureCreateRetryLimit = 8
|
||||
ensureCreateRetryLimit = 10
|
||||
)
|
||||
|
||||
// ResolveByEmailInput stores one auth-facing resolve-by-email request.
|
||||
@@ -155,7 +155,6 @@ type Ensurer struct {
|
||||
store ports.AuthDirectoryStore
|
||||
clock ports.Clock
|
||||
idGenerator ports.IDGenerator
|
||||
policy ports.RaceNamePolicy
|
||||
logger *slog.Logger
|
||||
telemetry *telemetry.Runtime
|
||||
profilePublisher ports.ProfileChangedPublisher
|
||||
@@ -168,9 +167,8 @@ func NewEnsurer(
|
||||
store ports.AuthDirectoryStore,
|
||||
clock ports.Clock,
|
||||
idGenerator ports.IDGenerator,
|
||||
policy ports.RaceNamePolicy,
|
||||
) (*Ensurer, error) {
|
||||
return NewEnsurerWithObservability(store, clock, idGenerator, policy, nil, nil, nil, nil, nil)
|
||||
return NewEnsurerWithObservability(store, clock, idGenerator, nil, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
// NewEnsurerWithObservability returns one ensure-by-email use case instance
|
||||
@@ -180,7 +178,6 @@ func NewEnsurerWithObservability(
|
||||
store ports.AuthDirectoryStore,
|
||||
clock ports.Clock,
|
||||
idGenerator ports.IDGenerator,
|
||||
policy ports.RaceNamePolicy,
|
||||
logger *slog.Logger,
|
||||
telemetryRuntime *telemetry.Runtime,
|
||||
profilePublisher ports.ProfileChangedPublisher,
|
||||
@@ -194,14 +191,11 @@ func NewEnsurerWithObservability(
|
||||
return nil, fmt.Errorf("authdirectory ensurer: clock must not be nil")
|
||||
case idGenerator == nil:
|
||||
return nil, fmt.Errorf("authdirectory ensurer: id generator must not be nil")
|
||||
case policy == nil:
|
||||
return nil, fmt.Errorf("authdirectory ensurer: race-name policy must not be nil")
|
||||
default:
|
||||
return &Ensurer{
|
||||
store: store,
|
||||
clock: clock,
|
||||
idGenerator: idGenerator,
|
||||
policy: policy,
|
||||
logger: logger,
|
||||
telemetry: telemetryRuntime,
|
||||
profilePublisher: profilePublisher,
|
||||
@@ -256,7 +250,7 @@ func (service *Ensurer) Execute(ctx context.Context, input EnsureByEmailInput) (
|
||||
if err != nil {
|
||||
return EnsureByEmailResult{}, shared.ServiceUnavailable(err)
|
||||
}
|
||||
raceName, err := service.idGenerator.NewInitialRaceName()
|
||||
userName, err := service.idGenerator.NewUserName()
|
||||
if err != nil {
|
||||
return EnsureByEmailResult{}, shared.ServiceUnavailable(err)
|
||||
}
|
||||
@@ -264,7 +258,7 @@ func (service *Ensurer) Execute(ctx context.Context, input EnsureByEmailInput) (
|
||||
accountRecord := account.UserAccount{
|
||||
UserID: userID,
|
||||
Email: email,
|
||||
RaceName: raceName,
|
||||
UserName: userName,
|
||||
PreferredLanguage: preferredLanguage,
|
||||
TimeZone: timeZone,
|
||||
CreatedAt: now,
|
||||
@@ -294,21 +288,16 @@ func (service *Ensurer) Execute(ctx context.Context, input EnsureByEmailInput) (
|
||||
StartsAt: now,
|
||||
CreatedAt: now,
|
||||
}
|
||||
reservation, err := shared.BuildRaceNameReservation(service.policy, userID, raceName, now)
|
||||
if err != nil {
|
||||
return EnsureByEmailResult{}, shared.ServiceUnavailable(err)
|
||||
}
|
||||
|
||||
ensureResult, err := service.store.EnsureByEmail(ctx, ports.EnsureByEmailInput{
|
||||
Email: email,
|
||||
Account: accountRecord,
|
||||
Entitlement: entitlementSnapshot,
|
||||
EntitlementRecord: entitlementRecord,
|
||||
Reservation: reservation,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, ports.ErrRaceNameConflict) && service.telemetry != nil {
|
||||
service.telemetry.RecordRaceNameReservationConflict(ctx, "ensure_by_email")
|
||||
if errors.Is(err, ports.ErrUserNameConflict) && service.telemetry != nil {
|
||||
service.telemetry.RecordUserNameConflict(ctx, "ensure_by_email")
|
||||
}
|
||||
if errors.Is(err, ports.ErrConflict) {
|
||||
continue
|
||||
@@ -349,11 +338,12 @@ func (service *Ensurer) publishInitializedEvents(
|
||||
occurredAt := accountRecord.UpdatedAt.UTC()
|
||||
|
||||
service.publishProfileChanged(ctx, ports.ProfileChangedEvent{
|
||||
UserID: accountRecord.UserID,
|
||||
OccurredAt: occurredAt,
|
||||
Source: initialEntitlementSource,
|
||||
Operation: ports.ProfileChangedOperationInitialized,
|
||||
RaceName: accountRecord.RaceName,
|
||||
UserID: accountRecord.UserID,
|
||||
OccurredAt: occurredAt,
|
||||
Source: initialEntitlementSource,
|
||||
Operation: ports.ProfileChangedOperationInitialized,
|
||||
UserName: accountRecord.UserName,
|
||||
DisplayName: accountRecord.DisplayName,
|
||||
})
|
||||
service.publishSettingsChanged(ctx, ports.SettingsChangedEvent{
|
||||
UserID: accountRecord.UserID,
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"galaxy/user/internal/domain/account"
|
||||
"galaxy/user/internal/domain/common"
|
||||
"galaxy/user/internal/domain/entitlement"
|
||||
"galaxy/user/internal/domain/policy"
|
||||
@@ -101,12 +100,9 @@ func TestEnsurerExecuteCreatedBuildsInitialRecords(t *testing.T) {
|
||||
ensureByEmail: func(_ context.Context, input ports.EnsureByEmailInput) (ports.EnsureByEmailResult, error) {
|
||||
require.Equal(t, common.Email("created@example.com"), input.Email)
|
||||
require.Equal(t, common.UserID("user-created"), input.Account.UserID)
|
||||
require.Equal(t, common.RaceName("player-test123"), input.Account.RaceName)
|
||||
require.Equal(t, common.UserName("player-test123"), input.Account.UserName)
|
||||
require.Equal(t, common.LanguageTag("en-US"), input.Account.PreferredLanguage)
|
||||
require.Equal(t, common.TimeZoneName("Europe/Kaliningrad"), input.Account.TimeZone)
|
||||
require.Equal(t, input.Account.UserID, input.Reservation.UserID)
|
||||
require.Equal(t, input.Account.RaceName, input.Reservation.RaceName)
|
||||
require.Equal(t, accountTestCanonicalKey(input.Account.RaceName), input.Reservation.CanonicalKey)
|
||||
require.Equal(t, entitlement.PlanCodeFree, input.Entitlement.PlanCode)
|
||||
require.False(t, input.Entitlement.IsPaid)
|
||||
require.Equal(t, input.Account.UserID, input.Entitlement.UserID)
|
||||
@@ -124,9 +120,9 @@ func TestEnsurerExecuteCreatedBuildsInitialRecords(t *testing.T) {
|
||||
},
|
||||
}, fixedClock{now: now}, fixedIDGenerator{
|
||||
userID: common.UserID("user-created"),
|
||||
raceName: common.RaceName("player-test123"),
|
||||
userName: common.UserName("player-test123"),
|
||||
entitlementRecordID: entitlement.EntitlementRecordID("entitlement-created"),
|
||||
}, stubRaceNamePolicy{})
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := ensurer.Execute(context.Background(), EnsureByEmailInput{
|
||||
@@ -180,9 +176,9 @@ func TestEnsurerExecuteRejectsInvalidRegistrationContext(t *testing.T) {
|
||||
|
||||
ensurer, err := NewEnsurer(stubAuthDirectoryStore{}, fixedClock{now: time.Unix(1_775_240_000, 0).UTC()}, fixedIDGenerator{
|
||||
userID: common.UserID("user-created"),
|
||||
raceName: common.RaceName("player-test123"),
|
||||
userName: common.UserName("player-test123"),
|
||||
entitlementRecordID: entitlement.EntitlementRecordID("entitlement-created"),
|
||||
}, stubRaceNamePolicy{})
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ensurer.Execute(context.Background(), tt.input)
|
||||
@@ -210,9 +206,9 @@ func TestEnsurerExecuteRetriesConflicts(t *testing.T) {
|
||||
},
|
||||
}, fixedClock{now: time.Unix(1_775_240_000, 0).UTC()}, &sequenceIDGenerator{
|
||||
userIDs: []common.UserID{"user-first", "user-second"},
|
||||
raceNames: []common.RaceName{"player-first", "player-second"},
|
||||
userNames: []common.UserName{"player-firstxyz", "player-secondxy"},
|
||||
entitlementRecordIDs: []entitlement.EntitlementRecordID{"entitlement-first", "entitlement-second"},
|
||||
}, stubRaceNamePolicy{})
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := ensurer.Execute(context.Background(), EnsureByEmailInput{
|
||||
@@ -276,9 +272,9 @@ func TestEnsurerExecuteReturnsExistingAndBlocked(t *testing.T) {
|
||||
|
||||
ensurer, err := NewEnsurer(tt.store, fixedClock{now: time.Unix(1_775_240_000, 0).UTC()}, fixedIDGenerator{
|
||||
userID: common.UserID("user-created"),
|
||||
raceName: common.RaceName("player-test123"),
|
||||
userName: common.UserName("player-test123"),
|
||||
entitlementRecordID: entitlement.EntitlementRecordID("entitlement-created"),
|
||||
}, stubRaceNamePolicy{})
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := ensurer.Execute(context.Background(), EnsureByEmailInput{
|
||||
@@ -310,9 +306,9 @@ func TestEnsurerExecuteCreatedPublishesInitializedEvents(t *testing.T) {
|
||||
},
|
||||
}, fixedClock{now: now}, fixedIDGenerator{
|
||||
userID: common.UserID("user-created"),
|
||||
raceName: common.RaceName("player-test123"),
|
||||
userName: common.UserName("player-test123"),
|
||||
entitlementRecordID: entitlement.EntitlementRecordID("entitlement-created"),
|
||||
}, stubRaceNamePolicy{}, nil, telemetryRuntime, publisher, publisher, publisher)
|
||||
}, nil, telemetryRuntime, publisher, publisher, publisher)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := ensurer.Execute(context.Background(), EnsureByEmailInput{
|
||||
@@ -407,9 +403,9 @@ func TestEnsurerExecuteExistingBlockedAndFailedDoNotPublishEvents(t *testing.T)
|
||||
telemetryRuntime, reader := newObservedAuthTelemetryRuntime(t)
|
||||
ensurer, err := NewEnsurerWithObservability(tt.store, fixedClock{now: time.Unix(1_775_240_000, 0).UTC()}, fixedIDGenerator{
|
||||
userID: common.UserID("user-created"),
|
||||
raceName: common.RaceName("player-test123"),
|
||||
userName: common.UserName("player-test123"),
|
||||
entitlementRecordID: entitlement.EntitlementRecordID("entitlement-created"),
|
||||
}, stubRaceNamePolicy{}, nil, telemetryRuntime, publisher, publisher, publisher)
|
||||
}, nil, telemetryRuntime, publisher, publisher, publisher)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ensurer.Execute(context.Background(), tt.input)
|
||||
@@ -446,9 +442,9 @@ func TestEnsurerExecutePublishFailureDoesNotRollbackCreatedUser(t *testing.T) {
|
||||
},
|
||||
}, fixedClock{now: now}, fixedIDGenerator{
|
||||
userID: common.UserID("user-created"),
|
||||
raceName: common.RaceName("player-test123"),
|
||||
userName: common.UserName("player-test123"),
|
||||
entitlementRecordID: entitlement.EntitlementRecordID("entitlement-created"),
|
||||
}, stubRaceNamePolicy{}, nil, telemetryRuntime, publisher, publisher, publisher)
|
||||
}, nil, telemetryRuntime, publisher, publisher, publisher)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := ensurer.Execute(context.Background(), EnsureByEmailInput{
|
||||
@@ -546,7 +542,7 @@ func (clock fixedClock) Now() time.Time {
|
||||
|
||||
type fixedIDGenerator struct {
|
||||
userID common.UserID
|
||||
raceName common.RaceName
|
||||
userName common.UserName
|
||||
entitlementRecordID entitlement.EntitlementRecordID
|
||||
sanctionRecordID policy.SanctionRecordID
|
||||
limitRecordID policy.LimitRecordID
|
||||
@@ -556,8 +552,8 @@ func (generator fixedIDGenerator) NewUserID() (common.UserID, error) {
|
||||
return generator.userID, nil
|
||||
}
|
||||
|
||||
func (generator fixedIDGenerator) NewInitialRaceName() (common.RaceName, error) {
|
||||
return generator.raceName, nil
|
||||
func (generator fixedIDGenerator) NewUserName() (common.UserName, error) {
|
||||
return generator.userName, nil
|
||||
}
|
||||
|
||||
func (generator fixedIDGenerator) NewEntitlementRecordID() (entitlement.EntitlementRecordID, error) {
|
||||
@@ -574,7 +570,7 @@ func (generator fixedIDGenerator) NewLimitRecordID() (policy.LimitRecordID, erro
|
||||
|
||||
type sequenceIDGenerator struct {
|
||||
userIDs []common.UserID
|
||||
raceNames []common.RaceName
|
||||
userNames []common.UserName
|
||||
entitlementRecordIDs []entitlement.EntitlementRecordID
|
||||
sanctionRecordIDs []policy.SanctionRecordID
|
||||
limitRecordIDs []policy.LimitRecordID
|
||||
@@ -586,9 +582,9 @@ func (generator *sequenceIDGenerator) NewUserID() (common.UserID, error) {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (generator *sequenceIDGenerator) NewInitialRaceName() (common.RaceName, error) {
|
||||
value := generator.raceNames[0]
|
||||
generator.raceNames = generator.raceNames[1:]
|
||||
func (generator *sequenceIDGenerator) NewUserName() (common.UserName, error) {
|
||||
value := generator.userNames[0]
|
||||
generator.userNames = generator.userNames[1:]
|
||||
return value, nil
|
||||
}
|
||||
|
||||
@@ -610,16 +606,6 @@ func (generator *sequenceIDGenerator) NewLimitRecordID() (policy.LimitRecordID,
|
||||
return value, nil
|
||||
}
|
||||
|
||||
type stubRaceNamePolicy struct{}
|
||||
|
||||
func (stubRaceNamePolicy) CanonicalKey(raceName common.RaceName) (account.RaceNameCanonicalKey, error) {
|
||||
return accountTestCanonicalKey(raceName), nil
|
||||
}
|
||||
|
||||
func accountTestCanonicalKey(raceName common.RaceName) account.RaceNameCanonicalKey {
|
||||
return account.RaceNameCanonicalKey("key:" + raceName.String())
|
||||
}
|
||||
|
||||
type recordingAuthDomainEventPublisher struct {
|
||||
err error
|
||||
profileEvents []ports.ProfileChangedEvent
|
||||
@@ -710,7 +696,6 @@ var (
|
||||
_ ports.Clock = fixedClock{}
|
||||
_ ports.IDGenerator = fixedIDGenerator{}
|
||||
_ ports.IDGenerator = (*sequenceIDGenerator)(nil)
|
||||
_ ports.RaceNamePolicy = stubRaceNamePolicy{}
|
||||
_ ports.ProfileChangedPublisher = (*recordingAuthDomainEventPublisher)(nil)
|
||||
_ ports.SettingsChangedPublisher = (*recordingAuthDomainEventPublisher)(nil)
|
||||
_ ports.EntitlementChangedPublisher = (*recordingAuthDomainEventPublisher)(nil)
|
||||
|
||||
Reference in New Issue
Block a user