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:
268
docs/route-plan.md
Normal file
268
docs/route-plan.md
Normal file
@@ -0,0 +1,268 @@
|
||||
# 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()));
|
||||
```
|
||||
Reference in New Issue
Block a user