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>
This commit is contained in:
59
routes/auth.php
Normal file
59
routes/auth.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Auth\AuthenticatedSessionController;
|
||||
use App\Http\Controllers\Auth\ConfirmablePasswordController;
|
||||
use App\Http\Controllers\Auth\EmailVerificationNotificationController;
|
||||
use App\Http\Controllers\Auth\EmailVerificationPromptController;
|
||||
use App\Http\Controllers\Auth\NewPasswordController;
|
||||
use App\Http\Controllers\Auth\PasswordController;
|
||||
use App\Http\Controllers\Auth\PasswordResetLinkController;
|
||||
use App\Http\Controllers\Auth\RegisteredUserController;
|
||||
use App\Http\Controllers\Auth\VerifyEmailController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware('guest')->group(function () {
|
||||
Route::get('register', [RegisteredUserController::class, 'create'])
|
||||
->name('register');
|
||||
|
||||
Route::post('register', [RegisteredUserController::class, 'store']);
|
||||
|
||||
Route::get('login', [AuthenticatedSessionController::class, 'create'])
|
||||
->name('login');
|
||||
|
||||
Route::post('login', [AuthenticatedSessionController::class, 'store']);
|
||||
|
||||
Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
|
||||
->name('password.request');
|
||||
|
||||
Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
|
||||
->name('password.email');
|
||||
|
||||
Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
|
||||
->name('password.reset');
|
||||
|
||||
Route::post('reset-password', [NewPasswordController::class, 'store'])
|
||||
->name('password.store');
|
||||
});
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
Route::get('verify-email', EmailVerificationPromptController::class)
|
||||
->name('verification.notice');
|
||||
|
||||
Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
|
||||
->middleware(['signed', 'throttle:6,1'])
|
||||
->name('verification.verify');
|
||||
|
||||
Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
|
||||
->middleware('throttle:6,1')
|
||||
->name('verification.send');
|
||||
|
||||
Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])
|
||||
->name('password.confirm');
|
||||
|
||||
Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);
|
||||
|
||||
Route::put('password', [PasswordController::class, 'update'])->name('password.update');
|
||||
|
||||
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
|
||||
->name('logout');
|
||||
});
|
||||
8
routes/console.php
Normal file
8
routes/console.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
Artisan::command('inspire', function () {
|
||||
$this->comment(Inspiring::quote());
|
||||
})->purpose('Display an inspiring quote');
|
||||
120
routes/web.php
Normal file
120
routes/web.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?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';
|
||||
Reference in New Issue
Block a user