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,124 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\PublicApi\V1;
use App\Http\Resources\PublicApi\PublicClientResource;
use App\Http\Resources\PublicApi\PublicServiceResource;
use App\Http\Resources\PublicApi\PublicTalkResource;
use App\Http\Resources\PublicApi\PublicTestimonialResource;
use App\Models\Client;
use App\Models\Service;
use App\Models\Talk;
use App\Models\Testimonial;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
/**
* The pieces a landing page is made of.
*
* Four collections rather than one "home page" payload, and no ordering of
* sections anywhere in here: what gets composed, and in what order, is the
* frontend's business. This side only knows what exists.
*/
class ShowcaseController extends PublicController
{
/**
* GET /api/public/v1/services
*/
public function services(Request $request): AnonymousResourceCollection
{
$services = Service::query()
->with(['technologies'])
->withCount('projects')
->unless($this->isPreview($request), fn (Builder $q) => $q->published())
->when($request->boolean('featured'), fn (Builder $q) => $q->where('is_featured', true))
->orderBy('position')
->get();
return PublicServiceResource::collection($services);
}
/**
* GET /api/public/v1/services/{slug}
*
* Resolved by slug within the active locale, like every other record here.
*/
public function service(Request $request, string $slug): PublicServiceResource
{
$service = Service::query()
->with([
// The work that backs the offer, and what people said about
// it: a service page with neither is a promise.
'projects' => fn ($q) => $q->published()->with(['technologies', 'coverMedia']),
'technologies',
'testimonials' => fn ($q) => $q->public()->with(['client.logoMedia', 'avatarMedia'])->orderBy('position'),
])
->whereSlug($slug)
->unless($this->isPreview($request), fn (Builder $q) => $q->published())
->firstOrFail();
return PublicServiceResource::make($service)->detailed();
}
/**
* GET /api/public/v1/clients
*
* The wall of logos. Organisations that asked not to appear are absent
* whatever else is true of them.
*/
public function clients(Request $request): AnonymousResourceCollection
{
$clients = Client::query()
->with('logoMedia')
->withCount(['projects' => fn (Builder $q) => $q->published()])
->public()
// A client with nothing published behind it would be a logo the
// site cannot justify showing.
->when($request->boolean('with_projects', true), fn (Builder $q) => $q
->whereHas('projects', fn (Builder $p) => $p->published()))
->orderBy('position')
->orderBy('name')
->get();
return PublicClientResource::collection($clients);
}
/**
* GET /api/public/v1/testimonials
*/
public function testimonials(Request $request): AnonymousResourceCollection
{
$testimonials = Testimonial::query()
->with(['client.logoMedia', 'avatarMedia'])
->public()
->when($request->filled('client'), fn (Builder $q) => $q
->whereHas('client', fn (Builder $c) => $c->where('slug', $request->string('client'))))
->when($request->filled('service'), fn (Builder $q) => $q
->whereHas('service', fn (Builder $s) => $s
->where('slug->'.app()->getLocale(), $request->string('service'))))
->orderBy('position')
->orderByDesc('given_at')
->get();
return PublicTestimonialResource::collection($testimonials);
}
/**
* GET /api/public/v1/talks
*/
public function talks(Request $request): AnonymousResourceCollection
{
$talks = Talk::query()
->with('member')
->public()
->when($request->boolean('with_video'), fn (Builder $q) => $q->whereNotNull('video_url'))
->recentFirst()
->limit(min($request->integer('limit', 50), 100))
->get();
return PublicTalkResource::collection($talks);
}
}