Keep similar-ai from tripping the global circuit on a lone URL 502, clamp Qdrant search to 100, and add Server-Timing plus slow-request logging. Studio shared props, Academy S3 exists caching, heat chunking, and Redis/scheduler hygiene stay in this rollout.
89 lines
2.9 KiB
PHP
89 lines
2.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Services\Sitemaps\PublishedSitemapResolver;
|
|
use App\Services\Sitemaps\SitemapBuildService;
|
|
use App\Services\Sitemaps\SitemapXmlRenderer;
|
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
use Illuminate\Http\Response;
|
|
|
|
final class SitemapController extends Controller
|
|
{
|
|
public function __construct(
|
|
private readonly PublishedSitemapResolver $published,
|
|
private readonly SitemapBuildService $build,
|
|
private readonly SitemapXmlRenderer $renderer,
|
|
) {
|
|
}
|
|
|
|
public function index(): Response|BinaryFileResponse
|
|
{
|
|
if ((bool) config('sitemaps.pre_generated.enabled', true)) {
|
|
foreach ([
|
|
public_path('sitemaps/index.xml'),
|
|
public_path('sitemaps/sitemap.xml'),
|
|
public_path('sitemap.xml'),
|
|
] as $path) {
|
|
if (is_file($path) && is_readable($path)) {
|
|
return $this->xmlFileResponse($path);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1. Published release (release management pipeline fallback).
|
|
$published = $this->published->resolveIndex();
|
|
if ($published !== null) {
|
|
return $this->renderer->xmlResponse($published['content']);
|
|
}
|
|
|
|
// 2. Live-build fallback when no published sitemap is available.
|
|
if ((bool) config('sitemaps.delivery.fallback_to_live_build', true)) {
|
|
$built = $this->build->buildIndex(force: true, persist: false);
|
|
|
|
return $this->renderer->xmlResponse($built['content']);
|
|
}
|
|
|
|
throw new NotFoundHttpException();
|
|
}
|
|
|
|
public function show(string $name): Response|BinaryFileResponse
|
|
{
|
|
if ($name === 'sitemap') {
|
|
return $this->index();
|
|
}
|
|
|
|
// 1. Static file.
|
|
$path = public_path('sitemaps/' . $name . '.xml');
|
|
if ((bool) config('sitemaps.pre_generated.enabled', true) && file_exists($path)) {
|
|
return $this->xmlFileResponse($path);
|
|
}
|
|
|
|
// 2. Published release.
|
|
$published = $this->published->resolveNamed($name);
|
|
if ($published !== null) {
|
|
return $this->renderer->xmlResponse($published['content']);
|
|
}
|
|
|
|
if ((bool) config('sitemaps.delivery.fallback_to_live_build', true)) {
|
|
$built = $this->build->buildNamed($name, force: true, persist: false);
|
|
if ($built !== null) {
|
|
return $this->renderer->xmlResponse($built['content']);
|
|
}
|
|
}
|
|
|
|
throw new NotFoundHttpException();
|
|
}
|
|
|
|
private function xmlFileResponse(string $absolutePath): BinaryFileResponse
|
|
{
|
|
return response()->file($absolutePath, [
|
|
'Content-Type' => 'application/xml; charset=UTF-8',
|
|
'Cache-Control' => 'public, max-age=' . max(60, (int) config('sitemaps.cache_ttl_seconds', 900)),
|
|
]);
|
|
}
|
|
}
|