- 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>
269 lines
11 KiB
Markdown
269 lines
11 KiB
Markdown
# 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
|
|
|
|
```php
|
|
// 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
|
|
|
|
```php
|
|
// 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`:
|
|
```php
|
|
RateLimiter::for('checkin', fn($req) => Limit::perMinute(60)->by($req->ip()));
|
|
RateLimiter::for('certificate', fn($req) => Limit::perMinute(30)->by($req->ip()));
|
|
```
|