Files
ladill-transfer/app/Http/Controllers/Public/TransferPublicController.php
T
isaaccladandCursor c1e3d8b3ac Initial Ladill Transfer app — file sharing with QR links.
Scaffolded from the Ladill mini/events extraction pattern: multi-file transfers,
retention controls, public landing pages, analytics, and Gitea deploy workflow.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 09:25:30 +00:00

98 lines
3.3 KiB
PHP

<?php
namespace App\Http\Controllers\Public;
use App\Http\Controllers\Controller;
use App\Models\QrCode;
use App\Models\Transfer;
use App\Models\TransferFile;
use App\Services\Qr\QrScanRecorder;
use App\Services\Transfer\TransferService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;
class TransferPublicController extends Controller
{
public function __construct(
private QrScanRecorder $scanRecorder,
private TransferService $transfers,
) {}
public function landing(Request $request, string $shortCode): RedirectResponse|View
{
$qrCode = QrCode::query()->where('short_code', $shortCode)->firstOrFail();
$this->scanRecorder->record($qrCode, $request);
if (! $qrCode->is_active || ! $qrCode->isTransferType()) {
return view('public.qr.inactive', ['qrCode' => $qrCode]);
}
$transfer = $this->resolveTransfer($qrCode);
abort_unless($transfer && $transfer->isActive(), 404);
if ($transfer->isPasswordProtected() && ! $request->session()->get("transfer_unlocked.{$transfer->id}")) {
return view('public.transfer.password', [
'qrCode' => $qrCode,
'transfer' => $transfer,
]);
}
return view('public.transfer.landing', [
'qrCode' => $qrCode,
'transfer' => $transfer->load('files'),
]);
}
public function unlock(Request $request, string $shortCode): RedirectResponse|View
{
$qrCode = QrCode::query()->where('short_code', $shortCode)->firstOrFail();
$transfer = $this->resolveTransfer($qrCode);
abort_unless($transfer, 404);
$data = $request->validate(['password' => 'required|string|max:64']);
if (! $this->transfers->verifyPassword($transfer, $data['password'])) {
return back()->with('error', 'Incorrect password.');
}
$request->session()->put("transfer_unlocked.{$transfer->id}", true);
return redirect()->route('qr.public.transfer', $shortCode);
}
public function download(Request $request, string $shortCode, TransferFile $file): StreamedResponse
{
$qrCode = QrCode::query()->where('short_code', $shortCode)->firstOrFail();
$transfer = $this->resolveTransfer($qrCode);
abort_unless($transfer && $transfer->isActive() && $file->transfer_id === $transfer->id, 404);
if ($transfer->isPasswordProtected() && ! $request->session()->get("transfer_unlocked.{$transfer->id}")) {
abort(403);
}
abort_unless($file->existsOnDisk(), 404);
$this->transfers->recordDownload($transfer, $file, $request);
return Storage::disk($file->disk)->response(
$file->path,
$file->original_name,
['Content-Type' => $file->mime_type],
);
}
private function resolveTransfer(QrCode $qrCode): ?Transfer
{
$transferId = $qrCode->content()['transfer_id'] ?? null;
if ($transferId) {
return Transfer::query()->with('files')->find($transferId);
}
return Transfer::query()->where('qr_code_id', $qrCode->id)->with('files')->first();
}
}