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>
This commit is contained in:
2026-08-19 17:55:27 +02:00
commit 3897113313
534 changed files with 64050 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace App\Models\Concerns;
use App\Models\Redirect;
use App\Support\SiteUrls;
use Illuminate\Database\Eloquent\Model;
/**
* Leaves a redirect behind when a slug changes.
*
* Renaming a slug breaks every link anybody shared, every bookmark and every
* search result — silently, because nothing notices and the frontend only
* finds out when a visitor gets a 404. The moment the slug changes is the only
* moment the old address is still known, so that is when the note is written.
*/
trait RecordsSlugChanges
{
/**
* The `site.paths` key this model publishes under.
*/
abstract public function publicPathType(): string;
public static function bootRecordsSlugChanges(): void
{
static::updated(function (Model $model): void {
if (! $model->wasChanged('slug')) {
return;
}
$model->recordSlugChange();
});
}
protected function recordSlugChange(): void
{
$type = $this->publicPathType();
$previous = (array) json_decode((string) json_encode($this->getOriginal('slug')), true);
$current = (array) $this->getTranslations('slug');
foreach (array_keys((array) config('locales.supported')) as $locale) {
$was = (string) ($previous[$locale] ?? '');
$now = (string) ($current[$locale] ?? '');
// Nothing to redirect from when the record had no address in this
// language, and nowhere to redirect to when it no longer has one.
if (blank($was) || blank($now) || $was === $now) {
continue;
}
$from = Redirect::normalise(SiteUrls::path($type, $locale, $was));
$to = Redirect::normalise(SiteUrls::path($type, $locale, $now));
if ($from === $to) {
continue;
}
$this->writeRedirect($from, $to, $locale);
}
}
protected function writeRedirect(string $from, string $to, string $locale): void
{
$scope = fn () => Redirect::query()->where('locale', $locale);
// The new address resolves to this record again, so any rule pointing
// away from it is now wrong — this is what renaming something back to
// its original slug looks like.
$scope()->where('from', $to)->delete();
// Whoever pointed at the old address now points at the new one, so a
// frontend never has to follow two hops.
$scope()->where('to', $from)->update(['to' => $to]);
$redirect = Redirect::query()->firstOrNew(['from' => $from, 'locale' => $locale]);
// `source` is set here rather than through mass assignment: it records
// what the application did, not anything somebody submitted.
$redirect->forceFill([
'to' => $to,
'status' => 301,
'is_enabled' => true,
'source_type' => $this->getMorphClass(),
'source_id' => $this->getKey(),
])->save();
// Belt and braces: a rule pointing at its own address would send a
// browser round for ever.
Redirect::query()->whereColumn('from', 'to')->delete();
}
}