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,104 @@
<?php
declare(strict_types=1);
namespace App\Services\Git;
use App\Enums\GitProvider;
use App\Models\Repository;
use App\Services\Git\Contracts\RepositoryProvider;
use Illuminate\Support\Facades\DB;
/**
* Resolves the right driver for a repository and applies what it returns.
*
* This is the only class that both knows about drivers and touches models,
* which keeps the drivers free of persistence concerns and the models free of
* HTTP concerns.
*/
class GitManager
{
/** @var array<string, RepositoryProvider> */
protected array $drivers = [];
public function driver(GitProvider|string $provider): RepositoryProvider
{
$key = $provider instanceof GitProvider ? $provider->value : $provider;
if (isset($this->drivers[$key])) {
return $this->drivers[$key];
}
$config = config("git.providers.{$key}");
if (! is_array($config) || ! isset($config['driver'])) {
throw GitException::unsupportedProvider($key);
}
/** @var class-string $class */
$class = $config['driver'];
return $this->drivers[$key] = $class::fromConfig($config);
}
/**
* Refresh one repository from its provider.
*
* Both outcomes are recorded: a success clears any previous error, and a
* failure increments a counter so the panel can flag repositories that
* have been broken for a while instead of quietly showing stale numbers.
*/
public function sync(Repository $repository): Repository
{
try {
$snapshot = $this->driver($repository->provider)->fetch($repository->full_name);
} catch (GitException $e) {
$repository->forceFill([
'sync_error' => $e->getMessage(),
'sync_failures' => $repository->sync_failures + 1,
'last_synced_at' => $repository->last_synced_at,
])->save();
throw $e;
}
DB::transaction(function () use ($repository, $snapshot): void {
$repository->forceFill([
...$snapshot->toAttributes(),
'full_name' => $snapshot->fullName,
'sync_error' => null,
'sync_failures' => 0,
'last_synced_at' => now(),
])->save();
$this->syncIssues($repository, $snapshot);
});
return $repository->refresh();
}
/**
* Replace the mirrored issues with what the provider currently reports.
*
* Issues that were closed or had their label removed upstream must
* disappear here too — an "open" issue on the site that is actually closed
* wastes a would-be contributor's time.
*/
protected function syncIssues(Repository $repository, RepositorySnapshot $snapshot): void
{
$seen = [];
foreach ($snapshot->issues as $issue) {
$repository->issues()->updateOrCreate(
['number' => $issue->number],
$issue->toAttributes(),
);
$seen[] = $issue->number;
}
$repository->issues()
->when($seen !== [], fn ($q) => $q->whereNotIn('number', $seen))
->delete();
}
}