find($uploadId); if (! $upload) { throw new UploadNotFoundException('Upload not found.'); } $this->validateBeforePublish($upload, $user); $mainFile = DB::table('upload_files') ->where('upload_id', $uploadId) ->where('type', 'main') ->orderBy('id') ->first(['path', 'hash']); if (! $mainFile || empty($mainFile->hash)) { throw new UploadPublishValidationException('Main file hash is missing.'); } $hash = strtolower((string) preg_replace('/[^a-z0-9]/', '', (string) $mainFile->hash)); if ($hash === '' || strlen($hash) < 4) { throw new UploadPublishValidationException('Invalid main file hash.'); } $aa = substr($hash, 0, 2); $bb = substr($hash, 2, 2); $targetDir = "files/artworks/{$aa}/{$bb}/{$hash}"; $tempPrefix = 'tmp/drafts/' . $uploadId . '/'; $promoted = false; try { DB::beginTransaction(); $this->fileMoveService->promoteDraft($uploadId, $targetDir); $promoted = true; $files = DB::table('upload_files')->where('upload_id', $uploadId)->get(['id', 'path']); foreach ($files as $file) { $oldPath = (string) $file->path; if (str_starts_with($oldPath, $tempPrefix)) { $newPath = $targetDir . '/' . ltrim(substr($oldPath, strlen($tempPrefix)), '/'); DB::table('upload_files')->where('id', $file->id)->update(['path' => $newPath]); } } $upload->status = 'published'; $upload->processing_state = 'published'; if (empty($upload->slug)) { $upload->slug = $this->slugService->makeSlug((string) ($upload->title ?? '')); } $upload->published_at = now(); $upload->final_path = $targetDir; $upload->save(); DB::commit(); } catch (\Throwable $e) { DB::rollBack(); if ($promoted) { Storage::disk('local')->deleteDirectory($targetDir); } throw $e; } Storage::disk('local')->deleteDirectory('tmp/drafts/' . $uploadId); return Upload::query()->findOrFail($uploadId); } private function validateBeforePublish(Upload $upload, User $user): void { if ((int) $upload->user_id !== (int) $user->id) { throw new UploadOwnershipException('You do not own this upload.'); } $role = strtolower((string) ($user->role ?? '')); $isAdmin = $role === 'admin'; if (! $isAdmin && (string) ($upload->moderation_status ?? 'pending') !== 'approved') { throw new UploadPublishValidationException('Upload requires moderation approval before publish.'); } if ((string) $upload->status !== 'draft') { throw new UploadPublishValidationException('Only draft uploads can be published.'); } if (! (bool) $upload->is_scanned) { throw new UploadPublishValidationException('Upload must be scanned before publish.'); } if (empty($upload->preview_path)) { throw new UploadPublishValidationException('Preview is required before publish.'); } if (! (bool) $upload->has_tags) { throw new UploadPublishValidationException('Tag analysis must complete before publish.'); } if (empty($upload->title) || empty($upload->category_id)) { throw new UploadPublishValidationException('Title and category are required before publish.'); } if ((string) $upload->type === 'archive') { $hasScreenshot = DB::table('upload_files') ->where('upload_id', (string) $upload->id) ->where('type', 'screenshot') ->exists(); if (! $hasScreenshot) { throw new UploadPublishValidationException('Archive uploads require at least one screenshot.'); } } } }