- 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>
11 KiB
11 KiB
eCert MBIP — Route Plan
Admin Routes (Authenticated)
Prefix: /admin
Middleware: auth, verified (optional)
Auth
GET /login → Auth\AuthenticatedSessionController@create
POST /login → Auth\AuthenticatedSessionController@store
POST /logout → Auth\AuthenticatedSessionController@destroy
Dashboard
GET /admin/dashboard → Admin\DashboardController@index
Programs
GET /admin/programs → Admin\ProgramController@index
GET /admin/programs/create → Admin\ProgramController@create
POST /admin/programs → Admin\ProgramController@store
GET /admin/programs/{program:uuid} → Admin\ProgramController@show
GET /admin/programs/{program:uuid}/edit → Admin\ProgramController@edit
PUT /admin/programs/{program:uuid} → Admin\ProgramController@update
DELETE /admin/programs/{program:uuid} → Admin\ProgramController@destroy
POST /admin/programs/{program:uuid}/publish → Admin\ProgramController@publish
POST /admin/programs/{program:uuid}/close → Admin\ProgramController@close
QR Code
GET /admin/programs/{program:uuid}/qr → Admin\QrCodeController@show
POST /admin/programs/{program:uuid}/qr/generate → Admin\QrCodeController@generate
GET /admin/programs/{program:uuid}/qr/download → Admin\QrCodeController@download
POST /admin/programs/{program:uuid}/qr/deactivate → Admin\QrCodeController@deactivate
Participants (Pre-registered)
GET /admin/programs/{program:uuid}/participants → Admin\ParticipantController@index
GET /admin/programs/{program:uuid}/participants/create → Admin\ParticipantController@create
POST /admin/programs/{program:uuid}/participants → Admin\ParticipantController@store
DELETE /admin/programs/{program:uuid}/participants/{pp} → Admin\ParticipantController@destroy
GET /admin/programs/{program:uuid}/participants/import → Admin\ParticipantController@importForm
POST /admin/programs/{program:uuid}/participants/import → Admin\ParticipantController@import
GET /admin/programs/{program:uuid}/participants/export → Admin\ParticipantController@export
Certificate Template
GET /admin/programs/{program:uuid}/template → Admin\CertificateTemplateController@show
POST /admin/programs/{program:uuid}/template → Admin\CertificateTemplateController@store
PUT /admin/programs/{program:uuid}/template/config → Admin\CertificateTemplateController@updateConfig
DELETE /admin/programs/{program:uuid}/template → Admin\CertificateTemplateController@destroy
GET /admin/programs/{program:uuid}/template/preview → Admin\CertificateTemplateController@preview
POST /admin/programs/{program:uuid}/template/test → Admin\CertificateTemplateController@testGenerate
Questionnaire (Attachment to Program)
GET /admin/programs/{program:uuid}/questionnaire → Admin\ProgramQuestionnaireController@show
POST /admin/programs/{program:uuid}/questionnaire/attach → Admin\ProgramQuestionnaireController@attach
POST /admin/programs/{program:uuid}/questionnaire/confirm → Admin\ProgramQuestionnaireController@confirm
DELETE /admin/programs/{program:uuid}/questionnaire/detach → Admin\ProgramQuestionnaireController@detach
Statistics
GET /admin/programs/{program:uuid}/statistics → Admin\StatisticsController@show
GET /admin/programs/{program:uuid}/statistics/export → Admin\StatisticsController@export
Questionnaire Sets (Reusable)
GET /admin/questionnaires → Admin\QuestionnaireSetController@index
GET /admin/questionnaires/create → Admin\QuestionnaireSetController@create
POST /admin/questionnaires → Admin\QuestionnaireSetController@store
GET /admin/questionnaires/{set} → Admin\QuestionnaireSetController@show
GET /admin/questionnaires/{set}/edit → Admin\QuestionnaireSetController@edit
PUT /admin/questionnaires/{set} → Admin\QuestionnaireSetController@update
DELETE /admin/questionnaires/{set} → Admin\QuestionnaireSetController@destroy
POST /admin/questionnaires/{set}/publish → Admin\QuestionnaireSetController@publish
POST /admin/questionnaires/{set}/archive → Admin\QuestionnaireSetController@archive
Questionnaire Questions
POST /admin/questionnaires/{set}/questions → Admin\QuestionController@store
PUT /admin/questions/{question} → Admin\QuestionController@update
DELETE /admin/questions/{question} → Admin\QuestionController@destroy
POST /admin/questions/reorder → Admin\QuestionController@reorder
Certificate Generation (Admin Trigger)
POST /admin/programs/{program:uuid}/certificates/generate-all → Admin\CertificateController@generateAll
POST /admin/programs/{program:uuid}/certificates/email-all → Admin\CertificateController@emailAll
GET /admin/programs/{program:uuid}/certificates → Admin\CertificateController@index
Public Routes (Token-based, No Auth)
Prefix: none
Middleware: throttle:60,1 (check-in), throttle:30,1 (download)
Check-in Flow
GET /p/{qr_token} → Public\CheckinController@show
↑ Papar: nama program, status, pilihan jenis peserta
↑ Redirect ke questionnaire/download jika masa download aktif
POST /p/{qr_token}/staff → Public\CheckinController@staffCheckin
↑ Input: no_kp, email
↑ Semak pre-registered, rekod attendance
POST /p/{qr_token}/external → Public\CheckinController@externalRegister
↑ Input: name, no_kp, email, phone, agency
↑ Daftar + rekod attendance
Questionnaire (Public)
GET /p/{qr_token}/questionnaire/{participant_uuid}
→ Public\QuestionnaireController@show
↑ Semak: attendance exists, questionnaire published, belum jawab
POST /p/{qr_token}/questionnaire/{participant_uuid}
→ Public\QuestionnaireController@submit
↑ Simpan responses, redirect ke certificate page
Semakan Kehadiran (via QR masa download)
GET /p/{qr_token}/semak → Public\AttendanceCheckController@show
↑ Papar form: masukkan no_kp untuk semak
POST /p/{qr_token}/semak → Public\AttendanceCheckController@check
↑ Semak kehadiran, papar status sijil
Certificate Download
GET /certificate/{cert_token} → Public\CertificateController@show
↑ Semak: token valid, masa download aktif, soalselidik dijawab
↑ Jika semua OK: papar download button / auto-download
POST /certificate/{cert_token}/download → Public\CertificateController@download
↑ Generate if not exists, serve file, increment download_count
Route Logic / Decision Tree
GET /p/{qr_token}
QR token valid?
NO → 404
YES →
Program published?
NO → Papar: "Program belum dibuka"
YES →
Masa download aktif (ecert_download_start_at <= now)?
YES → Redirect ke /p/{qr_token}/semak
NO →
Masa check-in aktif (checkin_start_at <= now <= checkin_end_at)?
YES → Papar check-in form
NO → Papar: "Check-in belum dibuka" atau "Check-in sudah ditutup"
POST /p/{qr_token}/staff
no_kp + email valid format?
NO → Validation error
YES →
Jumpa dalam program_participants (pre_registered)?
NO → "Tidak dijumpai dalam senarai" + pilihan daftar luar
YES →
Sudah check-in (attendances exists)?
YES → "Anda sudah check-in" + papar masa check-in
NO →
Rekod attendance
Papar: "Check-in berjaya! Sijil akan dihantar ke emel selepas program."
GET /certificate/{cert_token}
Token valid (dalam certificates table)?
NO → 404
YES →
Masa download aktif?
NO → Papar: "Sijil belum boleh dimuat turun. Mula dari: {datetime}"
YES →
Soalselidik diperlukan dan belum dijawab?
YES → Redirect ke /p/{qr_token}/questionnaire/{participant_uuid}
NO →
Sijil sudah generated?
NO → Dispatch GenerateCertificateJob, papar: "Sijil sedang disediakan..."
YES → Papar download page / auto-download PDF
Named Routes
// Admin
'admin.dashboard'
'admin.programs.index'
'admin.programs.create'
'admin.programs.show'
'admin.programs.edit'
'admin.programs.qr.show'
'admin.programs.qr.generate'
'admin.programs.qr.download'
'admin.programs.participants.index'
'admin.programs.participants.import'
'admin.programs.template.show'
'admin.programs.questionnaire.show'
'admin.programs.statistics.show'
'admin.questionnaires.index'
'admin.questions.store'
// Public
'public.checkin.show' → /p/{qr_token}
'public.checkin.staff' → POST /p/{qr_token}/staff
'public.checkin.external' → POST /p/{qr_token}/external
'public.questionnaire.show' → /p/{qr_token}/questionnaire/{uuid}
'public.questionnaire.submit' → POST /p/{qr_token}/questionnaire/{uuid}
'public.semak.show' → /p/{qr_token}/semak
'public.semak.check' → POST /p/{qr_token}/semak
'public.certificate.show' → /certificate/{cert_token}
'public.certificate.download' → POST /certificate/{cert_token}/download
Middleware Stack
// web.php
Route::middleware('auth')->prefix('admin')->name('admin.')->group(function () {
// semua admin routes
});
Route::middleware(['throttle:checkin'])->prefix('p')->name('public.')->group(function () {
// check-in routes
});
Route::middleware(['throttle:certificate'])->prefix('certificate')->name('public.certificate.')->group(function () {
// download routes
});
Rate limiter dalam AppServiceProvider:
RateLimiter::for('checkin', fn($req) => Limit::perMinute(60)->by($req->ip()));
RateLimiter::for('certificate', fn($req) => Limit::perMinute(30)->by($req->ip()));