feat: user service
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
package ports
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"galaxy/user/internal/domain/account"
|
||||
"galaxy/user/internal/domain/common"
|
||||
)
|
||||
|
||||
// CreateAccountInput stores the atomic account-create state that must commit
|
||||
// together.
|
||||
type CreateAccountInput struct {
|
||||
// Account stores the durable user-account state.
|
||||
Account account.UserAccount
|
||||
|
||||
// Reservation stores the canonical race-name reservation linked to Account.
|
||||
Reservation account.RaceNameReservation
|
||||
}
|
||||
|
||||
// Validate reports whether CreateAccountInput is structurally complete.
|
||||
func (input CreateAccountInput) Validate() error {
|
||||
if err := input.Account.Validate(); err != nil {
|
||||
return fmt.Errorf("create account input account: %w", err)
|
||||
}
|
||||
if err := input.Reservation.Validate(); err != nil {
|
||||
return fmt.Errorf("create account input reservation: %w", err)
|
||||
}
|
||||
if input.Account.UserID != input.Reservation.UserID {
|
||||
return fmt.Errorf("create account input reservation user id must match account user id")
|
||||
}
|
||||
if input.Account.RaceName != input.Reservation.RaceName {
|
||||
return fmt.Errorf("create account input reservation race name must match account race name")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenameRaceNameInput stores the atomic state required to replace one stored
|
||||
// race name and its canonical reservation.
|
||||
type RenameRaceNameInput struct {
|
||||
// UserID identifies the account that must be updated.
|
||||
UserID common.UserID
|
||||
|
||||
// CurrentCanonicalKey stores the currently owned canonical reservation key.
|
||||
CurrentCanonicalKey account.RaceNameCanonicalKey
|
||||
|
||||
// NewRaceName stores the replacement exact stored race name.
|
||||
NewRaceName common.RaceName
|
||||
|
||||
// NewReservation stores the replacement canonical reservation.
|
||||
NewReservation account.RaceNameReservation
|
||||
|
||||
// UpdatedAt stores the account mutation timestamp.
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// Validate reports whether RenameRaceNameInput is structurally complete.
|
||||
func (input RenameRaceNameInput) Validate() error {
|
||||
if err := input.UserID.Validate(); err != nil {
|
||||
return fmt.Errorf("rename race name input user id: %w", err)
|
||||
}
|
||||
if err := input.CurrentCanonicalKey.Validate(); err != nil {
|
||||
return fmt.Errorf("rename race name input current canonical key: %w", err)
|
||||
}
|
||||
if err := input.NewRaceName.Validate(); err != nil {
|
||||
return fmt.Errorf("rename race name input race name: %w", err)
|
||||
}
|
||||
if err := input.NewReservation.Validate(); err != nil {
|
||||
return fmt.Errorf("rename race name input reservation: %w", err)
|
||||
}
|
||||
if err := common.ValidateTimestamp("rename race name input updated at", input.UpdatedAt); err != nil {
|
||||
return err
|
||||
}
|
||||
if input.NewReservation.UserID != input.UserID {
|
||||
return fmt.Errorf("rename race name input reservation user id must match user id")
|
||||
}
|
||||
if input.NewReservation.RaceName != input.NewRaceName {
|
||||
return fmt.Errorf("rename race name input reservation race name must match new race name")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserAccountStore persists source-of-truth user-account records and their
|
||||
// exact lookup mappings.
|
||||
type UserAccountStore interface {
|
||||
// Create stores one new account record. Implementations must wrap
|
||||
// ErrConflict when the user id, e-mail, or exact race-name lookup already
|
||||
// exists.
|
||||
Create(ctx context.Context, input CreateAccountInput) error
|
||||
|
||||
// GetByUserID returns the stored account identified by userID.
|
||||
GetByUserID(ctx context.Context, userID common.UserID) (account.UserAccount, error)
|
||||
|
||||
// GetByEmail returns the stored account identified by the normalized e-mail
|
||||
// address.
|
||||
GetByEmail(ctx context.Context, email common.Email) (account.UserAccount, error)
|
||||
|
||||
// GetByRaceName returns the stored account identified by the exact stored
|
||||
// race name.
|
||||
GetByRaceName(ctx context.Context, raceName common.RaceName) (account.UserAccount, error)
|
||||
|
||||
// ExistsByUserID reports whether userID currently identifies a stored
|
||||
// account.
|
||||
ExistsByUserID(ctx context.Context, userID common.UserID) (bool, error)
|
||||
|
||||
// RenameRaceName replaces the stored race name of userID and swaps the
|
||||
// exact race-name lookup atomically. Implementations must wrap ErrConflict
|
||||
// when newRaceName is already owned by another account.
|
||||
RenameRaceName(ctx context.Context, input RenameRaceNameInput) error
|
||||
|
||||
// Update replaces the stored account state for record.UserID.
|
||||
Update(ctx context.Context, record account.UserAccount) error
|
||||
}
|
||||
|
||||
// RaceNameReservationStore persists source-of-truth race-name reservations.
|
||||
type RaceNameReservationStore interface {
|
||||
// Create stores one new race-name reservation keyed by its canonical
|
||||
// uniqueness key. Implementations must wrap ErrConflict when the canonical
|
||||
// key is already reserved.
|
||||
Create(ctx context.Context, record account.RaceNameReservation) error
|
||||
|
||||
// GetByCanonicalKey returns the stored reservation identified by key.
|
||||
GetByCanonicalKey(ctx context.Context, key account.RaceNameCanonicalKey) (account.RaceNameReservation, error)
|
||||
|
||||
// DeleteByCanonicalKey removes the reservation identified by key.
|
||||
DeleteByCanonicalKey(ctx context.Context, key account.RaceNameCanonicalKey) error
|
||||
}
|
||||
Reference in New Issue
Block a user