141 lines
4.7 KiB
PHP
141 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Uploads\Services;
|
|
|
|
use App\Models\Upload;
|
|
use App\Models\User;
|
|
use App\Uploads\Exceptions\UploadNotFoundException;
|
|
use App\Uploads\Exceptions\UploadOwnershipException;
|
|
use App\Uploads\Exceptions\UploadPublishValidationException;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use RuntimeException;
|
|
|
|
final class PublishService
|
|
{
|
|
public function __construct(
|
|
private readonly FileMoveService $fileMoveService,
|
|
private readonly SlugService $slugService,
|
|
)
|
|
{
|
|
}
|
|
|
|
public function publish(string $uploadId, User $user): Upload
|
|
{
|
|
$upload = Upload::query()->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.');
|
|
}
|
|
}
|
|
}
|
|
}
|