Files
landing-api/app/Support/Maintenance.php
Josep Piguillem 3897113313 Initial commit: Pireon Admin — headless admin panel and public API
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>
2026-08-19 17:55:27 +02:00

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 : [];
}
}