Upload beautify
This commit is contained in:
150
app/Services/Uploads/UploadPipelineService.php
Normal file
150
app/Services/Uploads/UploadPipelineService.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Uploads;
|
||||
|
||||
use App\DTOs\Uploads\UploadSessionData;
|
||||
use App\DTOs\Uploads\UploadInitResult;
|
||||
use App\DTOs\Uploads\UploadValidatedFile;
|
||||
use App\DTOs\Uploads\UploadScanResult;
|
||||
use App\Repositories\Uploads\ArtworkFileRepository;
|
||||
use App\Repositories\Uploads\UploadSessionRepository;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
final class UploadPipelineService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UploadStorageService $storage,
|
||||
private readonly UploadSessionRepository $sessions,
|
||||
private readonly UploadValidationService $validator,
|
||||
private readonly UploadHashService $hasher,
|
||||
private readonly UploadScanService $scanner,
|
||||
private readonly UploadAuditService $audit,
|
||||
private readonly UploadDerivativesService $derivatives,
|
||||
private readonly ArtworkFileRepository $artworkFiles,
|
||||
private readonly UploadTokenService $tokens
|
||||
) {
|
||||
}
|
||||
|
||||
public function initSession(int $userId, string $ip): UploadInitResult
|
||||
{
|
||||
$dir = $this->storage->ensureSection('tmp');
|
||||
$filename = Str::uuid()->toString() . '.upload';
|
||||
$tempPath = $dir . DIRECTORY_SEPARATOR . $filename;
|
||||
|
||||
File::put($tempPath, '');
|
||||
|
||||
$sessionId = (string) Str::uuid();
|
||||
$session = $this->sessions->create($sessionId, $userId, $tempPath, UploadSessionStatus::INIT, $ip);
|
||||
$token = $this->tokens->generate($sessionId, $userId);
|
||||
|
||||
$this->audit->log($userId, 'upload_init', $ip, [
|
||||
'session_id' => $sessionId,
|
||||
]);
|
||||
|
||||
return new UploadInitResult($session->id, $token, $session->status);
|
||||
}
|
||||
|
||||
public function receiveToTmp(UploadedFile $file, int $userId, string $ip): UploadSessionData
|
||||
{
|
||||
$stored = $this->storage->storeUploadedFile($file, 'tmp');
|
||||
$sessionId = (string) Str::uuid();
|
||||
$session = $this->sessions->create($sessionId, $userId, $stored->path, UploadSessionStatus::TMP, $ip);
|
||||
$this->sessions->updateProgress($sessionId, 10);
|
||||
|
||||
$this->audit->log($userId, 'upload_received', $ip, [
|
||||
'session_id' => $sessionId,
|
||||
'size' => $stored->size,
|
||||
]);
|
||||
|
||||
return $session;
|
||||
}
|
||||
|
||||
public function validateAndHash(string $sessionId): UploadValidatedFile
|
||||
{
|
||||
$session = $this->sessions->getOrFail($sessionId);
|
||||
$validation = $this->validator->validate($session->tempPath);
|
||||
|
||||
if (! $validation->ok) {
|
||||
$this->quarantine($session, $validation->reason);
|
||||
return new UploadValidatedFile($validation, null);
|
||||
}
|
||||
|
||||
$hash = $this->hasher->hashFile($session->tempPath);
|
||||
$this->sessions->updateStatus($sessionId, UploadSessionStatus::VALIDATED);
|
||||
$this->sessions->updateProgress($sessionId, 30);
|
||||
$this->audit->log($session->userId, 'upload_validated', $session->ip, [
|
||||
'session_id' => $sessionId,
|
||||
'hash' => $hash,
|
||||
]);
|
||||
|
||||
return new UploadValidatedFile($validation, $hash);
|
||||
}
|
||||
|
||||
public function scan(string $sessionId): UploadScanResult
|
||||
{
|
||||
$session = $this->sessions->getOrFail($sessionId);
|
||||
$result = $this->scanner->scan($session->tempPath);
|
||||
|
||||
if (! $result->ok) {
|
||||
$this->quarantine($session, $result->reason);
|
||||
return $result;
|
||||
}
|
||||
|
||||
$this->sessions->updateStatus($sessionId, UploadSessionStatus::SCANNED);
|
||||
$this->sessions->updateProgress($sessionId, 50);
|
||||
$this->audit->log($session->userId, 'upload_scanned', $session->ip, [
|
||||
'session_id' => $sessionId,
|
||||
]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function processAndPublish(string $sessionId, string $hash, int $artworkId): array
|
||||
{
|
||||
$session = $this->sessions->getOrFail($sessionId);
|
||||
|
||||
$originalPath = $this->derivatives->storeOriginal($session->tempPath, $hash);
|
||||
$originalRelative = $this->storage->sectionRelativePath('originals', $hash, 'orig.webp');
|
||||
$this->artworkFiles->upsert($artworkId, 'orig', $originalRelative, 'image/webp', (int) filesize($originalPath));
|
||||
|
||||
$publicAbsolute = $this->derivatives->generatePublicDerivatives($session->tempPath, $hash);
|
||||
$publicRelative = [];
|
||||
|
||||
foreach ($publicAbsolute as $variant => $absolutePath) {
|
||||
$filename = $variant . '.webp';
|
||||
$relativePath = $this->storage->publicRelativePath($hash, $filename);
|
||||
$this->artworkFiles->upsert($artworkId, $variant, $relativePath, 'image/webp', (int) filesize($absolutePath));
|
||||
$publicRelative[$variant] = $relativePath;
|
||||
}
|
||||
|
||||
$this->sessions->updateStatus($sessionId, UploadSessionStatus::PROCESSED);
|
||||
$this->sessions->updateProgress($sessionId, 100);
|
||||
$this->audit->log($session->userId, 'upload_processed', $session->ip, [
|
||||
'session_id' => $sessionId,
|
||||
'hash' => $hash,
|
||||
'artwork_id' => $artworkId,
|
||||
]);
|
||||
|
||||
return [
|
||||
'orig' => $originalRelative,
|
||||
'public' => $publicRelative,
|
||||
];
|
||||
}
|
||||
|
||||
private function quarantine(UploadSessionData $session, string $reason): void
|
||||
{
|
||||
$newPath = $this->storage->moveToSection($session->tempPath, 'quarantine');
|
||||
$this->sessions->updateTempPath($session->id, $newPath);
|
||||
$this->sessions->updateStatus($session->id, UploadSessionStatus::QUARANTINED);
|
||||
$this->sessions->updateFailureReason($session->id, $reason);
|
||||
$this->sessions->updateProgress($session->id, 0);
|
||||
$this->audit->log($session->userId, 'upload_quarantined', $session->ip, [
|
||||
'session_id' => $session->id,
|
||||
'reason' => $reason,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user