feat: qr code generation
- QrCodeService: generate unique 48-char token, create QR PNG (400x400, error-correction H) - QrCodeController: show, generate, download PNG, deactivate - Admin QR page: preview, copy URL, download, regenerate, deactivate - Existing active QR deactivated on regenerate - Token-based URL (not program ID) for PDPA compliance Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,59 @@
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Program;
|
||||
use App\Services\AuditLogService;
|
||||
use App\Services\QrCodeService;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class QrCodeController extends Controller
|
||||
{
|
||||
//
|
||||
public function __construct(private QrCodeService $qrCodeService) {}
|
||||
|
||||
public function show(Program $program): View
|
||||
{
|
||||
$qrCode = $program->qrCodes()->where('is_active', true)->latest()->first();
|
||||
|
||||
return view('admin.programs.qr', compact('program', 'qrCode'));
|
||||
}
|
||||
|
||||
public function generate(Program $program): RedirectResponse
|
||||
{
|
||||
$qrCode = $this->qrCodeService->generateForProgram($program);
|
||||
|
||||
AuditLogService::log('qrcode.generated', $program);
|
||||
|
||||
return redirect()
|
||||
->route('admin.programs.qr.show', $program)
|
||||
->with('success', 'QR Code berjaya dijana.');
|
||||
}
|
||||
|
||||
public function download(Program $program): Response|RedirectResponse
|
||||
{
|
||||
$qrCode = $program->qrCodes()->where('is_active', true)->latest()->first();
|
||||
|
||||
if (! $qrCode) {
|
||||
return back()->with('error', 'QR Code belum dijana.');
|
||||
}
|
||||
|
||||
$png = $this->qrCodeService->getRawPng($qrCode);
|
||||
$filename = 'QR_' . Str::slug($program->title) . '_' . now()->format('Ymd') . '.png';
|
||||
|
||||
return response($png, 200, [
|
||||
'Content-Type' => 'image/png',
|
||||
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
|
||||
]);
|
||||
}
|
||||
|
||||
public function deactivate(Program $program): RedirectResponse
|
||||
{
|
||||
$program->qrCodes()->where('is_active', true)->update(['is_active' => false]);
|
||||
|
||||
AuditLogService::log('qrcode.deactivated', $program);
|
||||
|
||||
return back()->with('success', 'QR Code berjaya dinyahaktifkan.');
|
||||
}
|
||||
}
|
||||
|
||||
51
app/Services/QrCodeService.php
Normal file
51
app/Services/QrCodeService.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Program;
|
||||
use App\Models\ProgramQrCode;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use SimpleSoftwareIO\QrCode\Facades\QrCode;
|
||||
|
||||
class QrCodeService
|
||||
{
|
||||
public function generateForProgram(Program $program): ProgramQrCode
|
||||
{
|
||||
// Deactivate existing active QR codes
|
||||
$program->qrCodes()->where('is_active', true)->update(['is_active' => false]);
|
||||
|
||||
$token = Str::random(48);
|
||||
$url = route('public.checkin.show', $token);
|
||||
$path = 'public/qrcodes/' . $token . '.png';
|
||||
$absPath = Storage::path($path);
|
||||
|
||||
// Ensure directory exists
|
||||
Storage::makeDirectory('public/qrcodes');
|
||||
|
||||
// Generate QR code PNG (400×400, with quiet zone)
|
||||
$png = QrCode::format('png')
|
||||
->size(400)
|
||||
->margin(2)
|
||||
->errorCorrection('H')
|
||||
->generate($url);
|
||||
|
||||
Storage::put($path, $png);
|
||||
|
||||
return $program->qrCodes()->create([
|
||||
'token' => $token,
|
||||
'qr_image_path' => $path,
|
||||
'is_active' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getPublicUrl(ProgramQrCode $qrCode): string
|
||||
{
|
||||
return Storage::url($qrCode->qr_image_path);
|
||||
}
|
||||
|
||||
public function getRawPng(ProgramQrCode $qrCode): string
|
||||
{
|
||||
return Storage::get($qrCode->qr_image_path);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user