'datetime', 'used_at' => 'datetime', 'is_active' => 'boolean', 'metadata' => 'array', ]; /** * @var array */ protected $dates = [ 'expires_at', 'used_at', 'created_at', 'updated_at', ]; /** * Get the user who created this invitation */ public function invitedBy(): BelongsTo { return $this->belongsTo(User::class, 'invited_by'); } /** * Get the user who used this invitation */ public function usedBy(): BelongsTo { return $this->belongsTo(User::class, 'used_by'); } /** * Scope to get only active invitations */ public function scopeActive(Builder $query): Builder { return $query->where('is_active', true); } /** * Scope to get valid (active and not expired) invitations */ public function scopeValid(Builder $query): Builder { return $query->active() ->where('expires_at', '>', now()) ->whereNull('used_at'); } /** * Scope to get expired invitations */ public function scopeExpired(Builder $query): Builder { return $query->where('expires_at', '<=', now()); } /** * Scope to get unused invitations */ public function scopeUnused(Builder $query): Builder { return $query->whereNull('used_at'); } /** * Scope to get used invitations */ public function scopeUsed(Builder $query): Builder { return $query->whereNotNull('used_at'); } /** * Check if the invitation is valid */ public function isValid(): bool { return $this->is_active && $this->expires_at->isFuture() && is_null($this->used_at); } /** * Check if the invitation is expired */ public function isExpired(): bool { return $this->expires_at->isPast(); } /** * Check if the invitation has been used */ public function isUsed(): bool { return ! is_null($this->used_at); } /** * Mark the invitation as used */ public function markAsUsed(int $userId): bool { $this->used_at = now(); $this->used_by = $userId; $this->is_active = false; return $this->save(); } /** * Mark the invitation as expired */ public function markAsExpired(): bool { $this->is_active = false; return $this->save(); } /** * Create a new invitation */ public static function createInvitation( string $email, int $invitedBy, int $expiryDays = self::DEFAULT_INVITE_EXPIRY_DAYS, array $metadata = [] ): self { return self::create([ 'token' => Str::random(64), 'email' => strtolower(trim($email)), 'invited_by' => $invitedBy, 'expires_at' => now()->addDays($expiryDays), 'metadata' => $metadata, ]); } /** * Find invitation by token */ public static function findByToken(string $token): ?self { return self::where('token', $token)->first(); } /** * Find valid invitation by token */ public static function findValidByToken(string $token): ?self { return self::valid()->where('token', $token)->first(); } /** * Find invitation by email */ public static function findByEmail(string $email): ?self { return self::where('email', strtolower(trim($email)))->first(); } /** * Get invitation by token (legacy compatibility) */ public static function getInvite(string $token): ?self { return self::findValidByToken($token); } /** * Delete invitation by token (legacy compatibility) */ public static function deleteInvite(string $token): bool { $invitation = self::findByToken($token); if ($invitation) { return $invitation->delete(); } return false; } /** * Add invite (legacy compatibility) */ public static function addInvite(int $userId, string $token): self { // For legacy compatibility, we create an invitation with a specific token // In practice, this should use createInvitation method instead return self::create([ 'token' => $token, 'email' => '', // Legacy method doesn't specify email 'invited_by' => $userId, 'expires_at' => now()->addDays(self::DEFAULT_INVITE_EXPIRY_DAYS), ]); } /** * Clean up expired invitations */ public static function cleanupExpired(): int { $expiredCount = self::expired()->active()->count(); self::expired()->active()->update(['is_active' => false]); return $expiredCount; } /** * Get invitation statistics */ public static function getStats(): array { return [ 'total' => self::count(), 'active' => self::active()->count(), 'valid' => self::valid()->count(), 'expired' => self::expired()->count(), 'used' => self::used()->count(), 'unused' => self::unused()->count(), ]; } }