Add service API to list mailbox files for Ladill Mail.
Deploy Ladill Transfer / deploy (push) Successful in 1m8s

Expose GET /api/v1/transfers/files so webmail can sync the mobile Files tab with Transfer storage.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-14 00:39:37 +00:00
co-authored by Cursor
parent 26cc0c9a64
commit ea58a13fce
2 changed files with 105 additions and 0 deletions
@@ -0,0 +1,104 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Transfer;
use App\Models\TransferFile;
use App\Services\Identity\MailboxUserResolver;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use RuntimeException;
/** Service-to-service file listing for Ladill Mail (synced with Transfer Files). */
class TransferFilesController extends Controller
{
public function __construct(private MailboxUserResolver $users) {}
public function index(Request $request): JsonResponse
{
if ($request->attributes->get('service_caller') !== 'webmail') {
return response()->json(['message' => 'Forbidden.'], 403);
}
$data = $request->validate([
'mailbox' => ['required', 'email', 'max:255'],
'q' => ['nullable', 'string', 'max:120'],
'page' => ['nullable', 'integer', 'min:1'],
'sort' => ['nullable', 'in:newest,oldest,name,size'],
]);
try {
$user = $this->users->resolve((string) $data['mailbox']);
} catch (RuntimeException $e) {
return response()->json(['message' => $e->getMessage()], 422);
}
$search = trim((string) ($data['q'] ?? ''));
$sort = (string) ($data['sort'] ?? 'newest');
$filesQuery = TransferFile::query()
->whereHas('transfer', fn ($q) => $q
->where('user_id', $user->id)
->where('status', '!=', Transfer::STATUS_DELETED)
->where(function ($query) {
$query->where('is_root_storage', true)
->orWhere(function ($nested) {
$nested->where('is_folder', false)->where('is_root_storage', false);
});
}))
->when($search !== '', fn ($q) => $q->where('original_name', 'like', '%'.$search.'%'))
->with(['transfer.qrCode'])
->when($sort === 'oldest', fn ($q) => $q->oldest())
->when($sort === 'name', fn ($q) => $q->orderBy('original_name'))
->when($sort === 'size', fn ($q) => $q->orderByDesc('size_bytes'))
->when($sort === 'newest', fn ($q) => $q->latest());
$paginator = $filesQuery->paginate(30, ['*'], 'page', (int) ($data['page'] ?? 1));
$items = collect($paginator->items())->map(function (TransferFile $file) {
$transfer = $file->transfer;
$title = (string) ($transfer?->title ?? '');
return [
'id' => $file->id,
'name' => $file->original_name,
'size_bytes' => (int) $file->size_bytes,
'mime_type' => $file->mime_type,
'source' => $this->sourceFor($transfer),
'public_url' => $transfer?->qrCode?->publicUrl(),
'transfer_id' => $transfer?->id,
'transfer_title' => $title !== '' ? $title : null,
'created_at' => $file->created_at?->toIso8601String(),
];
})->values();
return response()->json([
'data' => $items,
'meta' => [
'current_page' => $paginator->currentPage(),
'last_page' => $paginator->lastPage(),
'per_page' => $paginator->perPage(),
'total' => $paginator->total(),
],
]);
}
private function sourceFor(?Transfer $transfer): string
{
if ($transfer === null) {
return 'transfer';
}
if ($transfer->is_root_storage) {
return 'transfer';
}
$title = strtolower(trim($transfer->title));
if (str_starts_with($title, 'email:') || str_starts_with($title, 'email attachment:')) {
return 'mail';
}
return 'transfer';
}
}
+1
View File
@@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Route;
Route::middleware('auth.service:transfer')->prefix('v1')->group(function () {
Route::post('/transfers', [TransferController::class, 'store']);
Route::get('/transfers/files', [\App\Http\Controllers\Api\TransferFilesController::class, 'index']);
Route::post('/transfers/upload/init', [ChunkedUploadController::class, 'init']);
Route::post('/transfers/upload/chunk', [ChunkedUploadController::class, 'chunk']);
Route::post('/transfers/upload/finalize', [ChunkedUploadController::class, 'finalize']);