Files
landing-api/app/Support/ReviewTarget.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

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;
}
}