@@ -3,21 +3,22 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Opportunity;
|
||||
use App\Services\CountryCatalogue;
|
||||
use App\Services\InvestmentGeography;
|
||||
use App\Services\OpportunityImageProcessor;
|
||||
use App\Services\OpportunityManagement;
|
||||
use App\Services\OpportunityResourceInstallation;
|
||||
use App\Services\OpportunityResources;
|
||||
use App\Services\PortalProfileAccess;
|
||||
use App\Services\VerifiedDocumentBinary;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
use Symfony\Component\HttpFoundation\HeaderUtils;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
class OpportunityResourceController extends Controller
|
||||
{
|
||||
@@ -40,9 +41,12 @@ class OpportunityResourceController extends Controller
|
||||
public function index(int $id, OpportunityResources $service): Response
|
||||
{
|
||||
$opportunity = Opportunity::withTrashed()->findOrFail($id);
|
||||
app(PortalProfileAccess::class)->authorize(request()->user(), 'view', $opportunity);
|
||||
Inertia::share(['basePath' => str_starts_with(request()->path(), 'minhas-oportunidades') ? '/minhas-oportunidades' : '/management/opportunities', 'readOnly' => ! app(PortalProfileAccess::class)->canEdit(request()->user(), $opportunity)]);
|
||||
abort_unless($service->installed(), 503, 'Recursos ainda não instalados; consulte o plano autenticado.');
|
||||
$resources = [];
|
||||
Inertia::share('imageProcessingAvailable', app(OpportunityImageProcessor::class)->available());
|
||||
Inertia::share('countries', app(CountryCatalogue::class)->all());
|
||||
foreach (OpportunityResources::KINDS as $kind) {
|
||||
$resources[$kind] = $service->listing($id, $kind);
|
||||
}
|
||||
@@ -54,7 +58,7 @@ class OpportunityResourceController extends Controller
|
||||
$image->derivativePreviews = [];
|
||||
foreach ($image->payload['derivatives'] ?? [] as $derived) {
|
||||
if (DB::table('documents')->where('id', $derived['document_id'])->where('status', 'active')->where('malware_scan_status', 'clean')->whereColumn('sha256', 'malware_scanned_sha256')->exists()) {
|
||||
$image->derivativePreviews[] = [...$derived, 'url' => URL::temporarySignedRoute('management.opportunity-image-preview', now()->addMinutes(10), ['id' => $id, 'resourceId' => $image->id, 'documentId' => $derived['document_id']])];
|
||||
$image->derivativePreviews[] = [...$derived, 'url' => URL::temporarySignedRoute(str_starts_with(request()->path(), 'minhas-oportunidades') ? 'promoter.opportunity-image-preview' : 'management.opportunity-image-preview', now()->addMinutes(10), ['id' => $id, 'resourceId' => $image->id, 'documentId' => $derived['document_id']])];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +91,7 @@ class OpportunityResourceController extends Controller
|
||||
return response()->json(['message' => 'Derivadas WebP geradas sem EXIF original; scan independente e revisão de divulgação pendentes.']);
|
||||
}
|
||||
|
||||
public function previewImage(int $id, string $resourceId, string $documentId): \Illuminate\Http\Response
|
||||
public function previewImage(int $id, string $resourceId, string $documentId): \Illuminate\Http\Response|BinaryFileResponse
|
||||
{
|
||||
$o = Opportunity::findOrFail($id);
|
||||
abort_if($o->status === 'archived', 404);
|
||||
@@ -97,26 +101,20 @@ class OpportunityResourceController extends Controller
|
||||
abort_unless(in_array($documentId, array_column($metadata['derivatives'] ?? [], 'document_id'), true), 404);
|
||||
$doc = DB::table('documents')->where('id', $documentId)->whereNull('deleted_at')->first();
|
||||
abort_unless($doc && $doc->status === 'active' && $doc->malware_scan_status === 'clean' && hash_equals(trim($doc->sha256), (string) $doc->malware_scanned_sha256), 423, 'Scan da derivada pendente.');
|
||||
$bytes = Crypt::decryptString(Storage::disk('local')->get($doc->storage_key));
|
||||
abort_unless(hash_equals(trim($doc->sha256), hash('sha256', $bytes)), 409, 'Integridade divergente.');
|
||||
|
||||
return response($bytes)->header('Content-Type', 'image/webp')->header('X-Content-Type-Options', 'nosniff')->header('Cache-Control', 'private,no-store');
|
||||
return app(VerifiedDocumentBinary::class)->respond($doc, true);
|
||||
}
|
||||
|
||||
public function download(int $id, string $kind, string $resourceId, OpportunityResources $service): \Illuminate\Http\Response
|
||||
public function download(int $id, string $kind, string $resourceId, OpportunityResources $service): \Illuminate\Http\Response|BinaryFileResponse
|
||||
{
|
||||
abort_unless(in_array($kind, ['documents', 'images'], true), 404);
|
||||
$opportunity = Opportunity::findOrFail($id);
|
||||
abort_if($opportunity->status === 'archived', 404);
|
||||
$record = DB::table($service->table($kind).' as r')->join('documents as d', 'd.id', '=', 'r.document_id')->where('r.id', $resourceId)->where('r.opportunity_id', $id)->whereNull('r.deleted_at')->whereNull('d.deleted_at')->first(['d.*']);
|
||||
abort_unless($record && $record->status === 'active' && $record->malware_scan_status === 'clean' && hash_equals(trim($record->sha256), (string) $record->malware_scanned_sha256), 423, 'Scan pendente, inconclusivo ou inseguro. O ficheiro permanece em quarentena.');
|
||||
$disk = Storage::disk('local');
|
||||
$path = $disk->path($record->storage_key);
|
||||
abort_unless($disk->exists($record->storage_key), 409, 'Ficheiro indisponível.');
|
||||
$bytes = $record->storage_encryption === 'laravel' ? Crypt::decryptString($disk->get($record->storage_key)) : $disk->get($record->storage_key);
|
||||
abort_unless(hash_equals(trim($record->sha256), hash('sha256', $bytes)), 409, 'Integridade do ficheiro divergente.');
|
||||
abort_unless($record !== null, 404, 'Recurso indisponível ou excluído logicamente.');
|
||||
abort_unless($record->status === 'active' && $record->malware_scan_status === 'clean' && hash_equals(trim($record->sha256), (string) $record->malware_scanned_sha256), 423, 'Scan pendente, inconclusivo ou inseguro. O ficheiro permanece em quarentena.');
|
||||
|
||||
return response($bytes)->header('Content-Type', $record->media_type)->header('Content-Disposition', HeaderUtils::makeDisposition('attachment', $record->file_name, 'document'))->header('X-Content-Type-Options', 'nosniff')->header('Cache-Control', 'no-store');
|
||||
return app(VerifiedDocumentBinary::class)->respond($record);
|
||||
}
|
||||
|
||||
/** @param array<string,list<\stdClass>> $resources
|
||||
|
||||
Reference in New Issue
Block a user