Files
SkinbaseNova/app/Services/NovaCards/NovaCardExportService.php
2026-03-28 19:15:39 +01:00

108 lines
4.0 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\NovaCards;
use App\Jobs\NovaCards\GenerateNovaCardExportJob;
use App\Models\NovaCard;
use App\Models\NovaCardExport;
use App\Models\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Storage;
/**
* Handles export request creation, status checking, and file resolution.
* Actual rendering is delegated to GenerateNovaCardExportJob (queued).
*/
class NovaCardExportService
{
/** How long (in minutes) a generated export file is available. */
private const EXPORT_TTL_MINUTES = 60;
/** Allowed export types and their canvas dimensions. */
public const EXPORT_SPECS = [
NovaCardExport::TYPE_PREVIEW => ['width' => 1080, 'height' => 1080, 'format' => 'webp'],
NovaCardExport::TYPE_HIRES => ['width' => 2160, 'height' => 2160, 'format' => 'webp'],
NovaCardExport::TYPE_SQUARE => ['width' => 1080, 'height' => 1080, 'format' => 'webp'],
NovaCardExport::TYPE_STORY => ['width' => 1080, 'height' => 1920, 'format' => 'webp'],
NovaCardExport::TYPE_WALLPAPER => ['width' => 2560, 'height' => 1440, 'format' => 'webp'],
NovaCardExport::TYPE_OG => ['width' => 1200, 'height' => 630, 'format' => 'jpg'],
];
public function requestExport(User $user, NovaCard $card, string $exportType, array $options = []): NovaCardExport
{
if (! $card->allow_export && ! $card->isOwnedBy($user)) {
abort(403, 'This card does not allow exports.');
}
$spec = self::EXPORT_SPECS[$exportType] ?? self::EXPORT_SPECS[NovaCardExport::TYPE_PREVIEW];
// Reuse a recent pending/ready non-expired export for the same type.
$existing = NovaCardExport::query()
->where('card_id', $card->id)
->where('user_id', $user->id)
->where('export_type', $exportType)
->whereIn('status', [NovaCardExport::STATUS_READY, NovaCardExport::STATUS_PENDING, NovaCardExport::STATUS_PROCESSING])
->where('expires_at', '>', now())
->orderByDesc('created_at')
->first();
if ($existing !== null) {
return $existing;
}
$export = NovaCardExport::query()->create([
'card_id' => $card->id,
'user_id' => $user->id,
'export_type' => $exportType,
'status' => NovaCardExport::STATUS_PENDING,
'width' => $spec['width'],
'height' => $spec['height'],
'format' => $spec['format'],
'options_json' => $options,
'expires_at' => now()->addMinutes(self::EXPORT_TTL_MINUTES),
]);
GenerateNovaCardExportJob::dispatch($export->id)
->onQueue((string) config('nova_cards.render.queue', 'default'));
return $export->refresh();
}
public function getStatus(NovaCardExport $export): array
{
return [
'id' => (int) $export->id,
'export_type' => (string) $export->export_type,
'status' => (string) $export->status,
'output_url' => $export->isReady() && ! $export->isExpired() ? $export->outputUrl() : null,
'width' => $export->width,
'height' => $export->height,
'format' => (string) $export->format,
'ready_at' => optional($export->ready_at)?->toISOString(),
'expires_at' => optional($export->expires_at)?->toISOString(),
];
}
public function cleanupExpired(): int
{
$exports = NovaCardExport::query()
->where('expires_at', '<', now())
->whereNotNull('output_path')
->get();
$count = 0;
foreach ($exports as $export) {
$disk = Storage::disk((string) config('nova_cards.storage.public_disk', 'public'));
if ($export->output_path && $disk->exists($export->output_path)) {
$disk->delete($export->output_path);
}
$export->delete();
$count++;
}
return $count;
}
}