mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 22:01:33 +00:00
354 lines
11 KiB
PHP
354 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\RegistrationPeriod;
|
|
use App\Models\RegistrationStatusHistory;
|
|
use App\Models\Settings;
|
|
use App\Models\User;
|
|
use Carbon\CarbonInterface;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
class RegistrationStatusService
|
|
{
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
public function statusOptions(): array
|
|
{
|
|
return [
|
|
Settings::REGISTER_STATUS_OPEN => 'Open',
|
|
Settings::REGISTER_STATUS_INVITE => 'Invite',
|
|
Settings::REGISTER_STATUS_CLOSED => 'Closed',
|
|
];
|
|
}
|
|
|
|
public function statusLabel(int $status): string
|
|
{
|
|
return $this->statusOptions()[$status] ?? 'Unknown';
|
|
}
|
|
|
|
public function getManualStatus(): int
|
|
{
|
|
$manualStatus = (int) (Settings::settingValue('registerstatus') ?? Settings::REGISTER_STATUS_OPEN);
|
|
|
|
if (! array_key_exists($manualStatus, $this->statusOptions())) {
|
|
return Settings::REGISTER_STATUS_OPEN;
|
|
}
|
|
|
|
return $manualStatus;
|
|
}
|
|
|
|
public function getActivePeriod(?CarbonInterface $at = null): ?RegistrationPeriod
|
|
{
|
|
$at ??= now();
|
|
|
|
return RegistrationPeriod::query()
|
|
->activeAt($at)
|
|
->orderBy('starts_at')
|
|
->first();
|
|
}
|
|
|
|
public function getNextUpcomingPeriod(?CarbonInterface $at = null): ?RegistrationPeriod
|
|
{
|
|
$at ??= now();
|
|
|
|
return RegistrationPeriod::query()
|
|
->enabled()
|
|
->upcoming($at)
|
|
->orderBy('starts_at')
|
|
->first();
|
|
}
|
|
|
|
/**
|
|
* @return array{
|
|
* manual_status: int,
|
|
* manual_status_label: string,
|
|
* effective_status: int,
|
|
* effective_status_label: string,
|
|
* active_period: RegistrationPeriod|null,
|
|
* scheduled_override_active: bool,
|
|
* reason: string,
|
|
* message: string,
|
|
* available: bool,
|
|
* is_open: bool,
|
|
* is_invite_only: bool,
|
|
* is_closed: bool
|
|
* }
|
|
*/
|
|
public function resolve(?CarbonInterface $at = null): array
|
|
{
|
|
$at ??= now();
|
|
|
|
$manualStatus = $this->getManualStatus();
|
|
$activePeriod = $this->getActivePeriod($at);
|
|
$scheduledOverrideActive = $activePeriod !== null && $manualStatus !== Settings::REGISTER_STATUS_OPEN;
|
|
$effectiveStatus = $activePeriod !== null ? Settings::REGISTER_STATUS_OPEN : $manualStatus;
|
|
$reason = match (true) {
|
|
$scheduledOverrideActive => 'scheduled_open_period',
|
|
$effectiveStatus === Settings::REGISTER_STATUS_OPEN => 'manual_open',
|
|
$effectiveStatus === Settings::REGISTER_STATUS_INVITE => 'manual_invite',
|
|
default => 'manual_closed',
|
|
};
|
|
|
|
return [
|
|
'manual_status' => $manualStatus,
|
|
'manual_status_label' => $this->statusLabel($manualStatus),
|
|
'effective_status' => $effectiveStatus,
|
|
'effective_status_label' => $this->statusLabel($effectiveStatus),
|
|
'active_period' => $activePeriod,
|
|
'scheduled_override_active' => $scheduledOverrideActive,
|
|
'reason' => $reason,
|
|
'message' => $this->buildMessage($effectiveStatus, $activePeriod, $scheduledOverrideActive),
|
|
'available' => $effectiveStatus !== Settings::REGISTER_STATUS_CLOSED,
|
|
'is_open' => $effectiveStatus === Settings::REGISTER_STATUS_OPEN,
|
|
'is_invite_only' => $effectiveStatus === Settings::REGISTER_STATUS_INVITE,
|
|
'is_closed' => $effectiveStatus === Settings::REGISTER_STATUS_CLOSED,
|
|
];
|
|
}
|
|
|
|
public function updateManualStatus(int $newStatus, ?User $changedBy = null, ?string $note = null): void
|
|
{
|
|
$oldStatus = $this->getManualStatus();
|
|
|
|
Settings::settingsUpdate([
|
|
'registerstatus' => $newStatus,
|
|
]);
|
|
|
|
$this->forgetDashboardSnapshot();
|
|
|
|
if ($oldStatus === $newStatus && blank($note)) {
|
|
return;
|
|
}
|
|
|
|
$description = $oldStatus === $newStatus
|
|
? sprintf('Manual registration status note added while status remained %s.', $this->statusLabel($newStatus))
|
|
: sprintf(
|
|
'Manual registration status changed from %s to %s.',
|
|
$this->statusLabel($oldStatus),
|
|
$this->statusLabel($newStatus)
|
|
);
|
|
|
|
RegistrationStatusHistory::record(
|
|
RegistrationStatusHistory::ACTION_MANUAL_STATUS_CHANGED,
|
|
$description,
|
|
$changedBy?->id,
|
|
$oldStatus,
|
|
$newStatus,
|
|
null,
|
|
[
|
|
'note' => $note,
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array{name: string, starts_at: mixed, ends_at: mixed, is_enabled: bool, notes?: string|null} $data
|
|
*/
|
|
public function createPeriod(array $data, ?User $changedBy = null): RegistrationPeriod
|
|
{
|
|
$period = RegistrationPeriod::query()->create([
|
|
'name' => $data['name'],
|
|
'starts_at' => $data['starts_at'],
|
|
'ends_at' => $data['ends_at'],
|
|
'is_enabled' => $data['is_enabled'],
|
|
'notes' => $data['notes'] ?? null,
|
|
'created_by' => $changedBy?->id,
|
|
'updated_by' => $changedBy?->id,
|
|
]);
|
|
|
|
RegistrationStatusHistory::record(
|
|
RegistrationStatusHistory::ACTION_PERIOD_CREATED,
|
|
sprintf('Scheduled open period "%s" created.', $period->name),
|
|
$changedBy?->id,
|
|
null,
|
|
null,
|
|
$period->id,
|
|
[
|
|
'period' => $this->serializePeriod($period),
|
|
]
|
|
);
|
|
|
|
$this->forgetDashboardSnapshot();
|
|
|
|
return $period;
|
|
}
|
|
|
|
/**
|
|
* @param array{name: string, starts_at: mixed, ends_at: mixed, is_enabled: bool, notes?: string|null} $data
|
|
*/
|
|
public function updatePeriod(RegistrationPeriod $period, array $data, ?User $changedBy = null): RegistrationPeriod
|
|
{
|
|
$before = $this->serializePeriod($period);
|
|
|
|
$period->fill([
|
|
'name' => $data['name'],
|
|
'starts_at' => $data['starts_at'],
|
|
'ends_at' => $data['ends_at'],
|
|
'is_enabled' => $data['is_enabled'],
|
|
'notes' => $data['notes'] ?? null,
|
|
'updated_by' => $changedBy?->id,
|
|
]);
|
|
$period->save();
|
|
|
|
RegistrationStatusHistory::record(
|
|
RegistrationStatusHistory::ACTION_PERIOD_UPDATED,
|
|
sprintf('Scheduled open period "%s" updated.', $period->name),
|
|
$changedBy?->id,
|
|
null,
|
|
null,
|
|
$period->id,
|
|
[
|
|
'before' => $before,
|
|
'after' => $this->serializePeriod($period),
|
|
]
|
|
);
|
|
|
|
$this->forgetDashboardSnapshot();
|
|
|
|
return $period;
|
|
}
|
|
|
|
public function togglePeriod(RegistrationPeriod $period, ?User $changedBy = null, ?string $note = null): RegistrationPeriod
|
|
{
|
|
$period->forceFill([
|
|
'is_enabled' => ! $period->is_enabled,
|
|
'updated_by' => $changedBy?->id,
|
|
])->save();
|
|
|
|
RegistrationStatusHistory::record(
|
|
RegistrationStatusHistory::ACTION_PERIOD_TOGGLED,
|
|
sprintf(
|
|
'Scheduled open period "%s" %s.',
|
|
$period->name,
|
|
$period->is_enabled ? 'enabled' : 'disabled'
|
|
),
|
|
$changedBy?->id,
|
|
null,
|
|
null,
|
|
$period->id,
|
|
[
|
|
'note' => $note,
|
|
'period' => $this->serializePeriod($period),
|
|
]
|
|
);
|
|
|
|
$this->forgetDashboardSnapshot();
|
|
|
|
return $period;
|
|
}
|
|
|
|
/**
|
|
* @return Collection<int, RegistrationPeriod>
|
|
*/
|
|
public function disableExpiredPeriods(?CarbonInterface $at = null): Collection
|
|
{
|
|
$at ??= now();
|
|
|
|
$expiredPeriods = RegistrationPeriod::query()
|
|
->where('is_enabled', true)
|
|
->where('ends_at', '<', $at)
|
|
->get();
|
|
|
|
foreach ($expiredPeriods as $period) {
|
|
$period->forceFill([
|
|
'is_enabled' => false,
|
|
'updated_by' => null,
|
|
])->save();
|
|
|
|
RegistrationStatusHistory::record(
|
|
RegistrationStatusHistory::ACTION_PERIOD_COMPLETED,
|
|
sprintf('Scheduled open period "%s" completed after reaching its end time.', $period->name),
|
|
null,
|
|
null,
|
|
null,
|
|
$period->id,
|
|
[
|
|
'completed_at' => $at->toDateTimeString(),
|
|
'period' => $this->serializePeriod($period),
|
|
]
|
|
);
|
|
}
|
|
|
|
if ($expiredPeriods->isNotEmpty()) {
|
|
$this->forgetDashboardSnapshot();
|
|
}
|
|
|
|
return $expiredPeriods;
|
|
}
|
|
|
|
public function deletePeriod(RegistrationPeriod $period, ?User $changedBy = null, ?string $note = null): void
|
|
{
|
|
$snapshot = $this->serializePeriod($period);
|
|
$periodName = $period->name;
|
|
|
|
$period->delete();
|
|
|
|
RegistrationStatusHistory::record(
|
|
RegistrationStatusHistory::ACTION_PERIOD_DELETED,
|
|
sprintf('Scheduled open period "%s" deleted.', $periodName),
|
|
$changedBy?->id,
|
|
null,
|
|
null,
|
|
null,
|
|
[
|
|
'note' => $note,
|
|
'period' => $snapshot,
|
|
]
|
|
);
|
|
|
|
$this->forgetDashboardSnapshot();
|
|
}
|
|
|
|
private function buildMessage(
|
|
int $effectiveStatus,
|
|
?RegistrationPeriod $activePeriod,
|
|
bool $scheduledOverrideActive
|
|
): string {
|
|
if ($scheduledOverrideActive && $activePeriod !== null) {
|
|
return sprintf(
|
|
'Registrations are temporarily open until %s because of the scheduled period "%s".',
|
|
$activePeriod->ends_at->format('Y-m-d H:i'),
|
|
$activePeriod->name
|
|
);
|
|
}
|
|
|
|
return match ($effectiveStatus) {
|
|
Settings::REGISTER_STATUS_OPEN => 'Registrations are currently open.',
|
|
Settings::REGISTER_STATUS_INVITE => 'Registrations are currently invite only.',
|
|
default => 'Registrations are currently closed.',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function serializePeriod(RegistrationPeriod $period): array
|
|
{
|
|
return [
|
|
'id' => $period->id,
|
|
'name' => $period->name,
|
|
'starts_at' => $period->starts_at?->toDateTimeString(),
|
|
'ends_at' => $period->ends_at?->toDateTimeString(),
|
|
'is_enabled' => $period->is_enabled,
|
|
'notes' => $period->notes,
|
|
'created_by' => $period->created_by,
|
|
'updated_by' => $period->updated_by,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Drop the cached admin dashboard snapshot so the per-minute warmer (or
|
|
* the next admin request) rebuilds with fresh registration data. Avoids
|
|
* an inline rebuild here to keep write-path latency low.
|
|
*/
|
|
private function forgetDashboardSnapshot(): void
|
|
{
|
|
Cache::forget(AdminDashboardSnapshotService::CACHE_KEY);
|
|
Cache::forget('api_registration_status');
|
|
}
|
|
}
|