Files
ladill-transfer/app/Http/Controllers/Api/ChunkedUploadController.php
T
isaaccladandCursor c396c97ce2
Deploy Ladill Transfer / deploy (push) Successful in 36s
Fix transfer share links and redesign recipient download email.
Use transfer.ladill.com share URLs in notifications and APIs, add the email
logo, and show file details with a prominent download button.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 15:56:06 +00:00

158 lines
5.5 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\Identity\MailboxUserResolver;
use App\Services\Transfer\TransferService;
use App\Services\Upload\ChunkedUploadService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use RuntimeException;
class ChunkedUploadController extends Controller
{
public function __construct(
private ChunkedUploadService $uploads,
private TransferService $transfers,
private MailboxUserResolver $users,
) {}
public function init(Request $request): JsonResponse
{
if ($request->attributes->get('service_caller') !== 'webmail') {
return response()->json(['message' => 'Forbidden.'], 403);
}
$validated = $request->validate([
'mailbox' => ['required', 'email', 'max:255'],
'filename' => ['required', 'string', 'max:255'],
'filesize' => ['required', 'integer', 'min:1'],
]);
$mailbox = strtolower(trim((string) $validated['mailbox']));
try {
$this->users->resolve($mailbox);
} catch (RuntimeException $e) {
return response()->json(['message' => $e->getMessage()], 422);
}
$uploadId = $this->uploads->init($this->ownerKey($mailbox), [
'filename' => $validated['filename'],
'filesize' => (int) $validated['filesize'],
'mailbox' => $mailbox,
]);
return response()->json([
'success' => true,
'upload_id' => $uploadId,
]);
}
public function chunk(Request $request): JsonResponse
{
if ($request->attributes->get('service_caller') !== 'webmail') {
return response()->json(['message' => 'Forbidden.'], 403);
}
$validated = $request->validate([
'mailbox' => ['required', 'email', 'max:255'],
'upload_id' => ['required', 'string', 'uuid'],
'chunk_index' => ['required', 'integer', 'min:0'],
'chunk_size' => ['required', 'integer', 'min:1'],
'chunk' => ['required', 'file', 'max:10240'],
]);
$mailbox = strtolower(trim((string) $validated['mailbox']));
$chunk = $request->file('chunk');
if ($chunk === null) {
return response()->json(['success' => false, 'error' => 'Chunk missing.'], 422);
}
try {
$received = $this->uploads->receiveChunk(
$validated['upload_id'],
$this->ownerKey($mailbox),
(int) $validated['chunk_index'],
$chunk,
(int) $validated['chunk_size'],
);
} catch (RuntimeException $e) {
return response()->json(['success' => false, 'error' => $e->getMessage()], 400);
}
return response()->json([
'success' => true,
'chunk_index' => (int) $validated['chunk_index'],
'chunks_received' => $received,
]);
}
public function finalize(Request $request): JsonResponse
{
if ($request->attributes->get('service_caller') !== 'webmail') {
return response()->json(['message' => 'Forbidden.'], 403);
}
$validated = $request->validate([
'mailbox' => ['required', 'email', 'max:255'],
'upload_id' => ['required', 'string', 'uuid'],
'total_chunks' => ['required', 'integer', 'min:1'],
'title' => ['nullable', 'string', 'max:120'],
'retention_days' => ['nullable', 'integer', 'min:1', 'max:365'],
]);
$mailbox = strtolower(trim((string) $validated['mailbox']));
$ownerKey = $this->ownerKey($mailbox);
try {
$user = $this->users->resolve($mailbox);
$assembled = $this->uploads->finalize(
$validated['upload_id'],
$ownerKey,
(int) $validated['total_chunks'],
);
$uploadedFile = $this->uploads->toUploadedFile($validated['upload_id'], $ownerKey);
$title = trim((string) ($validated['title'] ?? ''));
if ($title === '') {
$title = Str::limit('Email attachment: '.$assembled['filename'], 120, '');
}
$retentionDays = (int) ($validated['retention_days'] ?? config('transfer.mail_retention_days', config('transfer.default_retention_days', 30)));
$transfer = $this->transfers->create($user, [
'title' => $title,
'retention_days' => $retentionDays,
], [$uploadedFile]);
} catch (RuntimeException $e) {
return response()->json(['message' => $e->getMessage()], 422);
} finally {
$this->uploads->cleanup($validated['upload_id']);
}
$transfer->load(['files', 'qrCode']);
return response()->json([
'data' => [
'id' => $transfer->id,
'title' => $transfer->title,
'public_url' => $transfer->qrCode?->shareUrl(),
'paid_until' => $transfer->paid_until?->toIso8601String(),
'expires_at' => $transfer->paid_until?->toIso8601String(),
'files' => $transfer->files->map(fn ($file) => [
'name' => $file->original_name,
'size_bytes' => $file->size_bytes,
])->values(),
],
], 201);
}
private function ownerKey(string $mailbox): string
{
return 'webmail:'.strtolower(trim($mailbox));
}
}