186 lines
5.5 KiB
PHP
186 lines
5.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Sitemaps;
|
|
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
|
|
final class SitemapReleaseManager
|
|
{
|
|
public function generateReleaseId(): string
|
|
{
|
|
return now()->utc()->format('YmdHis') . '-' . Str::lower((string) Str::ulid());
|
|
}
|
|
|
|
public function releaseExists(string $releaseId): bool
|
|
{
|
|
return Storage::disk($this->disk())->exists($this->manifestPath($releaseId));
|
|
}
|
|
|
|
public function putDocument(string $releaseId, string $documentName, string $content): void
|
|
{
|
|
Storage::disk($this->disk())->put($this->releaseDocumentPath($releaseId, $documentName), $content);
|
|
}
|
|
|
|
public function getDocument(string $releaseId, string $documentName): ?string
|
|
{
|
|
$disk = Storage::disk($this->disk());
|
|
$path = $this->releaseDocumentPath($releaseId, $documentName);
|
|
|
|
if (! $disk->exists($path)) {
|
|
return null;
|
|
}
|
|
|
|
$content = $disk->get($path);
|
|
|
|
return is_string($content) && $content !== '' ? $content : null;
|
|
}
|
|
|
|
public function writeManifest(string $releaseId, array $manifest): void
|
|
{
|
|
Storage::disk($this->disk())->put($this->manifestPath($releaseId), json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
|
}
|
|
|
|
public function readManifest(string $releaseId): ?array
|
|
{
|
|
$disk = Storage::disk($this->disk());
|
|
$path = $this->manifestPath($releaseId);
|
|
|
|
if (! $disk->exists($path)) {
|
|
return null;
|
|
}
|
|
|
|
$decoded = json_decode((string) $disk->get($path), true);
|
|
|
|
return is_array($decoded) ? $decoded : null;
|
|
}
|
|
|
|
public function writeBuildReport(string $releaseId, array $report): void
|
|
{
|
|
Storage::disk($this->disk())->put($this->buildReportPath($releaseId), json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
|
}
|
|
|
|
public function activate(string $releaseId): void
|
|
{
|
|
$payload = [
|
|
'release_id' => $releaseId,
|
|
'activated_at' => now()->toAtomString(),
|
|
];
|
|
|
|
$this->atomicJsonWrite($this->activePointerPath(), $payload);
|
|
}
|
|
|
|
public function activeReleaseId(): ?string
|
|
{
|
|
$disk = Storage::disk($this->disk());
|
|
$path = $this->activePointerPath();
|
|
|
|
if (! $disk->exists($path)) {
|
|
return null;
|
|
}
|
|
|
|
$decoded = json_decode((string) $disk->get($path), true);
|
|
|
|
return is_array($decoded) && is_string($decoded['release_id'] ?? null)
|
|
? $decoded['release_id']
|
|
: null;
|
|
}
|
|
|
|
public function activeManifest(): ?array
|
|
{
|
|
$releaseId = $this->activeReleaseId();
|
|
|
|
return $releaseId !== null ? $this->readManifest($releaseId) : null;
|
|
}
|
|
|
|
/**
|
|
* @return list<array<string, mixed>>
|
|
*/
|
|
public function listReleases(): array
|
|
{
|
|
$releases = [];
|
|
|
|
foreach (Storage::disk($this->disk())->allDirectories($this->releasesRootPath()) as $directory) {
|
|
$releaseId = basename($directory);
|
|
$manifest = $this->readManifest($releaseId);
|
|
|
|
if ($manifest !== null) {
|
|
$releases[] = $manifest;
|
|
}
|
|
}
|
|
|
|
usort($releases, static fn (array $left, array $right): int => strcmp((string) ($right['release_id'] ?? ''), (string) ($left['release_id'] ?? '')));
|
|
|
|
return $releases;
|
|
}
|
|
|
|
public function deleteRelease(string $releaseId): void
|
|
{
|
|
Storage::disk($this->disk())->deleteDirectory($this->releaseRootPath($releaseId));
|
|
}
|
|
|
|
public function documentRelativePath(string $documentName): string
|
|
{
|
|
return $documentName === SitemapCacheService::INDEX_DOCUMENT
|
|
? 'sitemap.xml'
|
|
: 'sitemaps/' . $documentName . '.xml';
|
|
}
|
|
|
|
public function releaseDocumentPath(string $releaseId, string $documentName): string
|
|
{
|
|
return $this->releaseRootPath($releaseId) . '/' . $this->documentRelativePath($documentName);
|
|
}
|
|
|
|
public function manifestPath(string $releaseId): string
|
|
{
|
|
return $this->releaseRootPath($releaseId) . '/manifest.json';
|
|
}
|
|
|
|
public function buildReportPath(string $releaseId): string
|
|
{
|
|
return $this->releaseRootPath($releaseId) . '/build-report.json';
|
|
}
|
|
|
|
private function releaseRootPath(string $releaseId): string
|
|
{
|
|
return $this->releasesRootPath() . '/' . $releaseId;
|
|
}
|
|
|
|
private function releasesRootPath(): string
|
|
{
|
|
return trim((string) config('sitemaps.releases.path', 'sitemaps'), '/') . '/releases';
|
|
}
|
|
|
|
private function activePointerPath(): string
|
|
{
|
|
return trim((string) config('sitemaps.releases.path', 'sitemaps'), '/') . '/active.json';
|
|
}
|
|
|
|
private function disk(): string
|
|
{
|
|
return (string) config('sitemaps.releases.disk', 'local');
|
|
}
|
|
|
|
private function atomicJsonWrite(string $relativePath, array $payload): void
|
|
{
|
|
$disk = Storage::disk($this->disk());
|
|
$json = json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
|
|
try {
|
|
$absolutePath = $disk->path($relativePath);
|
|
$directory = dirname($absolutePath);
|
|
|
|
if (! is_dir($directory)) {
|
|
mkdir($directory, 0755, true);
|
|
}
|
|
|
|
$temporaryPath = $absolutePath . '.tmp';
|
|
file_put_contents($temporaryPath, $json, LOCK_EX);
|
|
rename($temporaryPath, $absolutePath);
|
|
} catch (\Throwable) {
|
|
$disk->put($relativePath, $json);
|
|
}
|
|
}
|
|
} |