Improve studio artwork media revisions
This commit is contained in:
@@ -13,6 +13,7 @@ use App\Services\ArtworkEvolutionService;
|
||||
use App\Services\Cdn\ArtworkCdnPurgeService;
|
||||
use App\Services\ArtworkSearchIndexer;
|
||||
use App\Services\ArtworkAttributionService;
|
||||
use App\Services\Artworks\ArtworkPublicationService;
|
||||
use App\Services\TagService;
|
||||
use App\Services\ArtworkVersioningService;
|
||||
use App\Services\Studio\StudioArtworkQueryService;
|
||||
@@ -21,6 +22,7 @@ use App\Services\Tags\TagDiscoveryService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
@@ -40,6 +42,7 @@ final class StudioArtworksApiController extends Controller
|
||||
private readonly TagDiscoveryService $tagDiscoveryService,
|
||||
private readonly TagService $tagService,
|
||||
private readonly ArtworkCdnPurgeService $cdnPurge,
|
||||
private readonly ArtworkPublicationService $artworkPublication,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -50,6 +53,8 @@ final class StudioArtworksApiController extends Controller
|
||||
{
|
||||
$userId = $request->user()->id;
|
||||
|
||||
$this->artworkPublication->publishDueScheduledForUser((int) $userId);
|
||||
|
||||
$filters = $request->only([
|
||||
'q', 'status', 'category', 'tags', 'date_from', 'date_to',
|
||||
'performance', 'sort',
|
||||
@@ -418,6 +423,10 @@ final class StudioArtworksApiController extends Controller
|
||||
|
||||
private function transformArtwork($artwork): array
|
||||
{
|
||||
if ($artwork instanceof Artwork) {
|
||||
$artwork = $this->artworkPublication->publishIfDue($artwork);
|
||||
}
|
||||
|
||||
$stats = $artwork->stats ?? null;
|
||||
|
||||
return [
|
||||
@@ -468,6 +477,7 @@ final class StudioArtworksApiController extends Controller
|
||||
public function replaceFile(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$artwork = $request->user()->artworks()->findOrFail($id);
|
||||
$previousSnapshot = $this->versioningService->captureArtworkSnapshot($artwork);
|
||||
|
||||
$request->validate([
|
||||
'file' => 'required|file|mimes:jpeg,jpg,png,webp|max:51200', // 50 MB
|
||||
@@ -548,16 +558,14 @@ final class StudioArtworksApiController extends Controller
|
||||
'height' => max(1, $height),
|
||||
]);
|
||||
|
||||
// 5. Create version record, apply ranking protection, audit log
|
||||
$version = $this->versioningService->createNewVersion(
|
||||
// 5. Create version record from the new full media snapshot.
|
||||
$artwork->refresh();
|
||||
$version = $this->versioningService->createVersionFromSnapshot(
|
||||
$artwork,
|
||||
$originalRelative,
|
||||
$hash,
|
||||
max(1, $width),
|
||||
max(1, $height),
|
||||
$size,
|
||||
$this->versioningService->captureArtworkSnapshot($artwork),
|
||||
$request->user()->id,
|
||||
$request->input('change_note'),
|
||||
$previousSnapshot,
|
||||
);
|
||||
|
||||
// 6. Reindex in Meilisearch (non-blocking)
|
||||
@@ -575,14 +583,9 @@ final class StudioArtworksApiController extends Controller
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'thumb_url' => $artwork->thumbUrl('md'),
|
||||
'thumb_url_lg' => $artwork->thumbUrl('lg'),
|
||||
'width' => $artwork->width,
|
||||
'height' => $artwork->height,
|
||||
'file_size' => $artwork->file_size,
|
||||
'version_number' => $version->version_number,
|
||||
'requires_reapproval' => (bool) $artwork->requires_reapproval,
|
||||
]);
|
||||
] + $this->mediaPayload($artwork));
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('replaceFile: processing error', [
|
||||
'artwork_id' => $artwork->id,
|
||||
@@ -592,6 +595,259 @@ final class StudioArtworksApiController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
public function reviseMedia(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$artwork = $request->user()->artworks()->findOrFail($id);
|
||||
$previousSnapshot = $this->versioningService->captureArtworkSnapshot($artwork);
|
||||
$filesByVariant = $this->snapshotFilesByVariant($previousSnapshot);
|
||||
$hasArchiveFile = array_key_exists('orig_archive', $filesByVariant);
|
||||
|
||||
$request->validate([
|
||||
'cover_file' => 'sometimes|nullable|file|mimes:jpeg,jpg,png,webp|max:51200',
|
||||
'archive_file' => 'sometimes|nullable|file|mimes:zip,rar,7z,tar,gz|max:204800',
|
||||
'screenshot_files' => 'sometimes|array|max:4',
|
||||
'screenshot_files.*' => 'file|mimes:jpeg,jpg,png,webp|max:51200',
|
||||
'replace_shots' => 'sometimes|array|max:4',
|
||||
'replace_shots.*' => 'file|mimes:jpeg,jpg,png,webp|max:51200',
|
||||
'remove_shots' => 'sometimes|array|max:4',
|
||||
'remove_shots.*' => 'integer|min:0|max:3',
|
||||
'change_note' => 'sometimes|nullable|string|max:500',
|
||||
]);
|
||||
|
||||
/** @var UploadedFile|null $coverFile */
|
||||
$coverFile = $request->file('cover_file');
|
||||
/** @var UploadedFile|null $archiveFile */
|
||||
$archiveFile = $request->file('archive_file');
|
||||
$screenshotFiles = array_values(array_filter((array) $request->file('screenshot_files', []), fn ($file): bool => $file instanceof UploadedFile));
|
||||
$replaceShotFiles = array_filter((array) $request->file('replace_shots', []), fn ($file): bool => $file instanceof UploadedFile);
|
||||
$removeShotIndexes = collect((array) $request->input('remove_shots', []))
|
||||
->map(fn ($value): int => (int) $value)
|
||||
->filter(fn (int $value): bool => $value >= 0)
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
if (! $hasArchiveFile && $archiveFile) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => 'Archive package replacement is available only for archive artworks.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (! $coverFile && ! $archiveFile && $screenshotFiles === [] && $replaceShotFiles === [] && $removeShotIndexes->isEmpty()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => 'Choose a new cover screenshot, a new archive file, or extra screenshots first.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$existingScreenshots = collect($previousSnapshot['files'] ?? [])
|
||||
->filter(fn (array $file): bool => str_starts_with((string) ($file['variant'] ?? ''), 'shot'))
|
||||
->values();
|
||||
|
||||
$remainingScreenshotCount = max(0, $existingScreenshots->count() - $removeShotIndexes->count());
|
||||
|
||||
if (($remainingScreenshotCount + count($screenshotFiles)) > 4) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => 'Artworks can have up to 5 screenshots total: 1 main cover plus 4 additional screenshots.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->versioningService->rateLimitCheck($request->user()->id, $artwork->id);
|
||||
} catch (TooManyRequestsHttpException $e) {
|
||||
return response()->json(['success' => false, 'error' => $e->getMessage()], 429);
|
||||
}
|
||||
|
||||
$derivatives = app(\App\Services\Uploads\UploadDerivativesService::class);
|
||||
$storage = app(\App\Services\Uploads\UploadStorageService::class);
|
||||
$cleanupLocalPaths = [];
|
||||
$cleanupObjectPaths = [];
|
||||
|
||||
try {
|
||||
$coverDescriptor = null;
|
||||
$coverHash = (string) ($previousSnapshot['artwork']['hash'] ?? $artwork->hash ?? '');
|
||||
$width = (int) ($previousSnapshot['artwork']['width'] ?? $artwork->width ?? 0);
|
||||
$height = (int) ($previousSnapshot['artwork']['height'] ?? $artwork->height ?? 0);
|
||||
$publicAssets = $this->currentPublicAssetsFromSnapshot($previousSnapshot);
|
||||
|
||||
if ($coverFile) {
|
||||
$coverHash = hash_file('sha256', $coverFile->getPathname());
|
||||
$coverStored = $derivatives->storeOriginal($coverFile->getPathname(), $coverHash, $coverFile->getClientOriginalName());
|
||||
$cleanupLocalPaths[] = $coverStored['local_path'];
|
||||
$cleanupObjectPaths[] = $coverStored['object_path'];
|
||||
$coverDescriptor = $this->storedOriginalDescriptor($coverStored, 'orig_image');
|
||||
|
||||
$publicAssets = $derivatives->generatePublicDerivatives($coverFile->getPathname(), $coverHash);
|
||||
foreach ($publicAssets as $asset) {
|
||||
$cleanupObjectPaths[] = (string) ($asset['path'] ?? '');
|
||||
}
|
||||
|
||||
$dims = @getimagesize($coverFile->getPathname());
|
||||
$width = is_array($dims) && isset($dims[0]) ? max(1, (int) $dims[0]) : max(1, (int) $artwork->width);
|
||||
$height = is_array($dims) && isset($dims[1]) ? max(1, (int) $dims[1]) : max(1, (int) $artwork->height);
|
||||
} else {
|
||||
$coverDescriptor = $hasArchiveFile
|
||||
? ($this->existingVariantDescriptor($filesByVariant['orig_image'] ?? null, 'orig_image') ?? $this->existingVariantDescriptor($filesByVariant['orig'] ?? null, 'orig'))
|
||||
: $this->existingVariantDescriptor($filesByVariant['orig'] ?? null, 'orig');
|
||||
}
|
||||
|
||||
if (! $coverDescriptor) {
|
||||
return response()->json(['success' => false, 'error' => 'Unable to resolve the current cover screenshot.'], 422);
|
||||
}
|
||||
|
||||
$archiveDescriptor = null;
|
||||
if ($hasArchiveFile || $archiveFile) {
|
||||
if ($archiveFile) {
|
||||
$archiveHash = hash_file('sha256', $archiveFile->getPathname());
|
||||
$archiveStored = $derivatives->storeOriginal($archiveFile->getPathname(), $archiveHash, $archiveFile->getClientOriginalName());
|
||||
$cleanupLocalPaths[] = $archiveStored['local_path'];
|
||||
$cleanupObjectPaths[] = $archiveStored['object_path'];
|
||||
$archiveDescriptor = $this->storedOriginalDescriptor($archiveStored, 'orig_archive');
|
||||
} else {
|
||||
$archiveDescriptor = $this->existingVariantDescriptor($filesByVariant['orig_archive'] ?? null, 'orig_archive');
|
||||
}
|
||||
}
|
||||
|
||||
$newScreenshotDescriptors = [];
|
||||
foreach ($screenshotFiles as $screenshotFile) {
|
||||
$screenshotHash = hash_file('sha256', $screenshotFile->getPathname());
|
||||
$screenshotStored = $derivatives->storeOriginal($screenshotFile->getPathname(), $screenshotHash, $screenshotFile->getClientOriginalName());
|
||||
$cleanupLocalPaths[] = $screenshotStored['local_path'];
|
||||
$cleanupObjectPaths[] = $screenshotStored['object_path'];
|
||||
$newScreenshotDescriptors[] = $this->storedOriginalDescriptor($screenshotStored, '');
|
||||
}
|
||||
|
||||
// Build per-slot replacement descriptors
|
||||
$replaceShotDescriptors = [];
|
||||
foreach ($replaceShotFiles as $slotIndex => $replaceShotFile) {
|
||||
$replaceShotHash = hash_file('sha256', $replaceShotFile->getPathname());
|
||||
$replaceShotStored = $derivatives->storeOriginal($replaceShotFile->getPathname(), $replaceShotHash, $replaceShotFile->getClientOriginalName());
|
||||
$cleanupLocalPaths[] = $replaceShotStored['local_path'];
|
||||
$cleanupObjectPaths[] = $replaceShotStored['object_path'];
|
||||
$replaceShotDescriptors[(int) $slotIndex] = $this->storedOriginalDescriptor($replaceShotStored, '');
|
||||
}
|
||||
|
||||
// Merge existing slots with per-slot replacements
|
||||
$existingScreenshotDescriptors = $existingScreenshots
|
||||
->map(function (array $file, int $index) use ($replaceShotDescriptors, $removeShotIndexes): ?array {
|
||||
if ($removeShotIndexes->contains($index)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($replaceShotDescriptors[$index])) {
|
||||
return $replaceShotDescriptors[$index];
|
||||
}
|
||||
|
||||
return $this->existingVariantDescriptor($file, '');
|
||||
})
|
||||
->filter()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$allScreenshotDescriptors = [];
|
||||
foreach (array_values(array_merge($existingScreenshotDescriptors, $newScreenshotDescriptors)) as $index => $descriptor) {
|
||||
$descriptor['variant'] = 'shot' . ($index + 1);
|
||||
$allScreenshotDescriptors[] = $descriptor;
|
||||
}
|
||||
|
||||
$primaryDescriptor = $archiveDescriptor ?? $coverDescriptor;
|
||||
$preferredFileName = $archiveFile?->getClientOriginalName()
|
||||
?? ($archiveDescriptor ? (string) ($previousSnapshot['artwork']['file_name'] ?? $artwork->file_name) : $coverFile?->getClientOriginalName())
|
||||
?? (string) ($previousSnapshot['artwork']['file_name'] ?? $artwork->file_name);
|
||||
|
||||
$snapshot = [
|
||||
'artwork' => [
|
||||
'file_name' => $this->resolvePreferredFileName($preferredFileName, (string) ($primaryDescriptor['ext'] ?? '')),
|
||||
'file_path' => (string) ($primaryDescriptor['path'] ?? ''),
|
||||
'hash' => $coverHash,
|
||||
'file_ext' => (string) ($primaryDescriptor['ext'] ?? ''),
|
||||
'thumb_ext' => 'webp',
|
||||
'file_size' => (int) ($primaryDescriptor['size'] ?? 0),
|
||||
'mime_type' => (string) ($primaryDescriptor['mime'] ?? 'application/octet-stream'),
|
||||
'width' => max(1, $width),
|
||||
'height' => max(1, $height),
|
||||
],
|
||||
'files' => array_values(array_filter(array_merge(
|
||||
[[
|
||||
'variant' => 'orig',
|
||||
'path' => (string) ($primaryDescriptor['path'] ?? ''),
|
||||
'mime' => (string) ($primaryDescriptor['mime'] ?? 'application/octet-stream'),
|
||||
'size' => (int) ($primaryDescriptor['size'] ?? 0),
|
||||
]],
|
||||
[[
|
||||
'variant' => 'orig_image',
|
||||
'path' => (string) ($coverDescriptor['path'] ?? ''),
|
||||
'mime' => (string) ($coverDescriptor['mime'] ?? 'application/octet-stream'),
|
||||
'size' => (int) ($coverDescriptor['size'] ?? 0),
|
||||
]],
|
||||
$archiveDescriptor ? [[
|
||||
'variant' => 'orig_archive',
|
||||
'path' => (string) ($archiveDescriptor['path'] ?? ''),
|
||||
'mime' => (string) ($archiveDescriptor['mime'] ?? 'application/octet-stream'),
|
||||
'size' => (int) ($archiveDescriptor['size'] ?? 0),
|
||||
]] : [],
|
||||
collect($publicAssets)->map(fn (array $asset, string $variant): array => [
|
||||
'variant' => $variant,
|
||||
'path' => (string) ($asset['path'] ?? ''),
|
||||
'mime' => (string) ($asset['mime'] ?? 'image/webp'),
|
||||
'size' => (int) ($asset['size'] ?? 0),
|
||||
])->values()->all(),
|
||||
array_map(fn (array $descriptor): array => [
|
||||
'variant' => (string) $descriptor['variant'],
|
||||
'path' => (string) $descriptor['path'],
|
||||
'mime' => (string) ($descriptor['mime'] ?? 'application/octet-stream'),
|
||||
'size' => (int) ($descriptor['size'] ?? 0),
|
||||
], $allScreenshotDescriptors),
|
||||
), fn (array $file): bool => (string) ($file['variant'] ?? '') !== '' && (string) ($file['path'] ?? '') !== '')),
|
||||
];
|
||||
|
||||
$this->versioningService->applySnapshot($artwork, $snapshot);
|
||||
$artwork->refresh();
|
||||
|
||||
$version = $this->versioningService->createVersionFromSnapshot(
|
||||
$artwork,
|
||||
$snapshot,
|
||||
$request->user()->id,
|
||||
$request->input('change_note'),
|
||||
$previousSnapshot,
|
||||
);
|
||||
|
||||
try {
|
||||
$this->searchIndexer->update($artwork);
|
||||
} catch (\Throwable $exception) {
|
||||
Log::warning('Archive media revision reindex failed', [
|
||||
'artwork_id' => $artwork->id,
|
||||
'error' => $exception->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'version_number' => $version->version_number,
|
||||
'requires_reapproval' => (bool) $artwork->requires_reapproval,
|
||||
] + $this->mediaPayload($artwork));
|
||||
} catch (\Throwable $exception) {
|
||||
foreach ($cleanupLocalPaths as $path) {
|
||||
$storage->deleteLocalFile($path);
|
||||
}
|
||||
|
||||
foreach ($cleanupObjectPaths as $path) {
|
||||
$storage->deleteObject($path);
|
||||
}
|
||||
|
||||
Log::error('reviseMedia: processing error', [
|
||||
'artwork_id' => $artwork->id,
|
||||
'error' => $exception->getMessage(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => 'Revision update failed: ' . $exception->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/studio/artworks/{id}/versions
|
||||
* Return version history for an artwork (newest first).
|
||||
@@ -615,6 +871,7 @@ final class StudioArtworksApiController extends Controller
|
||||
'width' => $v->width,
|
||||
'height' => $v->height,
|
||||
'file_size' => $v->file_size,
|
||||
'file_name' => (string) data_get($v->snapshot_json, 'artwork.file_name', ''),
|
||||
'change_note' => $v->change_note,
|
||||
'is_current' => $v->is_current,
|
||||
'created_at' => $v->created_at?->toIso8601String(),
|
||||
@@ -637,14 +894,6 @@ final class StudioArtworksApiController extends Controller
|
||||
|
||||
try {
|
||||
$newVersion = $this->versioningService->restoreVersion($version, $artwork, $request->user()->id);
|
||||
|
||||
// Sync artwork file fields back to restored version dimensions
|
||||
$artwork->update([
|
||||
'width' => max(1, (int) $version->width),
|
||||
'height' => max(1, (int) $version->height),
|
||||
'file_size' => (int) $version->file_size,
|
||||
]);
|
||||
|
||||
$artwork->refresh();
|
||||
|
||||
// Reindex
|
||||
@@ -656,7 +905,7 @@ final class StudioArtworksApiController extends Controller
|
||||
'success' => true,
|
||||
'version_number' => $newVersion->version_number,
|
||||
'message' => "Version {$version->version_number} has been restored as version {$newVersion->version_number}.",
|
||||
]);
|
||||
] + $this->mediaPayload($artwork));
|
||||
} catch (TooManyRequestsHttpException $e) {
|
||||
return response()->json(['success' => false, 'error' => $e->getMessage()], 429);
|
||||
} catch (\Throwable $e) {
|
||||
@@ -681,4 +930,138 @@ final class StudioArtworksApiController extends Controller
|
||||
Log::warning('CDN cache purge failed', ['artwork_id' => $artwork->id, 'error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $snapshot
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
private function snapshotFilesByVariant(array $snapshot): array
|
||||
{
|
||||
return collect($snapshot['files'] ?? [])
|
||||
->filter(fn ($file): bool => is_array($file) && (string) ($file['variant'] ?? '') !== '')
|
||||
->mapWithKeys(fn (array $file): array => [(string) $file['variant'] => $file])
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $snapshot
|
||||
* @return array<string, array{path: string, mime: string, size: int}>
|
||||
*/
|
||||
private function currentPublicAssetsFromSnapshot(array $snapshot): array
|
||||
{
|
||||
return collect($snapshot['files'] ?? [])
|
||||
->filter(fn ($file): bool => is_array($file) && in_array((string) ($file['variant'] ?? ''), ['xs', 'sm', 'md', 'lg', 'xl', 'sq'], true))
|
||||
->mapWithKeys(fn (array $file): array => [
|
||||
(string) $file['variant'] => [
|
||||
'path' => (string) ($file['path'] ?? ''),
|
||||
'mime' => (string) ($file['mime'] ?? 'image/webp'),
|
||||
'size' => (int) ($file['size'] ?? 0),
|
||||
],
|
||||
])
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $file
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
private function existingVariantDescriptor(?array $file, string $fallbackVariant): ?array
|
||||
{
|
||||
if (! is_array($file) || (string) ($file['path'] ?? '') === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$path = (string) ($file['path'] ?? '');
|
||||
|
||||
return [
|
||||
'variant' => $fallbackVariant !== '' ? $fallbackVariant : (string) ($file['variant'] ?? ''),
|
||||
'path' => $path,
|
||||
'mime' => (string) ($file['mime'] ?? 'application/octet-stream'),
|
||||
'size' => (int) ($file['size'] ?? 0),
|
||||
'ext' => strtolower((string) pathinfo($path, PATHINFO_EXTENSION)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $stored
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function storedOriginalDescriptor(array $stored, string $variant): array
|
||||
{
|
||||
return [
|
||||
'variant' => $variant,
|
||||
'path' => (string) ($stored['object_path'] ?? ''),
|
||||
'mime' => (string) ($stored['mime'] ?? 'application/octet-stream'),
|
||||
'size' => (int) ($stored['size'] ?? 0),
|
||||
'ext' => strtolower((string) ($stored['ext'] ?? pathinfo((string) ($stored['object_path'] ?? ''), PATHINFO_EXTENSION))),
|
||||
];
|
||||
}
|
||||
|
||||
private function resolvePreferredFileName(?string $preferredFileName, string $ext): string
|
||||
{
|
||||
$candidate = basename(str_replace('\\', '/', (string) ($preferredFileName ?? '')));
|
||||
$candidate = preg_replace('/[\x00-\x1F\x7F]/', '', (string) $candidate) ?? '';
|
||||
$candidate = trim((string) $candidate);
|
||||
|
||||
if ($candidate === '') {
|
||||
$candidate = 'artwork';
|
||||
}
|
||||
|
||||
$candidateExt = strtolower((string) pathinfo($candidate, PATHINFO_EXTENSION));
|
||||
if ($candidateExt === '' && $ext !== '') {
|
||||
$candidate .= '.' . ltrim($ext, '.');
|
||||
}
|
||||
|
||||
return $candidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function mediaPayload(Artwork $artwork): array
|
||||
{
|
||||
$artwork->refresh();
|
||||
$snapshot = $this->versioningService->captureArtworkSnapshot($artwork);
|
||||
$filesByVariant = $this->snapshotFilesByVariant($snapshot);
|
||||
|
||||
return [
|
||||
'thumb_url' => $artwork->thumbUrl('md'),
|
||||
'thumb_url_lg' => $artwork->thumbUrl('lg'),
|
||||
'width' => (int) ($artwork->width ?? 0),
|
||||
'height' => (int) ($artwork->height ?? 0),
|
||||
'file_size' => (int) ($artwork->file_size ?? 0),
|
||||
'file_name' => (string) ($artwork->file_name ?? ''),
|
||||
'file_ext' => (string) ($artwork->file_ext ?? ''),
|
||||
'mime_type' => (string) ($artwork->mime_type ?? ''),
|
||||
'has_archive_file' => array_key_exists('orig_archive', $filesByVariant),
|
||||
'screenshots' => $this->screenshotAssetsFromSnapshot($snapshot),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $snapshot
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function screenshotAssetsFromSnapshot(array $snapshot): array
|
||||
{
|
||||
$base = rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/');
|
||||
|
||||
return collect($snapshot['files'] ?? [])
|
||||
->filter(fn ($file): bool => is_array($file) && str_starts_with((string) ($file['variant'] ?? ''), 'shot') && (string) ($file['path'] ?? '') !== '')
|
||||
->values()
|
||||
->map(function (array $file, int $index) use ($base): array {
|
||||
$path = trim((string) ($file['path'] ?? ''), '/');
|
||||
$url = $base . '/' . $path;
|
||||
|
||||
return [
|
||||
'id' => (string) ($file['variant'] ?? ('shot' . ($index + 1))),
|
||||
'label' => 'Screenshot ' . ($index + 1),
|
||||
'url' => $url,
|
||||
'thumb_url' => $url,
|
||||
'mime_type' => (string) ($file['mime'] ?? 'image/jpeg'),
|
||||
'size' => (int) ($file['size'] ?? 0),
|
||||
];
|
||||
})
|
||||
->all();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user