feat: game lobby service
This commit is contained in:
@@ -4,38 +4,11 @@ package account
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"galaxy/user/internal/domain/common"
|
||||
)
|
||||
|
||||
// RaceNameCanonicalKey stores the policy-produced reservation key used to
|
||||
// enforce replaceable race-name uniqueness.
|
||||
type RaceNameCanonicalKey string
|
||||
|
||||
// String returns RaceNameCanonicalKey as its stored canonical string.
|
||||
func (key RaceNameCanonicalKey) String() string {
|
||||
return string(key)
|
||||
}
|
||||
|
||||
// IsZero reports whether RaceNameCanonicalKey does not contain a usable value.
|
||||
func (key RaceNameCanonicalKey) IsZero() bool {
|
||||
return strings.TrimSpace(string(key)) == ""
|
||||
}
|
||||
|
||||
// Validate reports whether RaceNameCanonicalKey is non-empty and trimmed.
|
||||
func (key RaceNameCanonicalKey) Validate() error {
|
||||
switch {
|
||||
case key.IsZero():
|
||||
return fmt.Errorf("race name canonical key must not be empty")
|
||||
case strings.TrimSpace(string(key)) != string(key):
|
||||
return fmt.Errorf("race name canonical key must not contain surrounding whitespace")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// UserAccount stores the current editable account state of one regular user.
|
||||
type UserAccount struct {
|
||||
// UserID identifies the durable regular-user account.
|
||||
@@ -44,8 +17,11 @@ type UserAccount struct {
|
||||
// Email stores the normalized login/contact address of the account.
|
||||
Email common.Email
|
||||
|
||||
// RaceName stores the original-casing user-facing race name.
|
||||
RaceName common.RaceName
|
||||
// UserName stores the immutable auto-generated `player-<suffix>` handle.
|
||||
UserName common.UserName
|
||||
|
||||
// DisplayName stores the optional mutable free-text user-facing label.
|
||||
DisplayName common.DisplayName
|
||||
|
||||
// PreferredLanguage stores the current declared language tag.
|
||||
PreferredLanguage common.LanguageTag
|
||||
@@ -62,10 +38,23 @@ type UserAccount struct {
|
||||
|
||||
// UpdatedAt stores the last account mutation timestamp.
|
||||
UpdatedAt time.Time
|
||||
|
||||
// DeletedAt stores the soft-delete timestamp set by the `DeleteUser`
|
||||
// command. A nil value means the account is live. A non-nil value marks
|
||||
// the record as soft-deleted: external auth, self-service, admin-read,
|
||||
// and lobby-eligibility operations must reject subsequent access with
|
||||
// `subject_not_found`.
|
||||
DeletedAt *time.Time
|
||||
}
|
||||
|
||||
// Validate reports whether UserAccount satisfies the frozen Stage 02
|
||||
// structural invariants.
|
||||
// IsDeleted reports whether the account has been soft-deleted through the
|
||||
// `DeleteUser` command.
|
||||
func (record UserAccount) IsDeleted() bool {
|
||||
return record.DeletedAt != nil
|
||||
}
|
||||
|
||||
// Validate reports whether UserAccount satisfies the Stage 21 structural
|
||||
// invariants, including the Stage 22 soft-delete rules.
|
||||
func (record UserAccount) Validate() error {
|
||||
if err := record.UserID.Validate(); err != nil {
|
||||
return fmt.Errorf("user account user id: %w", err)
|
||||
@@ -73,8 +62,11 @@ func (record UserAccount) Validate() error {
|
||||
if err := record.Email.Validate(); err != nil {
|
||||
return fmt.Errorf("user account email: %w", err)
|
||||
}
|
||||
if err := record.RaceName.Validate(); err != nil {
|
||||
return fmt.Errorf("user account race name: %w", err)
|
||||
if err := record.UserName.Validate(); err != nil {
|
||||
return fmt.Errorf("user account user name: %w", err)
|
||||
}
|
||||
if err := record.DisplayName.Validate(); err != nil {
|
||||
return fmt.Errorf("user account display name: %w", err)
|
||||
}
|
||||
if err := record.PreferredLanguage.Validate(); err != nil {
|
||||
return fmt.Errorf("user account preferred language: %w", err)
|
||||
@@ -96,40 +88,13 @@ func (record UserAccount) Validate() error {
|
||||
if record.UpdatedAt.Before(record.CreatedAt) {
|
||||
return fmt.Errorf("user account updated at must not be before created at")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RaceNameReservation stores the current uniqueness reservation for one
|
||||
// canonicalized race-name key.
|
||||
type RaceNameReservation struct {
|
||||
// CanonicalKey stores the policy-produced uniqueness key.
|
||||
CanonicalKey RaceNameCanonicalKey
|
||||
|
||||
// UserID identifies the account that owns the reservation.
|
||||
UserID common.UserID
|
||||
|
||||
// RaceName stores the original-casing name linked to the reservation.
|
||||
RaceName common.RaceName
|
||||
|
||||
// ReservedAt stores when the reservation was acquired.
|
||||
ReservedAt time.Time
|
||||
}
|
||||
|
||||
// Validate reports whether RaceNameReservation satisfies the frozen Stage 02
|
||||
// structural invariants.
|
||||
func (record RaceNameReservation) Validate() error {
|
||||
if err := record.CanonicalKey.Validate(); err != nil {
|
||||
return fmt.Errorf("race name reservation canonical key: %w", err)
|
||||
}
|
||||
if err := record.UserID.Validate(); err != nil {
|
||||
return fmt.Errorf("race name reservation user id: %w", err)
|
||||
}
|
||||
if err := record.RaceName.Validate(); err != nil {
|
||||
return fmt.Errorf("race name reservation race name: %w", err)
|
||||
}
|
||||
if err := common.ValidateTimestamp("race name reservation reserved at", record.ReservedAt); err != nil {
|
||||
return err
|
||||
if record.DeletedAt != nil {
|
||||
if err := common.ValidateTimestamp("user account deleted at", *record.DeletedAt); err != nil {
|
||||
return err
|
||||
}
|
||||
if record.DeletedAt.Before(record.CreatedAt) {
|
||||
return fmt.Errorf("user account deleted at must not be before created at")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user