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>
83 lines
2.6 KiB
PHP
83 lines
2.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Filament\Resources\Pages\PageResource;
|
|
use App\Filament\Resources\Projects\ProjectResource;
|
|
use App\Filament\Resources\Services\ServiceResource;
|
|
use App\Models\Page;
|
|
use App\Models\Project;
|
|
use App\Models\Service;
|
|
use App\Models\User;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
/**
|
|
* Where a record under review lives, in words a notification can use.
|
|
*
|
|
* The panel-facing half of reviews, kept out of the models on purpose: a page
|
|
* has no business knowing which Filament resource happens to edit it. Written
|
|
* as an explicit map rather than resolved dynamically because there are three
|
|
* of them and there is no honest way to guess the fourth.
|
|
*/
|
|
final class ReviewTarget
|
|
{
|
|
/**
|
|
* @var array<class-string<Model>, class-string>
|
|
*/
|
|
protected const RESOURCES = [
|
|
Page::class => PageResource::class,
|
|
Project::class => ProjectResource::class,
|
|
Service::class => ServiceResource::class,
|
|
];
|
|
|
|
public static function url(Model $record): string
|
|
{
|
|
$resource = self::RESOURCES[$record::class] ?? null;
|
|
|
|
return $resource
|
|
? $resource::getUrl('edit', ['record' => $record])
|
|
: url('/admin');
|
|
}
|
|
|
|
/**
|
|
* The permission that decides who can answer a review of this record.
|
|
*
|
|
* Derived from the table name, which matches the permission prefix for
|
|
* every model that has a `publish` ability — the catalogue in
|
|
* `Permissions::RESOURCES` is keyed the same way, and the unit test over
|
|
* it is what keeps that true.
|
|
*/
|
|
public static function permission(Model $record): string
|
|
{
|
|
return $record->getTable().'.publish';
|
|
}
|
|
|
|
/**
|
|
* How many of these are waiting for the person reading the sidebar.
|
|
*
|
|
* Cached for a minute because Filament asks every resource for its badge
|
|
* on every page render, and three counting queries to draw three numbers
|
|
* would make the whole panel slower — the same reason the health badge is
|
|
* cached. A minute is well inside the time it takes somebody to notice.
|
|
*
|
|
* @param class-string<Model> $model
|
|
*/
|
|
public static function waitingFor(string $model, ?User $user): ?int
|
|
{
|
|
if ($user === null || $user->cannot((new $model)->getTable().'.publish')) {
|
|
return null;
|
|
}
|
|
|
|
$count = Cache::remember(
|
|
"reviews:waiting:{$model}:{$user->getAuthIdentifier()}",
|
|
now()->addMinute(),
|
|
fn () => $model::query()->awaitingReviewBy($user)->count(),
|
|
);
|
|
|
|
return $count > 0 ? $count : null;
|
|
}
|
|
}
|