Laravel 13 + Filament 5 admin panel and REST API for a non-profit developer collective, backed by the Pireon OAuth identity server. The public site is a separate, server-rendered application consuming /api/public/v1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
139 lines
3.6 KiB
PHP
139 lines
3.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Models\Setting;
|
|
use Carbon\CarbonImmutable;
|
|
|
|
/**
|
|
* Maintenance mode for the **public site only**.
|
|
*
|
|
* Deliberately not Laravel's `artisan down`, which would take this
|
|
* application down too — including the panel. The whole point is that the
|
|
* collective keeps working while visitors see a maintenance page: content can
|
|
* still be edited, repositories still sync, and the team can preview the real
|
|
* site through the usual bypasses.
|
|
*
|
|
* The flag lives in the database rather than in .env so an administrator can
|
|
* flip it from the panel without a deploy.
|
|
*/
|
|
final class Maintenance
|
|
{
|
|
public const KEY = 'site.maintenance';
|
|
|
|
public static function isEnabled(): bool
|
|
{
|
|
$state = self::state();
|
|
|
|
if (! ($state['enabled'] ?? false)) {
|
|
return false;
|
|
}
|
|
|
|
// A window that has already passed lifts itself, so a forgotten flag
|
|
// does not keep the site down indefinitely.
|
|
if ($until = self::until()) {
|
|
return ! $until->isPast();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string>|null $message Locale => message.
|
|
*/
|
|
public static function enable(?array $message = null, ?CarbonImmutable $until = null): void
|
|
{
|
|
Setting::set(self::KEY, [
|
|
'enabled' => true,
|
|
'message' => $message ?? self::state()['message'] ?? null,
|
|
'until' => $until?->toIso8601String(),
|
|
'started_at' => now()->toIso8601String(),
|
|
]);
|
|
}
|
|
|
|
public static function disable(): void
|
|
{
|
|
Setting::set(self::KEY, [
|
|
...self::state(),
|
|
'enabled' => false,
|
|
'until' => null,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* The message shown to visitors, resolved to the request's language and
|
|
* falling back to a translated default when none was written.
|
|
*/
|
|
public static function message(?string $locale = null): string
|
|
{
|
|
$locale ??= app()->getLocale();
|
|
$messages = self::state()['message'] ?? [];
|
|
|
|
if (is_array($messages)) {
|
|
$written = $messages[$locale]
|
|
?? $messages[config('locales.fallback')]
|
|
?? null;
|
|
|
|
if (filled($written)) {
|
|
return (string) $written;
|
|
}
|
|
}
|
|
|
|
return __('maintenance.default_message');
|
|
}
|
|
|
|
public static function until(): ?CarbonImmutable
|
|
{
|
|
$until = self::state()['until'] ?? null;
|
|
|
|
return filled($until) ? CarbonImmutable::parse($until) : null;
|
|
}
|
|
|
|
public static function startedAt(): ?CarbonImmutable
|
|
{
|
|
$started = self::state()['started_at'] ?? null;
|
|
|
|
return filled($started) ? CarbonImmutable::parse($started) : null;
|
|
}
|
|
|
|
/**
|
|
* Seconds a caller should wait before retrying, for the `Retry-After`
|
|
* header. Capped so a distant end date does not tell a CDN to stop asking
|
|
* for a day.
|
|
*/
|
|
public static function retryAfter(): int
|
|
{
|
|
$until = self::until();
|
|
|
|
if ($until === null || $until->isPast()) {
|
|
return 300;
|
|
}
|
|
|
|
return min(3600, max(60, (int) now()->diffInSeconds($until)));
|
|
}
|
|
|
|
/**
|
|
* Every locale's message, for editing in the panel.
|
|
*
|
|
* @return array<string, string>
|
|
*/
|
|
public static function messages(): array
|
|
{
|
|
$messages = self::state()['message'] ?? [];
|
|
|
|
return is_array($messages) ? $messages : [];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public static function state(): array
|
|
{
|
|
$state = Setting::get(self::KEY, []);
|
|
|
|
return is_array($state) ? $state : [];
|
|
}
|
|
}
|