Files
eCert-MBIP/routes/web.php
Saufi 5b85822b78 chore: initial Laravel 13 project setup for eCert MBIP
- Laravel 13.9 + PHP 8.5 + MySQL
- Bootstrap 5.3 + jQuery 3.7 + Chart.js (replacing Alpine/Tailwind)
- Packages: intervention/image, dompdf, simple-qrcode, league/csv, laravel/breeze, laravel/boost
- 17 database migrations: users, programs, qr_codes, participants, attendances, certificates, questionnaires, email_logs, audit_logs
- 13 Eloquent models with full relationships
- Admin layout (Bootstrap 5 sidebar) + public layout (mobile-first)
- Rate limiters: checkin (60/min), certificate (30/min)
- Admin seeder: admin@mbip.gov.my
- Storage directories + symlink configured

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 15:44:19 +08:00

121 lines
8.2 KiB
PHP

<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Admin\DashboardController;
use App\Http\Controllers\Admin\ProgramController;
use App\Http\Controllers\Admin\QrCodeController;
use App\Http\Controllers\Admin\ParticipantController;
use App\Http\Controllers\Admin\CertificateTemplateController;
use App\Http\Controllers\Admin\ProgramQuestionnaireController;
use App\Http\Controllers\Admin\StatisticsController;
use App\Http\Controllers\Admin\QuestionnaireSetController;
use App\Http\Controllers\Admin\QuestionController;
use App\Http\Controllers\Admin\CertificateController as AdminCertificateController;
use App\Http\Controllers\Public\CheckinController;
use App\Http\Controllers\Public\QuestionnaireController;
use App\Http\Controllers\Public\AttendanceCheckController;
use App\Http\Controllers\Public\CertificateController;
// ──────────────────────────────────────────────
// Root redirect
// ──────────────────────────────────────────────
Route::get('/', fn() => redirect()->route('admin.dashboard'));
// ──────────────────────────────────────────────
// Admin Routes
// ──────────────────────────────────────────────
Route::middleware('auth')->prefix('admin')->name('admin.')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
// Programs
Route::resource('programs', ProgramController::class)->parameters(['programs' => 'program:uuid']);
Route::post('/programs/{program:uuid}/publish', [ProgramController::class, 'publish'])->name('programs.publish');
Route::post('/programs/{program:uuid}/close', [ProgramController::class, 'close'])->name('programs.close');
// QR Code
Route::prefix('programs/{program:uuid}/qr')->name('programs.qr.')->group(function () {
Route::get('/', [QrCodeController::class, 'show'])->name('show');
Route::post('/generate', [QrCodeController::class, 'generate'])->name('generate');
Route::get('/download', [QrCodeController::class, 'download'])->name('download');
Route::post('/deactivate',[QrCodeController::class, 'deactivate'])->name('deactivate');
});
// Participants
Route::prefix('programs/{program:uuid}/participants')->name('programs.participants.')->group(function () {
Route::get('/', [ParticipantController::class, 'index'])->name('index');
Route::get('/create', [ParticipantController::class, 'create'])->name('create');
Route::post('/', [ParticipantController::class, 'store'])->name('store');
Route::delete('/{pp}', [ParticipantController::class, 'destroy'])->name('destroy');
Route::get('/import', [ParticipantController::class, 'importForm'])->name('import.form');
Route::post('/import', [ParticipantController::class, 'import'])->name('import');
Route::get('/export', [ParticipantController::class, 'export'])->name('export');
});
// Certificate Template
Route::prefix('programs/{program:uuid}/template')->name('programs.template.')->group(function () {
Route::get('/', [CertificateTemplateController::class, 'show'])->name('show');
Route::post('/', [CertificateTemplateController::class, 'store'])->name('store');
Route::put('/config', [CertificateTemplateController::class, 'updateConfig'])->name('config');
Route::delete('/', [CertificateTemplateController::class, 'destroy'])->name('destroy');
Route::get('/preview', [CertificateTemplateController::class, 'preview'])->name('preview');
Route::post('/test', [CertificateTemplateController::class, 'testGenerate'])->name('test');
});
// Program Questionnaire
Route::prefix('programs/{program:uuid}/questionnaire')->name('programs.questionnaire.')->group(function () {
Route::get('/', [ProgramQuestionnaireController::class, 'show'])->name('show');
Route::post('/attach', [ProgramQuestionnaireController::class, 'attach'])->name('attach');
Route::post('/confirm', [ProgramQuestionnaireController::class, 'confirm'])->name('confirm');
Route::delete('/detach', [ProgramQuestionnaireController::class, 'detach'])->name('detach');
});
// Program Statistics
Route::prefix('programs/{program:uuid}/statistics')->name('programs.statistics.')->group(function () {
Route::get('/', [StatisticsController::class, 'show'])->name('show');
Route::get('/export', [StatisticsController::class, 'export'])->name('export');
});
// Certificates (Admin)
Route::prefix('programs/{program:uuid}/certificates')->name('programs.certificates.')->group(function () {
Route::get('/', [AdminCertificateController::class, 'index'])->name('index');
Route::post('/generate-all', [AdminCertificateController::class, 'generateAll'])->name('generate-all');
Route::post('/email-all', [AdminCertificateController::class, 'emailAll'])->name('email-all');
});
// Questionnaire Sets
Route::resource('questionnaires', QuestionnaireSetController::class)->except(['show']);
Route::post('/questionnaires/{set}/publish', [QuestionnaireSetController::class, 'publish'])->name('questionnaires.publish');
Route::post('/questionnaires/{set}/archive', [QuestionnaireSetController::class, 'archive'])->name('questionnaires.archive');
Route::get('/questionnaires/{set}', [QuestionnaireSetController::class, 'show'])->name('questionnaires.show');
// Questions
Route::post('/questionnaires/{set}/questions', [QuestionController::class, 'store'])->name('questions.store');
Route::put('/questions/{question}', [QuestionController::class, 'update'])->name('questions.update');
Route::delete('/questions/{question}', [QuestionController::class, 'destroy'])->name('questions.destroy');
Route::post('/questions/reorder', [QuestionController::class, 'reorder'])->name('questions.reorder');
});
// ──────────────────────────────────────────────
// Public Routes (QR Check-in Flow)
// ──────────────────────────────────────────────
Route::middleware('throttle:checkin')->prefix('p')->name('public.')->group(function () {
Route::get('/{qr_token}', [CheckinController::class, 'show'])->name('checkin.show');
Route::post('/{qr_token}/staff', [CheckinController::class, 'staffCheckin'])->name('checkin.staff');
Route::post('/{qr_token}/external', [CheckinController::class, 'externalRegister'])->name('checkin.external');
Route::get('/{qr_token}/semak', [AttendanceCheckController::class, 'show'])->name('semak.show');
Route::post('/{qr_token}/semak', [AttendanceCheckController::class, 'check'])->name('semak.check');
Route::get('/{qr_token}/questionnaire/{participant_uuid}', [QuestionnaireController::class, 'show'])->name('questionnaire.show');
Route::post('/{qr_token}/questionnaire/{participant_uuid}', [QuestionnaireController::class, 'submit'])->name('questionnaire.submit');
});
// ──────────────────────────────────────────────
// Public Certificate Download
// ──────────────────────────────────────────────
Route::middleware('throttle:certificate')->prefix('certificate')->name('public.certificate.')->group(function () {
Route::get('/{cert_token}', [CertificateController::class, 'show'])->name('show');
Route::post('/{cert_token}/download',[CertificateController::class, 'download'])->name('download');
});
require __DIR__.'/auth.php';