mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 17:01:16 +00:00
312 lines
7.7 KiB
PHP
312 lines
7.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Str;
|
|
|
|
/**
|
|
* App\Models\Invitation
|
|
*
|
|
* @property int $id
|
|
* @property string $token
|
|
* @property string $email
|
|
* @property int $invited_by
|
|
* @property Carbon $expires_at
|
|
* @property Carbon|null $used_at
|
|
* @property int|null $used_by
|
|
* @property bool $is_active
|
|
* @property array|null $metadata
|
|
* @property Carbon|null $created_at
|
|
* @property Carbon|null $updated_at
|
|
* @property-read User $invitedBy
|
|
* @property-read User|null $usedBy
|
|
*
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Invitation newModelQuery()
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Invitation newQuery()
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Invitation query()
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Invitation active()
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Invitation valid()
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Invitation expired()
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Invitation unused()
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Invitation used()
|
|
*/
|
|
class Invitation extends Model // @phpstan-ignore missingType.iterableValue
|
|
{
|
|
public const DEFAULT_INVITES = 1;
|
|
|
|
public const DEFAULT_INVITE_EXPIRY_DAYS = 7;
|
|
|
|
/**
|
|
* @var list<string>
|
|
*/
|
|
protected $fillable = [
|
|
'token',
|
|
'email',
|
|
'invited_by',
|
|
'expires_at',
|
|
'used_at',
|
|
'used_by',
|
|
'is_active',
|
|
'metadata',
|
|
];
|
|
|
|
/**
|
|
* @var array<string, string>
|
|
*/
|
|
protected $casts = [
|
|
'expires_at' => 'datetime',
|
|
'used_at' => 'datetime',
|
|
'is_active' => 'boolean',
|
|
'metadata' => 'array',
|
|
];
|
|
|
|
/**
|
|
* @var array<string, mixed>
|
|
*/
|
|
protected $dates = [ // @phpstan-ignore property.defaultValue
|
|
'expires_at',
|
|
'used_at',
|
|
'created_at',
|
|
'updated_at',
|
|
];
|
|
|
|
/**
|
|
* Get the user who created this invitation
|
|
*
|
|
* @return BelongsTo<User, $this>
|
|
*/
|
|
public function invitedBy(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'invited_by');
|
|
}
|
|
|
|
/**
|
|
* Get the user who used this invitation
|
|
*
|
|
* @return BelongsTo<User, $this>
|
|
*/
|
|
public function usedBy(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'used_by');
|
|
}
|
|
|
|
/**
|
|
* Scope to get only active invitations
|
|
*
|
|
* @param Builder<Invitation> $query
|
|
* @return Builder<Invitation>
|
|
*/
|
|
public function scopeActive(Builder $query): Builder
|
|
{
|
|
return $query->where('is_active', true);
|
|
}
|
|
|
|
/**
|
|
* Scope to get valid (active and not expired) invitations
|
|
*
|
|
* @param Builder<Invitation> $query
|
|
* @return Builder<Invitation>
|
|
*/
|
|
public function scopeValid(Builder $query): Builder
|
|
{
|
|
return $query->active()
|
|
->where('expires_at', '>', now())
|
|
->whereNull('used_at');
|
|
}
|
|
|
|
/**
|
|
* Scope to get expired invitations
|
|
*
|
|
* @param Builder<Invitation> $query
|
|
* @return Builder<Invitation>
|
|
*/
|
|
public function scopeExpired(Builder $query): Builder
|
|
{
|
|
return $query->where('expires_at', '<=', now());
|
|
}
|
|
|
|
/**
|
|
* Scope to get unused invitations
|
|
*
|
|
* @param Builder<Invitation> $query
|
|
* @return Builder<Invitation>
|
|
*/
|
|
public function scopeUnused(Builder $query): Builder
|
|
{
|
|
return $query->whereNull('used_at');
|
|
}
|
|
|
|
/**
|
|
* Scope to get used invitations
|
|
*
|
|
* @param Builder<Invitation> $query
|
|
* @return Builder<Invitation>
|
|
*/
|
|
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() &&
|
|
$this->used_at === null;
|
|
}
|
|
|
|
/**
|
|
* 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 $this->used_at !== null;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*
|
|
* @param array<string, mixed> $metadata
|
|
*/
|
|
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
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
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(),
|
|
];
|
|
}
|
|
}
|