mirror of
https://github.com/dyzulk/trustlab-api.git
synced 2026-01-26 13:22:05 +07:00
fix: manual streaming response for attachments
This commit is contained in:
@@ -13,46 +13,63 @@ class AttachmentController extends Controller
|
|||||||
* Download a private attachment.
|
* Download a private attachment.
|
||||||
*/
|
*/
|
||||||
public function download(Request $request, TicketAttachment $attachment)
|
public function download(Request $request, TicketAttachment $attachment)
|
||||||
|
{
|
||||||
|
public function download(Request $request, TicketAttachment $attachment)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
// Paranoid Auth Check
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
if (!$user) {
|
||||||
|
return response()->json(['error' => 'Unauthenticated'], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$attachment->reply) {
|
||||||
|
return response()->json(['error' => 'Orphaned Attachment (No Reply)'], 404);
|
||||||
|
}
|
||||||
|
if (!$attachment->reply->ticket) {
|
||||||
|
return response()->json(['error' => 'Orphaned Attachment (No Ticket)'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Authorization Logic
|
|
||||||
$attachment->load(['reply.ticket']);
|
|
||||||
$ticket = $attachment->reply->ticket;
|
$ticket = $attachment->reply->ticket;
|
||||||
|
|
||||||
if ($ticket->user_id !== $user->id && !$user->isAdminOrOwner()) {
|
if ($ticket->user_id !== $user->id && !$user->isAdminOrOwner()) {
|
||||||
abort(403, 'Unauthorized access to this attachment.');
|
return response()->json(['error' => 'Unauthorized'], 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Fetch File
|
|
||||||
$path = $attachment->file_path;
|
$path = $attachment->file_path;
|
||||||
|
|
||||||
|
// Legacy URL handling
|
||||||
if (filter_var($path, FILTER_VALIDATE_URL)) {
|
if (filter_var($path, FILTER_VALIDATE_URL)) {
|
||||||
return redirect($path);
|
return redirect($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
$disk = 'r2-private';
|
$disk = 'r2-private';
|
||||||
|
|
||||||
|
// Use manual file retrieval to avoid header issues with Storage::download
|
||||||
if (!Storage::disk($disk)->exists($path)) {
|
if (!Storage::disk($disk)->exists($path)) {
|
||||||
\Log::error("Attachment 404: Path [$path] not found on disk [$disk]");
|
return response()->json(['error' => 'File not found on storage'], 404);
|
||||||
abort(404, 'File not found on secure storage.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Storage::disk($disk)->download($path, $attachment->file_name);
|
$mimeType = Storage::disk($disk)->mimeType($path) ?? 'application/octet-stream';
|
||||||
|
$size = Storage::disk($disk)->size($path);
|
||||||
|
$fileName = $attachment->file_name ?? basename($path);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
return response()->stream(function() use ($disk, $path) {
|
||||||
\Log::error("Attachment Download Error: " . $e->getMessage(), [
|
$stream = Storage::disk($disk)->readStream($path);
|
||||||
'attachment_id' => $attachment->id,
|
fpassthru($stream);
|
||||||
'path' => $attachment->file_path ?? 'unknown',
|
if (is_resource($stream)) {
|
||||||
'trace' => $e->getTraceAsString()
|
fclose($stream);
|
||||||
|
}
|
||||||
|
}, 200, [
|
||||||
|
'Content-Type' => $mimeType,
|
||||||
|
'Content-Length' => $size,
|
||||||
|
'Content-Disposition' => 'attachment; filename="' . $fileName . '"',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json([
|
} catch (\Exception $e) {
|
||||||
'error' => 'Server Error',
|
\Log::error("Stream Download Error: " . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
|
||||||
'message' => $e->getMessage(),
|
return response()->json(['error' => 'Server Error', 'message' => $e->getMessage()], 500);
|
||||||
'file_path' => $attachment->file_path ?? 'unknown'
|
}
|
||||||
], 500);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user