*/ protected $fillable = [ 'name', 'email', 'password', 'tier', 'tier_expires_at', 'referred_by', 'referral_count', 'referral_activated_at', ]; /** * The attributes that should be hidden for serialization. * * @var list */ protected $hidden = [ 'password', 'remember_token', ]; /** * Get the attributes that should be cast. * * @return array */ protected function casts(): array { return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', 'tier' => UserTier::class, 'tier_expires_at' => 'datetime', 'cached_stats' => 'array', 'stats_computed_at' => 'datetime', 'referral_activated_at' => 'datetime', ]; } // ───────────────────────────────────────────────────────────────────────── // Workspace Relationships // ───────────────────────────────────────────────────────────────────────── /** * Get all workspaces this user has access to. */ public function workspaces(): BelongsToMany { return $this->belongsToMany(Workspace::class, 'user_workspace') ->withPivot(['role', 'is_default']) ->withTimestamps(); } /** * Alias for workspaces() - kept for backward compatibility. */ public function hostWorkspaces(): BelongsToMany { return $this->workspaces(); } /** * Get the workspaces owned by this user. */ public function ownedWorkspaces(): BelongsToMany { return $this->belongsToMany(Workspace::class, 'user_workspace') ->wherePivot('role', 'owner') ->withPivot(['role', 'is_default']) ->withTimestamps(); } // ───────────────────────────────────────────────────────────────────────── // Tier Helpers // ───────────────────────────────────────────────────────────────────────── /** * Get the user's tier. */ public function getTier(): UserTier { // Check if tier has expired if ($this->tier_expires_at && $this->tier_expires_at->isPast()) { return UserTier::FREE; } return $this->tier ?? UserTier::FREE; } /** * Check if user is on a paid tier. */ public function isPaid(): bool { $tier = $this->getTier(); return $tier === UserTier::APOLLO || $tier === UserTier::HADES; } /** * Check if user is on Hades tier. */ public function isHades(): bool { return $this->getTier() === UserTier::HADES; } /** * Check if user is on Apollo tier. */ public function isApollo(): bool { return $this->getTier() === UserTier::APOLLO; } /** * Check if user has a specific feature. */ public function hasFeature(string $feature): bool { return $this->getTier()->hasFeature($feature); } /** * Get the maximum number of workspaces for this user. */ public function maxWorkspaces(): int { return $this->getTier()->maxWorkspaces(); } /** * Check if user can add more Host Hub workspaces. */ public function canAddHostWorkspace(): bool { $max = $this->maxWorkspaces(); if ($max === -1) { return true; // Unlimited } return $this->hostWorkspaces()->count() < $max; } /** * Get the user's default Host Hub workspace. */ public function defaultHostWorkspace(): ?Workspace { return $this->hostWorkspaces() ->wherePivot('is_default', true) ->first() ?? $this->hostWorkspaces()->first(); } // ───────────────────────────────────────────────────────────────────────── // Namespace Relationships // ───────────────────────────────────────────────────────────────────────── /** * Get all namespaces owned directly by this user. */ public function namespaces(): MorphMany { return $this->morphMany(Namespace_::class, 'owner'); } /** * Get the user's default namespace. * * Priority: * 1. User's default namespace (is_default = true) * 2. First active user-owned namespace * 3. First namespace from user's default workspace */ public function defaultNamespace(): ?Namespace_ { // Try user's explicit default $default = $this->namespaces() ->where('is_default', true) ->active() ->first(); if ($default) { return $default; } // Try first user-owned namespace $userOwned = $this->namespaces() ->active() ->ordered() ->first(); if ($userOwned) { return $userOwned; } // Try namespace from user's default workspace $workspace = $this->defaultHostWorkspace(); if ($workspace) { return $workspace->namespaces() ->active() ->ordered() ->first(); } return null; } /** * Get all namespaces accessible by this user (owned + via workspaces). */ public function accessibleNamespaces(): Builder { return Namespace_::accessibleBy($this); } // ───────────────────────────────────────────────────────────────────────── // Email Verification // ───────────────────────────────────────────────────────────────────────── /** * Check if user's email has been verified. * Hades accounts are always considered verified. */ public function hasVerifiedEmail(): bool { // Hades accounts bypass email verification if ($this->isHades()) { return true; } return $this->email_verified_at !== null; } /** * Mark the user's email as verified. */ public function markEmailAsVerified(): bool { return $this->forceFill([ 'email_verified_at' => $this->freshTimestamp(), ])->save(); } /** * Send the email verification notification. */ public function sendEmailVerificationNotification(): void { $this->notify(new VerifyEmail); } /** * Get the email address that should be used for verification. */ public function getEmailForVerification(): string { return $this->email; } // ───────────────────────────────────────────────────────────────────────── // Entitlement Relationships // ───────────────────────────────────────────────────────────────────────── /** * Get all boosts owned by this user. */ public function boosts(): HasMany { return $this->hasMany(Boost::class); } // ───────────────────────────────────────────────────────────────────────── // Referral Relationships // ───────────────────────────────────────────────────────────────────────── /** * Get the user who referred this user. */ public function referrer(): BelongsTo { return $this->belongsTo(self::class, 'referred_by'); } /** * Get all users referred by this user. */ public function referrals(): HasMany { return $this->hasMany(self::class, 'referred_by'); } /** * Check if user has activated referrals. */ public function hasActivatedReferrals(): bool { return $this->referral_activated_at !== null; } /** * Activate referrals for this user. */ public function activateReferrals(): void { if (! $this->hasActivatedReferrals()) { $this->update(['referral_activated_at' => now()]); } } /** * Get referral ranking (1-based position among all users by referral count). */ public function getReferralRank(): int { if ($this->referral_count === 0) { return 0; // Not ranked if no referrals } return self::where('referral_count', '>', $this->referral_count)->count() + 1; } // ───────────────────────────────────────────────────────────────────────── // Orderable Interface // ───────────────────────────────────────────────────────────────────────── public function getBillingName(): ?string { return $this->name; } public function getBillingEmail(): string { return $this->email; } public function getBillingAddress(): ?array { return null; } public function getTaxCountry(): ?string { return null; } }