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>
125 lines
4.4 KiB
PHP
125 lines
4.4 KiB
PHP
<?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);
|
|
}
|
|
}
|