Files
eCert-MBIP/docs/route-plan.md
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

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()));
```