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,85 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Roles\Schemas;
use App\Support\Permissions;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Illuminate\Validation\Rule;
class RoleForm
{
public static function configure(Schema $schema): Schema
{
return $schema->components([
Section::make()->schema([
TextInput::make('name')
->label(__('panel.roles.fields.name'))
->required()
->maxLength(120)
->rules(fn ($record) => [
Rule::unique('roles', 'name')->ignore($record?->id),
])
// Renaming a structural role would break the code that
// looks it up by name.
->disabled(fn ($record) => $record && in_array($record->name, Permissions::protectedRoles(), true))
->helperText(fn ($record) => $record && in_array($record->name, Permissions::protectedRoles(), true)
? __('panel.roles.protected')
: null),
]),
// One collapsible section per resource, generated from
// App\Support\Permissions — adding a permission there makes it
// appear here with no further work.
//
// Each group writes to its own state path rather than binding the
// `permissions` relationship directly: several components sharing
// one relationship would overwrite each other on save. The page
// classes flatten these back into a single syncPermissions() call
// (see HandlesRolePermissions).
...collect(Permissions::grouped())
->map(fn (array $permissions, string $group) => Section::make(
__("panel.permissions.groups.{$group}")
)
->collapsible()
->collapsed($group !== 'general')
->schema([
CheckboxList::make("permission_groups.{$group}")
->hiddenLabel()
->options(collect($permissions)
->mapWithKeys(fn (string $name) => [$name => self::abilityLabel($name)])
->all())
->bulkToggleable()
->columns(3),
]))
->values()
->all(),
]);
}
/**
* Human label for a permission, e.g. "pages.force_delete" becomes
* "Eliminar definitivamente".
*/
public static function abilityLabel(string $permission): string
{
if ($permission === Permissions::PANEL_ACCESS) {
return __('panel.permissions.abilities.panel_access');
}
$ability = str_contains($permission, '.')
? substr($permission, strpos($permission, '.') + 1)
: $permission;
$key = "panel.permissions.abilities.{$ability}";
$label = __($key);
// Fall back to the raw name if the catalogue grows a permission that
// has no translation yet, rather than printing the missing key.
return $label === $key ? $permission : $label;
}
}