Upload beautify

This commit is contained in:
2026-02-14 15:14:12 +01:00
parent e129618910
commit 79192345e3
249 changed files with 24436 additions and 1021 deletions

View File

@@ -0,0 +1,140 @@
<?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.');
}
}
}
}