Implement creator studio and upload updates
This commit is contained in:
@@ -105,6 +105,13 @@ final class ArtworkDownloadController extends Controller
|
||||
*/
|
||||
private function resolveDownloadUrl(Artwork $artwork): string
|
||||
{
|
||||
$filePath = trim((string) ($artwork->file_path ?? ''), '/');
|
||||
$cdn = rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/');
|
||||
|
||||
if ($filePath !== '') {
|
||||
return $cdn . '/' . $filePath;
|
||||
}
|
||||
|
||||
$hash = $artwork->hash ?? null;
|
||||
$ext = ltrim((string) ($artwork->file_ext ?: $artwork->thumb_ext ?: 'webp'), '.');
|
||||
|
||||
@@ -112,9 +119,9 @@ final class ArtworkDownloadController extends Controller
|
||||
$h = strtolower(preg_replace('/[^a-f0-9]/', '', $hash));
|
||||
$h1 = substr($h, 0, 2);
|
||||
$h2 = substr($h, 2, 2);
|
||||
$cdn = rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/');
|
||||
$prefix = trim((string) config('uploads.object_storage.prefix', 'artworks'), '/');
|
||||
|
||||
return sprintf('%s/original/%s/%s/%s.%s', $cdn, $h1, $h2, $h, $ext);
|
||||
return sprintf('%s/%s/original/%s/%s/%s.%s', $cdn, $prefix, $h1, $h2, $h, $ext);
|
||||
}
|
||||
|
||||
// Fallback: best available thumbnail size
|
||||
|
||||
@@ -16,6 +16,7 @@ use App\Services\NovaCards\NovaCardDraftService;
|
||||
use App\Services\NovaCards\NovaCardPresenter;
|
||||
use App\Services\NovaCards\NovaCardPublishService;
|
||||
use App\Services\NovaCards\NovaCardRenderService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -115,9 +116,10 @@ class NovaCardDraftController extends Controller
|
||||
public function publish(SaveNovaCardDraftRequest $request, int $id): JsonResponse
|
||||
{
|
||||
$card = $this->editableCard($request, $id);
|
||||
$validated = $request->validated();
|
||||
|
||||
if ($request->validated() !== []) {
|
||||
$card = $this->drafts->autosave($card, $request->validated());
|
||||
if ($validated !== []) {
|
||||
$card = $this->drafts->autosave($card, $validated);
|
||||
}
|
||||
|
||||
if (trim((string) $card->title) === '' || trim((string) $card->quote_text) === '') {
|
||||
@@ -126,6 +128,32 @@ class NovaCardDraftController extends Controller
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$publishMode = (string) ($validated['publish_mode'] ?? 'now');
|
||||
|
||||
if ($publishMode === 'schedule') {
|
||||
if (empty($validated['scheduled_for'])) {
|
||||
return response()->json([
|
||||
'message' => 'Choose a date and time for scheduled publishing.',
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
try {
|
||||
$card = $this->publishes->schedule(
|
||||
$card->loadMissing('backgroundImage'),
|
||||
Carbon::parse((string) $validated['scheduled_for']),
|
||||
isset($validated['scheduling_timezone']) ? (string) $validated['scheduling_timezone'] : null,
|
||||
);
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
return response()->json([
|
||||
'message' => $exception->getMessage(),
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'data' => $this->presenter->card($card, true, $request->user()),
|
||||
]);
|
||||
}
|
||||
|
||||
$card = $this->publishes->publishNow($card->loadMissing('backgroundImage'));
|
||||
event(new NovaCardPublished($card));
|
||||
|
||||
|
||||
@@ -83,6 +83,11 @@ final class UploadController extends Controller
|
||||
$sessionId = (string) $request->validated('session_id');
|
||||
$artworkId = (int) $request->validated('artwork_id');
|
||||
$originalFileName = $request->validated('file_name');
|
||||
$archiveSessionId = $request->validated('archive_session_id');
|
||||
$archiveOriginalFileName = $request->validated('archive_file_name');
|
||||
$additionalScreenshotSessions = collect($request->validated('additional_screenshot_sessions', []))
|
||||
->filter(fn ($payload) => is_array($payload) && is_string($payload['session_id'] ?? null))
|
||||
->values();
|
||||
|
||||
$session = $sessions->getOrFail($sessionId);
|
||||
|
||||
@@ -112,14 +117,81 @@ final class UploadController extends Controller
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$validatedArchive = null;
|
||||
if (is_string($archiveSessionId) && trim($archiveSessionId) !== '') {
|
||||
$validatedArchive = $pipeline->validateAndHashArchive($archiveSessionId);
|
||||
if (! $validatedArchive->validation->ok || ! $validatedArchive->hash) {
|
||||
return response()->json([
|
||||
'message' => 'Archive validation failed.',
|
||||
'reason' => $validatedArchive->validation->reason,
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$archiveScan = $pipeline->scan($archiveSessionId);
|
||||
if (! $archiveScan->ok) {
|
||||
return response()->json([
|
||||
'message' => 'Archive scan failed.',
|
||||
'reason' => $archiveScan->reason,
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
}
|
||||
|
||||
$validatedAdditionalScreenshots = [];
|
||||
foreach ($additionalScreenshotSessions as $payload) {
|
||||
$screenshotSessionId = (string) ($payload['session_id'] ?? '');
|
||||
if ($screenshotSessionId === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$validatedScreenshot = $pipeline->validateAndHash($screenshotSessionId);
|
||||
if (! $validatedScreenshot->validation->ok || ! $validatedScreenshot->hash) {
|
||||
return response()->json([
|
||||
'message' => 'Screenshot validation failed.',
|
||||
'reason' => $validatedScreenshot->validation->reason,
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$screenshotScan = $pipeline->scan($screenshotSessionId);
|
||||
if (! $screenshotScan->ok) {
|
||||
return response()->json([
|
||||
'message' => 'Screenshot scan failed.',
|
||||
'reason' => $screenshotScan->reason,
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$validatedAdditionalScreenshots[] = [
|
||||
'session_id' => $screenshotSessionId,
|
||||
'hash' => $validatedScreenshot->hash,
|
||||
'file_name' => is_string($payload['file_name'] ?? null) ? $payload['file_name'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$status = DB::transaction(function () use ($pipeline, $sessionId, $validated, $artworkId, $originalFileName) {
|
||||
$status = DB::transaction(function () use ($pipeline, $sessionId, $validated, $artworkId, $originalFileName, $archiveSessionId, $validatedArchive, $archiveOriginalFileName, $validatedAdditionalScreenshots) {
|
||||
if ((bool) config('uploads.queue_derivatives', false)) {
|
||||
GenerateDerivativesJob::dispatch($sessionId, $validated->hash, $artworkId, is_string($originalFileName) ? $originalFileName : null)->afterCommit();
|
||||
GenerateDerivativesJob::dispatch(
|
||||
$sessionId,
|
||||
$validated->hash,
|
||||
$artworkId,
|
||||
is_string($originalFileName) ? $originalFileName : null,
|
||||
is_string($archiveSessionId) ? $archiveSessionId : null,
|
||||
$validatedArchive?->hash,
|
||||
is_string($archiveOriginalFileName) ? $archiveOriginalFileName : null,
|
||||
$validatedAdditionalScreenshots
|
||||
)->afterCommit();
|
||||
return 'queued';
|
||||
}
|
||||
|
||||
$pipeline->processAndPublish($sessionId, $validated->hash, $artworkId, is_string($originalFileName) ? $originalFileName : null);
|
||||
$pipeline->processAndPublish(
|
||||
$sessionId,
|
||||
$validated->hash,
|
||||
$artworkId,
|
||||
is_string($originalFileName) ? $originalFileName : null,
|
||||
is_string($archiveSessionId) ? $archiveSessionId : null,
|
||||
$validatedArchive?->hash,
|
||||
is_string($archiveOriginalFileName) ? $archiveOriginalFileName : null,
|
||||
$validatedAdditionalScreenshots
|
||||
);
|
||||
|
||||
// Derivatives are available now; dispatch AI auto-tagging.
|
||||
AutoTagArtworkJob::dispatch($artworkId, $validated->hash)->afterCommit();
|
||||
@@ -132,6 +204,8 @@ final class UploadController extends Controller
|
||||
'hash' => $validated->hash,
|
||||
'artwork_id' => $artworkId,
|
||||
'status' => $status,
|
||||
'archive_session_id' => is_string($archiveSessionId) ? $archiveSessionId : null,
|
||||
'additional_screenshot_session_ids' => array_values(array_map(static fn (array $payload): string => (string) $payload['session_id'], $validatedAdditionalScreenshots)),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
@@ -540,13 +614,6 @@ final class UploadController extends Controller
|
||||
$slugBase = 'artwork';
|
||||
}
|
||||
|
||||
$slug = $slugBase;
|
||||
$suffix = 2;
|
||||
while (Artwork::query()->where('slug', $slug)->where('id', '!=', $artwork->id)->exists()) {
|
||||
$slug = $slugBase . '-' . $suffix;
|
||||
$suffix++;
|
||||
}
|
||||
|
||||
$artwork->title = $title;
|
||||
if (array_key_exists('description', $validated)) {
|
||||
$artwork->description = $validated['description'];
|
||||
@@ -554,7 +621,7 @@ final class UploadController extends Controller
|
||||
if (array_key_exists('is_mature', $validated) || array_key_exists('nsfw', $validated)) {
|
||||
$artwork->is_mature = (bool) ($validated['is_mature'] ?? $validated['nsfw'] ?? false);
|
||||
}
|
||||
$artwork->slug = $slug;
|
||||
$artwork->slug = Str::limit($slugBase, 160, '');
|
||||
$artwork->artwork_timezone = $validated['timezone'] ?? null;
|
||||
|
||||
// Sync category if provided
|
||||
|
||||
Reference in New Issue
Block a user