'datetime', ]; } /** * A fresh invitation for $email, retiring any other still-pending one * for the same address first — one live token per address at a time, * whether this is staff sending a second invite or the invited person * asking for a new link after the first expired. */ public static function issue(string $email, ?string $name, ?Group $group, ?User $invitedBy, Carbon $expiresAt, int $storageQuotaMb = 0): self { self::query()->pending()->where('email', $email)->update(['status' => self::STATUS_SUPERSEDED]); return self::query()->create([ 'name' => $name, 'email' => $email, 'token' => Str::random(40), 'status' => self::STATUS_PENDING, 'storage_quota_mb' => $storageQuotaMb, 'group_id' => $group?->id, 'invited_by_id' => $invitedBy?->id, 'expires_at' => $expiresAt, ]); } public function isExpired(): bool { return $this->expires_at->isPast(); } /** * @param Builder $query * @return Builder */ public function scopePending(Builder $query): Builder { return $query->where('status', self::STATUS_PENDING); } /** * @return BelongsTo */ public function group(): BelongsTo { return $this->belongsTo(Group::class); } /** * @return BelongsTo */ public function invitedBy(): BelongsTo { return $this->belongsTo(User::class, 'invited_by_id'); } }