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>
98 lines
3.3 KiB
PHP
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();
|
|
}
|
|
}
|