name('api.public.') ->middleware(['throttle:public-api', 'preview', 'site.available', 'cache.public']) ->group(function () { Route::get('site', [SiteController::class, 'index'])->name('site'); Route::get('slugs', [SiteController::class, 'slugs'])->name('slugs'); Route::get('radar', [SiteController::class, 'radar'])->name('radar'); Route::get('technologies', [SiteController::class, 'technologies'])->name('technologies'); Route::get('good-first-issues', [SiteController::class, 'goodFirstIssues'])->name('good-first-issues'); Route::get('redirects', [SiteController::class, 'redirects'])->name('redirects'); Route::get('pages', [PublicPageController::class, 'index'])->name('pages.index'); Route::get('pages/{slug}', [PublicPageController::class, 'show'])->name('pages.show'); Route::get('projects', [PublicProjectController::class, 'index'])->name('projects.index'); Route::get('projects/{slug}', [PublicProjectController::class, 'show'])->name('projects.show'); Route::get('members', [PublicMemberController::class, 'index'])->name('members.index'); Route::get('members/{slug}', [PublicMemberController::class, 'show'])->name('members.show'); Route::get('calls', [PublicCallController::class, 'index'])->name('calls.index'); Route::get('calls/{slug}', [PublicCallController::class, 'show'])->name('calls.show'); // The pieces a landing page is made of. Four collections rather than a // "home page" payload: what gets composed, and in what order, is the // frontend's business. Route::get('services', [ShowcaseController::class, 'services'])->name('services.index'); Route::get('services/{slug}', [ShowcaseController::class, 'service'])->name('services.show'); Route::get('clients', [ShowcaseController::class, 'clients'])->name('clients'); Route::get('testimonials', [ShowcaseController::class, 'testimonials'])->name('testimonials'); Route::get('talks', [ShowcaseController::class, 'talks'])->name('talks'); }); /* |-------------------------------------------------------------------------- | Public API — the write half |-------------------------------------------------------------------------- | | The only endpoints on the public API that are not read-only, and therefore | the only ones a stranger can use to put a row in the database. They carry | their own throttle, because the read budget is sized for an SSR frontend | making hundreds of requests a minute and would be meaningless here. | | `cache.public` is absent on purpose: a confirmation is nobody else's, and a | shared cache holding one would hand it to the next visitor. | */ Route::prefix('public/v1') ->name('api.public.') ->middleware(['throttle:public-write', 'site.available']) ->group(function () { Route::post('calls/{slug}/apply', [ApplicationController::class, 'apply']) ->name('calls.apply'); Route::post('contact', [ApplicationController::class, 'contact']) ->name('contact'); }); /* |-------------------------------------------------------------------------- | Public API — the frontend reporting back |-------------------------------------------------------------------------- | | Also a write endpoint, but not the same kind: the author is the frontend | itself rather than a visitor, so it can hold a shared secret and does. It | carries its own throttle for the same reason the submissions do — the read | budget is sized for an SSR frontend and would mean nothing here. | | Outside `site.available` on purpose. Maintenance mode is about what visitors | are shown; a 404 that happens while the site is down is still a 404 worth | knowing about, and dropping it would lose exactly the reports produced by | whatever change the maintenance was for. | */ Route::prefix('public/v1') ->name('api.public.') ->middleware(['throttle:public-telemetry', 'ingest']) ->group(function () { Route::post('telemetry/not-found', [TelemetryController::class, 'notFound']) ->name('telemetry.not-found'); Route::post('telemetry/views', [TelemetryController::class, 'views']) ->name('telemetry.views'); }); /* |-------------------------------------------------------------------------- | Admin API — authenticated |-------------------------------------------------------------------------- */ Route::prefix('v1')->name('api.v1.')->group(function () { // Convenience endpoint for first-party clients: exchanges credentials for // a token upstream so they do not need to know the identity server's URL. Route::post('auth/token', [TokenController::class, 'store']) ->middleware('throttle:auth-token') ->name('auth.token'); Route::middleware(['auth:api', 'throttle:api'])->group(function () { Route::get('me', MeController::class)->name('me'); Route::apiResource('pages', PageController::class); Route::post('pages/{page}/restore', [PageController::class, 'restore']) ->withTrashed() ->name('pages.restore'); Route::apiResource('categories', CategoryController::class); Route::apiResource('users', UserController::class)->only(['index', 'show', 'update']); // Declared before the resource route so "catalogue" is not swallowed // by the {role} parameter. Route::get('roles/catalogue', [RoleController::class, 'catalogue'])->name('roles.catalogue'); Route::apiResource('roles', RoleController::class)->only(['index', 'show']); }); });