You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
846 lines
21 KiB
Go
846 lines
21 KiB
Go
package user
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"knowfoolery/backend/shared/types"
|
|
"knowfoolery/backend/shared/errors"
|
|
"knowfoolery/backend/services/user-service/internal/domain/valueobjects"
|
|
)
|
|
|
|
// User represents a user aggregate
|
|
type User struct {
|
|
// Identity
|
|
id types.UserID
|
|
email string
|
|
|
|
// Profile information
|
|
profile *valueobjects.UserProfile
|
|
playerName *valueobjects.PlayerName
|
|
preferences *valueobjects.UserPreferences
|
|
|
|
// Authentication data
|
|
authProvider string // "zitadel", "google", "facebook", etc.
|
|
externalID string // ID from external auth provider
|
|
emailVerified bool
|
|
|
|
// Account status
|
|
status types.UserStatus
|
|
isActive bool
|
|
isBanned bool
|
|
banReason *string
|
|
bannedUntil *types.Timestamp
|
|
|
|
// Statistics
|
|
statistics *valueobjects.UserStatistics
|
|
|
|
// Lifecycle
|
|
createdAt types.Timestamp
|
|
updatedAt types.Timestamp
|
|
lastLoginAt *types.Timestamp
|
|
|
|
// Security
|
|
roles []types.UserRole
|
|
permissions []string
|
|
|
|
// Privacy settings
|
|
privacySettings *valueobjects.PrivacySettings
|
|
|
|
// Version control
|
|
version int
|
|
}
|
|
|
|
// NewUser creates a new user
|
|
func NewUser(
|
|
email string,
|
|
authProvider string,
|
|
externalID string,
|
|
) (*User, error) {
|
|
// Validate inputs
|
|
if err := validateEmail(email); err != nil {
|
|
return nil, fmt.Errorf("invalid email: %w", err)
|
|
}
|
|
|
|
if authProvider == "" {
|
|
return nil, errors.ErrValidationFailed("auth_provider", "auth provider cannot be empty")
|
|
}
|
|
|
|
if externalID == "" {
|
|
return nil, errors.ErrValidationFailed("external_id", "external ID cannot be empty")
|
|
}
|
|
|
|
now := types.NewTimestamp()
|
|
|
|
// Create default profile
|
|
profile, err := valueobjects.NewUserProfile("", "", nil, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create default profile: %w", err)
|
|
}
|
|
|
|
// Create default preferences
|
|
preferences := valueobjects.NewDefaultUserPreferences()
|
|
|
|
// Create default privacy settings
|
|
privacySettings := valueobjects.NewDefaultPrivacySettings()
|
|
|
|
// Create default statistics
|
|
statistics := valueobjects.NewUserStatistics()
|
|
|
|
user := &User{
|
|
id: types.NewUserID(),
|
|
email: strings.ToLower(strings.TrimSpace(email)),
|
|
profile: profile,
|
|
playerName: nil, // Will be set later
|
|
preferences: preferences,
|
|
authProvider: authProvider,
|
|
externalID: externalID,
|
|
emailVerified: false,
|
|
status: types.UserStatusActive,
|
|
isActive: true,
|
|
isBanned: false,
|
|
banReason: nil,
|
|
bannedUntil: nil,
|
|
statistics: statistics,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
lastLoginAt: nil,
|
|
roles: []types.UserRole{types.UserRolePlayer}, // Default role
|
|
permissions: []string{},
|
|
privacySettings: privacySettings,
|
|
version: 1,
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
// NewUserWithID creates a user with a specific ID (for loading from persistence)
|
|
func NewUserWithID(
|
|
id types.UserID,
|
|
email string,
|
|
authProvider string,
|
|
externalID string,
|
|
createdAt types.Timestamp,
|
|
) (*User, error) {
|
|
if id.IsEmpty() {
|
|
return nil, errors.ErrValidationFailed("id", "user ID cannot be empty")
|
|
}
|
|
|
|
user, err := NewUser(email, authProvider, externalID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
user.id = id
|
|
user.createdAt = createdAt
|
|
user.updatedAt = createdAt
|
|
|
|
return user, nil
|
|
}
|
|
|
|
// Getters
|
|
|
|
// ID returns the user's unique identifier
|
|
func (u *User) ID() types.UserID {
|
|
return u.id
|
|
}
|
|
|
|
// Email returns the user's email address
|
|
func (u *User) Email() string {
|
|
return u.email
|
|
}
|
|
|
|
// Profile returns the user's profile
|
|
func (u *User) Profile() *valueobjects.UserProfile {
|
|
return u.profile
|
|
}
|
|
|
|
// PlayerName returns the user's player name
|
|
func (u *User) PlayerName() *valueobjects.PlayerName {
|
|
return u.playerName
|
|
}
|
|
|
|
// Preferences returns the user's preferences
|
|
func (u *User) Preferences() *valueobjects.UserPreferences {
|
|
return u.preferences
|
|
}
|
|
|
|
// AuthProvider returns the authentication provider
|
|
func (u *User) AuthProvider() string {
|
|
return u.authProvider
|
|
}
|
|
|
|
// ExternalID returns the external ID from auth provider
|
|
func (u *User) ExternalID() string {
|
|
return u.externalID
|
|
}
|
|
|
|
// IsEmailVerified returns true if email is verified
|
|
func (u *User) IsEmailVerified() bool {
|
|
return u.emailVerified
|
|
}
|
|
|
|
// Status returns the user status
|
|
func (u *User) Status() types.UserStatus {
|
|
return u.status
|
|
}
|
|
|
|
// IsActive returns true if the user is active
|
|
func (u *User) IsActive() bool {
|
|
return u.isActive && u.status == types.UserStatusActive && !u.isBanned
|
|
}
|
|
|
|
// IsBanned returns true if the user is banned
|
|
func (u *User) IsBanned() bool {
|
|
return u.isBanned
|
|
}
|
|
|
|
// BanReason returns the ban reason if banned
|
|
func (u *User) BanReason() *string {
|
|
return u.banReason
|
|
}
|
|
|
|
// BannedUntil returns when the ban expires
|
|
func (u *User) BannedUntil() *types.Timestamp {
|
|
return u.bannedUntil
|
|
}
|
|
|
|
// Statistics returns the user's statistics
|
|
func (u *User) Statistics() *valueobjects.UserStatistics {
|
|
return u.statistics
|
|
}
|
|
|
|
// CreatedAt returns the creation timestamp
|
|
func (u *User) CreatedAt() types.Timestamp {
|
|
return u.createdAt
|
|
}
|
|
|
|
// UpdatedAt returns the last update timestamp
|
|
func (u *User) UpdatedAt() types.Timestamp {
|
|
return u.updatedAt
|
|
}
|
|
|
|
// LastLoginAt returns the last login timestamp
|
|
func (u *User) LastLoginAt() *types.Timestamp {
|
|
return u.lastLoginAt
|
|
}
|
|
|
|
// Roles returns the user's roles
|
|
func (u *User) Roles() []types.UserRole {
|
|
return append([]types.UserRole(nil), u.roles...) // Return copy
|
|
}
|
|
|
|
// Permissions returns the user's permissions
|
|
func (u *User) Permissions() []string {
|
|
return append([]string(nil), u.permissions...) // Return copy
|
|
}
|
|
|
|
// PrivacySettings returns the user's privacy settings
|
|
func (u *User) PrivacySettings() *valueobjects.PrivacySettings {
|
|
return u.privacySettings
|
|
}
|
|
|
|
// Version returns the current version
|
|
func (u *User) Version() int {
|
|
return u.version
|
|
}
|
|
|
|
// Business methods
|
|
|
|
// UpdateProfile updates the user's profile
|
|
func (u *User) UpdateProfile(
|
|
firstName string,
|
|
lastName string,
|
|
dateOfBirth *types.Date,
|
|
avatar *string,
|
|
) error {
|
|
newProfile, err := valueobjects.NewUserProfile(firstName, lastName, dateOfBirth, avatar)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update profile: %w", err)
|
|
}
|
|
|
|
u.profile = newProfile
|
|
u.markUpdated()
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetPlayerName sets the user's player name
|
|
func (u *User) SetPlayerName(playerName string) error {
|
|
if playerName == "" {
|
|
u.playerName = nil
|
|
u.markUpdated()
|
|
return nil
|
|
}
|
|
|
|
newPlayerName, err := valueobjects.NewPlayerName(playerName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set player name: %w", err)
|
|
}
|
|
|
|
u.playerName = newPlayerName
|
|
u.markUpdated()
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateEmail updates the user's email address
|
|
func (u *User) UpdateEmail(newEmail string) error {
|
|
if err := validateEmail(newEmail); err != nil {
|
|
return fmt.Errorf("invalid email: %w", err)
|
|
}
|
|
|
|
normalizedEmail := strings.ToLower(strings.TrimSpace(newEmail))
|
|
if normalizedEmail == u.email {
|
|
return nil // No change
|
|
}
|
|
|
|
u.email = normalizedEmail
|
|
u.emailVerified = false // Reset verification when email changes
|
|
u.markUpdated()
|
|
|
|
return nil
|
|
}
|
|
|
|
// VerifyEmail marks the email as verified
|
|
func (u *User) VerifyEmail() {
|
|
if !u.emailVerified {
|
|
u.emailVerified = true
|
|
u.markUpdated()
|
|
}
|
|
}
|
|
|
|
// UpdatePreferences updates the user's preferences
|
|
func (u *User) UpdatePreferences(preferences *valueobjects.UserPreferences) error {
|
|
if preferences == nil {
|
|
return errors.ErrValidationFailed("preferences", "preferences cannot be nil")
|
|
}
|
|
|
|
if err := preferences.Validate(); err != nil {
|
|
return fmt.Errorf("invalid preferences: %w", err)
|
|
}
|
|
|
|
u.preferences = preferences
|
|
u.markUpdated()
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdatePrivacySettings updates the user's privacy settings
|
|
func (u *User) UpdatePrivacySettings(settings *valueobjects.PrivacySettings) error {
|
|
if settings == nil {
|
|
return errors.ErrValidationFailed("privacy_settings", "privacy settings cannot be nil")
|
|
}
|
|
|
|
if err := settings.Validate(); err != nil {
|
|
return fmt.Errorf("invalid privacy settings: %w", err)
|
|
}
|
|
|
|
u.privacySettings = settings
|
|
u.markUpdated()
|
|
|
|
return nil
|
|
}
|
|
|
|
// RecordLogin records a successful login
|
|
func (u *User) RecordLogin() error {
|
|
if !u.IsActive() {
|
|
return errors.ErrOperationNotAllowed("record login", "user is not active")
|
|
}
|
|
|
|
now := types.NewTimestamp()
|
|
u.lastLoginAt = &now
|
|
|
|
// Update statistics
|
|
u.statistics.RecordLogin()
|
|
|
|
u.markUpdated()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Ban bans the user
|
|
func (u *User) Ban(reason string, bannedBy types.UserID, duration *types.Duration) error {
|
|
if u.isBanned {
|
|
return errors.ErrOperationNotAllowed("ban user", "user is already banned")
|
|
}
|
|
|
|
if reason == "" {
|
|
return errors.ErrValidationFailed("reason", "ban reason cannot be empty")
|
|
}
|
|
|
|
if bannedBy.IsEmpty() {
|
|
return errors.ErrValidationFailed("banned_by", "banned by user ID cannot be empty")
|
|
}
|
|
|
|
u.isBanned = true
|
|
u.banReason = &reason
|
|
|
|
// Set ban expiration if duration provided
|
|
if duration != nil {
|
|
bannedUntil := types.NewTimestamp().Add(*duration)
|
|
u.bannedUntil = &bannedUntil
|
|
}
|
|
|
|
u.status = types.UserStatusBanned
|
|
u.markUpdated()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Unban removes the ban from the user
|
|
func (u *User) Unban() error {
|
|
if !u.isBanned {
|
|
return errors.ErrOperationNotAllowed("unban user", "user is not banned")
|
|
}
|
|
|
|
u.isBanned = false
|
|
u.banReason = nil
|
|
u.bannedUntil = nil
|
|
u.status = types.UserStatusActive
|
|
u.markUpdated()
|
|
|
|
return nil
|
|
}
|
|
|
|
// CheckBanExpiration checks if a temporary ban has expired
|
|
func (u *User) CheckBanExpiration() bool {
|
|
if !u.isBanned || u.bannedUntil == nil {
|
|
return false
|
|
}
|
|
|
|
now := types.NewTimestamp()
|
|
if now.After(*u.bannedUntil) {
|
|
// Ban has expired, unban the user
|
|
u.Unban()
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Deactivate deactivates the user account
|
|
func (u *User) Deactivate() error {
|
|
if !u.isActive {
|
|
return errors.ErrOperationNotAllowed("deactivate user", "user is already inactive")
|
|
}
|
|
|
|
u.isActive = false
|
|
u.status = types.UserStatusInactive
|
|
u.markUpdated()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Reactivate reactivates the user account
|
|
func (u *User) Reactivate() error {
|
|
if u.isActive {
|
|
return errors.ErrOperationNotAllowed("reactivate user", "user is already active")
|
|
}
|
|
|
|
if u.isBanned {
|
|
return errors.ErrOperationNotAllowed("reactivate user", "cannot reactivate banned user")
|
|
}
|
|
|
|
u.isActive = true
|
|
u.status = types.UserStatusActive
|
|
u.markUpdated()
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddRole adds a role to the user
|
|
func (u *User) AddRole(role types.UserRole) error {
|
|
// Check if role already exists
|
|
for _, existingRole := range u.roles {
|
|
if existingRole == role {
|
|
return nil // Already has role
|
|
}
|
|
}
|
|
|
|
u.roles = append(u.roles, role)
|
|
u.markUpdated()
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveRole removes a role from the user
|
|
func (u *User) RemoveRole(role types.UserRole) error {
|
|
// Don't allow removal of Player role (everyone must have it)
|
|
if role == types.UserRolePlayer {
|
|
return errors.ErrOperationNotAllowed("remove role", "cannot remove Player role")
|
|
}
|
|
|
|
for i, existingRole := range u.roles {
|
|
if existingRole == role {
|
|
u.roles = append(u.roles[:i], u.roles[i+1:]...)
|
|
u.markUpdated()
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return errors.ErrNotFound("role", string(role))
|
|
}
|
|
|
|
// AddPermission adds a permission to the user
|
|
func (u *User) AddPermission(permission string) error {
|
|
permission = strings.TrimSpace(permission)
|
|
if permission == "" {
|
|
return errors.ErrValidationFailed("permission", "permission cannot be empty")
|
|
}
|
|
|
|
// Check if permission already exists
|
|
for _, existingPermission := range u.permissions {
|
|
if existingPermission == permission {
|
|
return nil // Already has permission
|
|
}
|
|
}
|
|
|
|
u.permissions = append(u.permissions, permission)
|
|
u.markUpdated()
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemovePermission removes a permission from the user
|
|
func (u *User) RemovePermission(permission string) error {
|
|
for i, existingPermission := range u.permissions {
|
|
if existingPermission == permission {
|
|
u.permissions = append(u.permissions[:i], u.permissions[i+1:]...)
|
|
u.markUpdated()
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return errors.ErrNotFound("permission", permission)
|
|
}
|
|
|
|
// UpdateStatistics updates user statistics
|
|
func (u *User) UpdateStatistics(stats *valueobjects.UserStatistics) error {
|
|
if stats == nil {
|
|
return errors.ErrValidationFailed("statistics", "statistics cannot be nil")
|
|
}
|
|
|
|
if err := stats.Validate(); err != nil {
|
|
return fmt.Errorf("invalid statistics: %w", err)
|
|
}
|
|
|
|
u.statistics = stats
|
|
u.markUpdated()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Query methods
|
|
|
|
// HasRole checks if the user has a specific role
|
|
func (u *User) HasRole(role types.UserRole) bool {
|
|
for _, userRole := range u.roles {
|
|
if userRole == role {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// HasPermission checks if the user has a specific permission
|
|
func (u *User) HasPermission(permission string) bool {
|
|
for _, userPermission := range u.permissions {
|
|
if userPermission == permission {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsAdmin returns true if the user is an admin
|
|
func (u *User) IsAdmin() bool {
|
|
return u.HasRole(types.UserRoleAdmin)
|
|
}
|
|
|
|
// IsModerator returns true if the user is a moderator
|
|
func (u *User) IsModerator() bool {
|
|
return u.HasRole(types.UserRoleModerator)
|
|
}
|
|
|
|
// CanPlay returns true if the user can play games
|
|
func (u *User) CanPlay() bool {
|
|
return u.IsActive() && !u.isBanned
|
|
}
|
|
|
|
// GetDisplayName returns the best display name for the user
|
|
func (u *User) GetDisplayName() string {
|
|
// Prefer player name
|
|
if u.playerName != nil && u.playerName.Value() != "" {
|
|
return u.playerName.Value()
|
|
}
|
|
|
|
// Fall back to profile name
|
|
if u.profile != nil {
|
|
fullName := strings.TrimSpace(u.profile.FirstName() + " " + u.profile.LastName())
|
|
if fullName != "" {
|
|
return fullName
|
|
}
|
|
|
|
if u.profile.FirstName() != "" {
|
|
return u.profile.FirstName()
|
|
}
|
|
}
|
|
|
|
// Fall back to email prefix
|
|
parts := strings.Split(u.email, "@")
|
|
if len(parts) > 0 && parts[0] != "" {
|
|
return parts[0]
|
|
}
|
|
|
|
// Last resort
|
|
return "User"
|
|
}
|
|
|
|
// GetAge returns the user's age if date of birth is available
|
|
func (u *User) GetAge() *int {
|
|
if u.profile == nil || u.profile.DateOfBirth() == nil {
|
|
return nil
|
|
}
|
|
|
|
now := time.Now()
|
|
birthDate := time.Time(*u.profile.DateOfBirth())
|
|
|
|
age := now.Year() - birthDate.Year()
|
|
if now.YearDay() < birthDate.YearDay() {
|
|
age--
|
|
}
|
|
|
|
return &age
|
|
}
|
|
|
|
// GetAccountAge returns how long the user has been registered
|
|
func (u *User) GetAccountAge() time.Duration {
|
|
now := types.NewTimestamp()
|
|
return now.Sub(u.createdAt)
|
|
}
|
|
|
|
// IsNewUser returns true if the user registered recently
|
|
func (u *User) IsNewUser() bool {
|
|
age := u.GetAccountAge()
|
|
return age < 7*24*time.Hour // Less than 7 days old
|
|
}
|
|
|
|
// Validation
|
|
|
|
// Validate performs comprehensive validation
|
|
func (u *User) Validate() *types.ValidationResult {
|
|
result := types.NewValidationResult()
|
|
|
|
// Validate ID
|
|
if u.id.IsEmpty() {
|
|
result.AddError("user ID cannot be empty")
|
|
}
|
|
|
|
// Validate email
|
|
if err := validateEmail(u.email); err != nil {
|
|
result.AddErrorf("invalid email: %v", err)
|
|
}
|
|
|
|
// Validate auth provider
|
|
if u.authProvider == "" {
|
|
result.AddError("auth provider cannot be empty")
|
|
}
|
|
|
|
// Validate external ID
|
|
if u.externalID == "" {
|
|
result.AddError("external ID cannot be empty")
|
|
}
|
|
|
|
// Validate profile
|
|
if u.profile != nil {
|
|
if profileResult := u.profile.Validate(); !profileResult.IsValid() {
|
|
for _, error := range profileResult.Errors() {
|
|
result.AddErrorf("profile validation failed: %s", error)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate player name
|
|
if u.playerName != nil {
|
|
if playerNameResult := u.playerName.Validate(); !playerNameResult.IsValid() {
|
|
for _, error := range playerNameResult.Errors() {
|
|
result.AddErrorf("player name validation failed: %s", error)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate preferences
|
|
if u.preferences != nil {
|
|
if prefResult := u.preferences.Validate(); !prefResult.IsValid() {
|
|
for _, error := range prefResult.Errors() {
|
|
result.AddErrorf("preferences validation failed: %s", error)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate privacy settings
|
|
if u.privacySettings != nil {
|
|
if privacyResult := u.privacySettings.Validate(); !privacyResult.IsValid() {
|
|
for _, error := range privacyResult.Errors() {
|
|
result.AddErrorf("privacy settings validation failed: %s", error)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate statistics
|
|
if u.statistics != nil {
|
|
if statsResult := u.statistics.Validate(); !statsResult.IsValid() {
|
|
for _, error := range statsResult.Errors() {
|
|
result.AddErrorf("statistics validation failed: %s", error)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate roles
|
|
if len(u.roles) == 0 {
|
|
result.AddError("user must have at least one role")
|
|
}
|
|
|
|
// Check for Player role
|
|
hasPlayerRole := false
|
|
for _, role := range u.roles {
|
|
if role == types.UserRolePlayer {
|
|
hasPlayerRole = true
|
|
break
|
|
}
|
|
}
|
|
if !hasPlayerRole {
|
|
result.AddError("user must have Player role")
|
|
}
|
|
|
|
// Validate ban logic
|
|
if u.isBanned && u.banReason == nil {
|
|
result.AddError("banned user must have ban reason")
|
|
}
|
|
|
|
if u.banReason != nil && !u.isBanned {
|
|
result.AddError("user has ban reason but is not marked as banned")
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Helper methods
|
|
|
|
// markUpdated updates the timestamp and version
|
|
func (u *User) markUpdated() {
|
|
u.updatedAt = types.NewTimestamp()
|
|
u.version++
|
|
}
|
|
|
|
// ToSnapshot creates a read-only snapshot of the user
|
|
func (u *User) ToSnapshot() *UserSnapshot {
|
|
var playerName *string
|
|
if u.playerName != nil {
|
|
name := u.playerName.Value()
|
|
playerName = &name
|
|
}
|
|
|
|
return &UserSnapshot{
|
|
ID: u.id,
|
|
Email: u.email,
|
|
Profile: u.profile.ToSnapshot(),
|
|
PlayerName: playerName,
|
|
Preferences: u.preferences.ToSnapshot(),
|
|
AuthProvider: u.authProvider,
|
|
ExternalID: u.externalID,
|
|
EmailVerified: u.emailVerified,
|
|
Status: u.status,
|
|
IsActive: u.isActive,
|
|
IsBanned: u.isBanned,
|
|
BanReason: u.banReason,
|
|
BannedUntil: u.bannedUntil,
|
|
Statistics: u.statistics.ToSnapshot(),
|
|
CreatedAt: u.createdAt,
|
|
UpdatedAt: u.updatedAt,
|
|
LastLoginAt: u.lastLoginAt,
|
|
Roles: append([]types.UserRole(nil), u.roles...),
|
|
Permissions: append([]string(nil), u.permissions...),
|
|
PrivacySettings: u.privacySettings.ToSnapshot(),
|
|
Version: u.version,
|
|
}
|
|
}
|
|
|
|
// UserSnapshot represents a read-only view of a user
|
|
type UserSnapshot struct {
|
|
ID types.UserID `json:"id"`
|
|
Email string `json:"email"`
|
|
Profile *valueobjects.UserProfileSnapshot `json:"profile"`
|
|
PlayerName *string `json:"player_name,omitempty"`
|
|
Preferences *valueobjects.UserPreferencesSnapshot `json:"preferences"`
|
|
AuthProvider string `json:"auth_provider"`
|
|
ExternalID string `json:"external_id"`
|
|
EmailVerified bool `json:"email_verified"`
|
|
Status types.UserStatus `json:"status"`
|
|
IsActive bool `json:"is_active"`
|
|
IsBanned bool `json:"is_banned"`
|
|
BanReason *string `json:"ban_reason,omitempty"`
|
|
BannedUntil *types.Timestamp `json:"banned_until,omitempty"`
|
|
Statistics *valueobjects.UserStatisticsSnapshot `json:"statistics"`
|
|
CreatedAt types.Timestamp `json:"created_at"`
|
|
UpdatedAt types.Timestamp `json:"updated_at"`
|
|
LastLoginAt *types.Timestamp `json:"last_login_at,omitempty"`
|
|
Roles []types.UserRole `json:"roles"`
|
|
Permissions []string `json:"permissions"`
|
|
PrivacySettings *valueobjects.PrivacySettingsSnapshot `json:"privacy_settings"`
|
|
Version int `json:"version"`
|
|
}
|
|
|
|
// GetDisplayName returns the display name for the snapshot
|
|
func (us *UserSnapshot) GetDisplayName() string {
|
|
if us.PlayerName != nil && *us.PlayerName != "" {
|
|
return *us.PlayerName
|
|
}
|
|
|
|
if us.Profile != nil {
|
|
fullName := strings.TrimSpace(us.Profile.FirstName + " " + us.Profile.LastName)
|
|
if fullName != "" {
|
|
return fullName
|
|
}
|
|
if us.Profile.FirstName != "" {
|
|
return us.Profile.FirstName
|
|
}
|
|
}
|
|
|
|
parts := strings.Split(us.Email, "@")
|
|
if len(parts) > 0 && parts[0] != "" {
|
|
return parts[0]
|
|
}
|
|
|
|
return "User"
|
|
}
|
|
|
|
// Validation helper functions
|
|
|
|
func validateEmail(email string) error {
|
|
email = strings.TrimSpace(email)
|
|
if email == "" {
|
|
return errors.ErrValidationFailed("email", "email cannot be empty")
|
|
}
|
|
|
|
if len(email) > types.MaxEmailLength {
|
|
return errors.ErrValidationFailed("email", fmt.Sprintf("email too long (max %d characters)", types.MaxEmailLength))
|
|
}
|
|
|
|
// Basic email format validation
|
|
if !strings.Contains(email, "@") {
|
|
return errors.ErrValidationFailed("email", "email must contain @ symbol")
|
|
}
|
|
|
|
parts := strings.Split(email, "@")
|
|
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
|
return errors.ErrValidationFailed("email", "invalid email format")
|
|
}
|
|
|
|
// More comprehensive email validation would go here
|
|
// For production, use a proper email validation library
|
|
|
|
return nil
|
|
} |