70 lines
4.4 KiB
PHP
70 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Jobs\ScanDocumentForMalware;
|
|
use App\Models\User;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
|
|
class PartnershipRoomUploads
|
|
{
|
|
/** @return array{uploaded:bool,document_id:string,replayed:bool} */
|
|
public function save(User $actor, int $roomId, UploadedFile $file, bool $shared, ?string $clientKey): array
|
|
{
|
|
$path = $file->getRealPath();
|
|
abort_unless(is_string($path), 503);
|
|
$hash = hash_file('sha256', $path);
|
|
abort_unless(is_string($hash), 503);
|
|
$commandHash = hash('sha256', json_encode(['sha256' => $hash, 'file_name' => basename($file->getClientOriginalName()), 'shared' => $shared], JSON_THROW_ON_ERROR));
|
|
$stored = null;
|
|
try {
|
|
$result = DB::transaction(function () use ($actor, $roomId, $file, $shared, $clientKey, $hash, $commandHash, &$stored) {
|
|
$exchange = app(PartnershipExchange::class);
|
|
$context = $exchange->room($actor, $roomId);
|
|
abort_unless($exchange->owns($actor, $context['partnership']) || $exchange->owns($actor, $context['interest']), 403);
|
|
$room = DB::table('partnership_rooms')->where('id', $roomId)->lockForUpdate()->first();
|
|
abort_unless($room !== null, 404);
|
|
if ($clientKey !== null) {
|
|
$previous = DB::table('partnership_events')->where('partnership_id', $context['partnership']->id)->where('actor_id', $actor->id)->where('event_type', 'partnership.document_added.v1')
|
|
->whereRaw("payload->'payload'->>'room_id' = ?", [(string) $roomId])->whereRaw("payload->'payload'->>'client_key' = ?", [$clientKey])->first(['payload']);
|
|
if ($previous) {
|
|
$payload = json_decode($previous->payload, true, 512, JSON_THROW_ON_ERROR)['payload'];
|
|
abort_unless($payload['upload_hash'] === $commandHash, 409, 'Chave de envio já utilizada com outro documento ou autorização.');
|
|
abort_unless(DB::table('partnership_documents')->where('room_id', $roomId)->where('document_id', $payload['document_id'])->where('owner_id', $actor->id)->exists(), 409, 'O envio original não está disponível para reconciliar.');
|
|
|
|
return ['uploaded' => true, 'document_id' => $payload['document_id'], 'replayed' => true];
|
|
}
|
|
}
|
|
abort_if(in_array($room->status, ['closed', 'cancelled'], true), 409, 'Sala encerrada.');
|
|
$documentId = (string) Str::uuid();
|
|
$stored = $file->storeAs('partnerships/'.$roomId, $documentId, 'local');
|
|
abort_unless(is_string($stored) && $stored !== '', 503);
|
|
abort_unless(hash_file('sha256', Storage::disk('local')->path($stored)) === $hash, 503, 'Os bytes armazenados não correspondem ao envio.');
|
|
DB::table('documents')->insert(['id' => $documentId, 'source_system' => 'partnership-exchange', 'data_classification' => 'restricted', 'file_name' => basename($file->getClientOriginalName()),
|
|
'media_type' => $file->getMimeType(), 'storage_key' => $stored, 'sha256' => $hash, 'status' => 'quarantined', 'size_bytes' => $file->getSize(), 'uploaded_by' => $actor->id]);
|
|
DB::table('partnership_documents')->insert(['room_id' => $roomId, 'document_id' => $documentId, 'owner_id' => $actor->id, 'shared' => $shared]);
|
|
$metadata = ['room_id' => $roomId, 'document_id' => $documentId, 'shared' => $shared];
|
|
if ($clientKey !== null) {
|
|
$metadata += ['client_key' => $clientKey, 'upload_hash' => $commandHash];
|
|
}
|
|
$exchange->event($context['partnership'], $actor, 'partnership.document_added.v1', $metadata, 'Documento privado anexado');
|
|
|
|
return ['uploaded' => true, 'document_id' => $documentId, 'replayed' => false];
|
|
});
|
|
} catch (\Throwable $error) {
|
|
if (is_string($stored) && str_starts_with($stored, 'partnerships/'.$roomId.'/')) {
|
|
Storage::disk('local')->delete($stored);
|
|
}
|
|
throw $error;
|
|
}
|
|
if (! $result['replayed']) {
|
|
ScanDocumentForMalware::dispatch($result['document_id'])->afterCommit();
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|