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:
Saufi
2026-05-16 15:44:19 +08:00
commit 5b85822b78
159 changed files with 18351 additions and 0 deletions

434
docs/execution-plan.md Normal file
View File

@@ -0,0 +1,434 @@
# eCert MBIP — Execution Plan (Phased)
## Pre-requisites (Sebelum Fasa 1)
- [ ] Sahkan "Laravel Boost" — package apa yang dimaksudkan
- [ ] Sahkan MySQL server credentials (host, port, db name, user, password)
- [ ] Sahkan SMTP settings untuk email
- [ ] Sahkan storage path boleh write
- [ ] Sahkan Git SSH key berfungsi ke git.mbip.my
---
## Fasa 1: Foundation & Authentication
**Anggaran: 1-2 jam**
### Tasks
- [ ] Install Laravel 12 via `composer create-project`
- [ ] Setup `.env` (DB, queue, mail, app URL)
- [ ] Install Laravel Breeze (Blade stack)
- [ ] Install Composer packages (intervention/image, simple-qrcode, dompdf, league/csv)
- [ ] Buat semua 15 migrations
- [ ] Buat admin layout Blade (sidebar, navbar) dengan Bootstrap 5
- [ ] Buat public layout Blade (mobile-first) dengan Bootstrap 5
- [ ] Setup Queue (database driver → create `jobs` table)
- [ ] Setup Storage symlink
- [ ] Buat Seeder: admin user
- [ ] Commit: `chore: initial Laravel project setup`
- [ ] Commit: `feat: admin authentication and layout`
### Files Utama
```
app/Http/Controllers/Admin/DashboardController.php
resources/views/layouts/admin.blade.php
resources/views/layouts/public.blade.php
resources/views/admin/dashboard.blade.php
database/migrations/* (15 migration files)
database/seeders/AdminSeeder.php
```
### Commands Selepas Fasa 1
```bash
php artisan migrate
php artisan db:seed --class=AdminSeeder
php artisan storage:link
php artisan queue:table && php artisan migrate
```
### Manual Test
- [ ] Login admin berjaya
- [ ] Dashboard papar (walaupun kosong)
- [ ] Logout berjaya
---
## Fasa 2: Program Management
**Anggaran: 2-3 jam**
### Tasks
- [ ] `ProgramController` — CRUD penuh
- [ ] `StoreProgramRequest` + `UpdateProgramRequest` — validation
- [ ] Program index view (table + badge status)
- [ ] Program create/edit form
- [ ] Program show view (tab: details, participants, qr, template, questionnaire, stats)
- [ ] Status management (draft → published → closed)
- [ ] Protect delete jika ada kehadiran
- [ ] Commit: `feat: program management`
### Files Utama
```
app/Http/Controllers/Admin/ProgramController.php
app/Http/Requests/Admin/StoreProgramRequest.php
app/Http/Requests/Admin/UpdateProgramRequest.php
app/Models/Program.php
resources/views/admin/programs/*
```
### Manual Test
- [ ] Tambah program
- [ ] Edit program
- [ ] Tukar status
- [ ] Cuba padam program yang ada kehadiran (mesti gagal)
---
## Fasa 3: QR Code Generation
**Anggaran: 1-2 jam**
### Tasks
- [ ] `QrCodeController` — generate, show, download
- [ ] `QrCodeService` — generate token, buat QR image, simpan storage
- [ ] QR code preview dalam admin
- [ ] Download QR sebagai PNG
- [ ] Commit: `feat: qr code generation`
### Files Utama
```
app/Http/Controllers/Admin/QrCodeController.php
app/Services/QrCodeService.php
app/Models/ProgramQrCode.php
resources/views/admin/programs/qr.blade.php
```
### Manual Test
- [ ] Generate QR code untuk program
- [ ] QR code papar dalam admin
- [ ] Download QR sebagai PNG berjaya
- [ ] Scan QR code → bawa ke /p/{token} (walaupun page belum siap)
---
## Fasa 4: Participant Management & CSV Import
**Anggaran: 2-3 jam**
### Tasks
- [ ] `ParticipantController` — add manual, import, list, export
- [ ] `ParticipantImport` service — parse CSV, validate, bulk insert
- [ ] Import summary: berjaya, duplicate, gagal
- [ ] Export CSV senarai peserta
- [ ] Commit: `feat: participant management`
### Files Utama
```
app/Http/Controllers/Admin/ParticipantController.php
app/Services/ParticipantImportService.php
app/Models/Participant.php
app/Models/ProgramParticipant.php
resources/views/admin/programs/participants/*
```
### CSV Template Headers
```
name,no_kp,email,phone,agency
```
### Manual Test
- [ ] Tambah peserta manual
- [ ] Import CSV (normal)
- [ ] Import CSV dengan duplicate (summary papar betul)
- [ ] Import CSV dengan row kosong/invalid
- [ ] Export CSV peserta
---
## Fasa 5: Public Check-in Flow
**Anggaran: 3-4 jam**
### Tasks
- [ ] `CheckinController` — show, staffCheckin, externalRegister
- [ ] `AttendanceService` — rekod kehadiran, cegah duplicate
- [ ] Public check-in page (mobile-first, Bootstrap 5)
- [ ] Staff check-in form + validation
- [ ] Walk-in registration form + validation
- [ ] Status page selepas check-in
- [ ] Rate limiting pada routes
- [ ] Commit: `feat: participant registration and attendance`
### Files Utama
```
app/Http/Controllers/Public/CheckinController.php
app/Services/AttendanceService.php
resources/views/public/checkin/*
```
### Manual Test
- [ ] Scan QR → buka page check-in
- [ ] Staff check-in dengan no_kp betul
- [ ] Staff check-in dengan no_kp salah
- [ ] Staff check-in yang sudah hadir (mesej duplicate)
- [ ] Walk-in daftar baru
- [ ] Walk-in dengan no_kp sama → error duplicate
- [ ] Test rate limit (cuba submit banyak kali)
---
## Fasa 6: Questionnaire Management
**Anggaran: 3-4 jam**
### Tasks
- [ ] `QuestionnaireSetController` — CRUD
- [ ] `QuestionController` — CRUD, reorder
- [ ] `ProgramQuestionnaireController` — attach, confirm, detach
- [ ] Public questionnaire form (semua jenis soalan)
- [ ] Submit response + answers
- [ ] Semak sudah jawab (cegah double submit)
- [ ] Commit: `feat: questionnaire management`
### Files Utama
```
app/Http/Controllers/Admin/QuestionnaireSetController.php
app/Http/Controllers/Admin/QuestionController.php
app/Http/Controllers/Admin/ProgramQuestionnaireController.php
app/Http/Controllers/Public/QuestionnaireController.php
app/Models/QuestionnaireSet.php
app/Models/QuestionnaireQuestion.php
app/Models/QuestionnaireResponse.php
app/Models/QuestionnaireAnswer.php
resources/views/admin/questionnaires/*
resources/views/public/questionnaire/*
```
### Manual Test
- [ ] Cipta questionnaire set
- [ ] Tambah pelbagai jenis soalan
- [ ] Attach questionnaire ke program
- [ ] Confirm questionnaire
- [ ] Peserta boleh akses questionnaire (selepas check-in)
- [ ] Submit soalselidik berjaya
- [ ] Cuba submit semula → error (sudah jawab)
---
## Fasa 7: Certificate Template & Generation
**Anggaran: 4-5 jam**
### Tasks
- [ ] `CertificateTemplateController` — upload, config, preview, test generate
- [ ] `CertificateService` — image overlay (Intervention Image), PDF wrap (DOMPDF)
- [ ] Font auto-scale berdasarkan panjang nama
- [ ] Preview template dalam admin
- [ ] Test generate dengan nama sample
- [ ] `GenerateCertificateJob` — queue job
- [ ] `AttendanceCheckController` — public semak kehadiran
- [ ] `CertificateController` — public, show, download gate
- [ ] Certificate gate: soalselidik dijawab + masa download aktif
- [ ] Rekod downloaded_at dan download_count
- [ ] Commit: `feat: certificate template upload`
- [ ] Commit: `feat: certificate generation and download`
### Files Utama
```
app/Http/Controllers/Admin/CertificateTemplateController.php
app/Http/Controllers/Public/CertificateController.php
app/Http/Controllers/Public/AttendanceCheckController.php
app/Services/CertificateService.php
app/Jobs/GenerateCertificateJob.php
app/Models/Certificate.php
app/Models/CertificateTemplate.php
resources/views/admin/programs/template/*
resources/views/public/certificate/*
resources/fonts/ ← bundel NotoSans TTF
```
### Manual Test
- [ ] Upload template imej
- [ ] Set koordinat nama + no_kp
- [ ] Preview template
- [ ] Test generate → papar hasil
- [ ] Peserta scan QR (masa download) → semak kehadiran
- [ ] Peserta download sijil (selepas soalselidik)
- [ ] Peserta cuba download sebelum jawab soalselidik → redirect
---
## Fasa 8: Email & Queue
**Anggaran: 2-3 jam**
### Tasks
- [ ] `CertificateReadyMail` — Mailable class
- [ ] Email template Blade (HTML + text)
- [ ] `SendCertificateEmailJob` — queue job
- [ ] `BlastCertificateLinkJob` — mass email untuk semua peserta hadir
- [ ] Admin trigger: "Email semua peserta" button
- [ ] `email_logs` rekod semua attempt
- [ ] Commit: `feat: email certificate link`
### Files Utama
```
app/Mail/CertificateReadyMail.php
app/Jobs/SendCertificateEmailJob.php
app/Jobs/BlastCertificateLinkJob.php
resources/views/emails/certificate-ready.blade.php
resources/views/emails/certificate-ready.text.blade.php
```
### Manual Test
- [ ] Trigger email untuk satu peserta
- [ ] Email diterima dengan link betul
- [ ] Link dalam email bawa ke certificate page
- [ ] `email_logs` rekod status sent
- [ ] Test email SMTP failure → status failed dalam email_logs
---
## Fasa 9: Statistics Dashboard
**Anggaran: 2-3 jam**
### Tasks
- [ ] `StatisticsController` — dashboard + per-program
- [ ] Dashboard cards (counts)
- [ ] Per-program breakdown (attendance by type, session, soalselidik)
- [ ] Chart.js integration (rating chart, attendance trend)
- [ ] Export CSV statistik
- [ ] Commit: `feat: admin statistics dashboard`
### Files Utama
```
app/Http/Controllers/Admin/StatisticsController.php
app/Http/Controllers/Admin/DashboardController.php (update)
resources/views/admin/dashboard.blade.php (update)
resources/views/admin/programs/statistics.blade.php
```
### Manual Test
- [ ] Dashboard papar kiraan betul
- [ ] Per-program statistik betul
- [ ] Chart.js render tanpa error
- [ ] Export CSV berjaya
---
## Fasa 10: Security Hardening & Audit Log
**Anggaran: 1-2 jam**
### Tasks
- [ ] `AuditLogService` — log admin actions
- [ ] Hook audit log ke key events
- [ ] Review semua route ada proper validation
- [ ] Semak file upload hanya benarkan jpg/png
- [ ] Semak no_kp tidak expose dalam URL
- [ ] Rate limiting final review
- [ ] Commit: `feat: audit logging and security hardening`
---
## Fasa 11: Testing
**Anggaran: 3-4 jam**
### Tasks
- [ ] Setup Pest
- [ ] Admin can create program
- [ ] QR token valid opens check-in page
- [ ] Staff pre-registered can check-in
- [ ] External participant can register and check-in
- [ ] Duplicate no_kp for same program rejected
- [ ] Participant cannot download certificate before questionnaire
- [ ] Participant can download certificate after questionnaire
- [ ] Admin can view basic statistics
- [ ] Commit: `test: core attendance and certificate flow`
---
## Environment Variables Checklist
```env
APP_NAME="eCert MBIP"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=ecert_mbip
DB_USERNAME=root
DB_PASSWORD=
QUEUE_CONNECTION=database
MAIL_MAILER=smtp
MAIL_HOST=
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@mbip.gov.my
MAIL_FROM_NAME="eCert MBIP"
FILESYSTEM_DISK=local
SESSION_DRIVER=database
SESSION_LIFETIME=120
```
---
## Git Commit Sequence
```bash
# Fasa 1
git commit -m "chore: initial Laravel project setup"
git commit -m "feat: admin authentication and layout"
# Fasa 2
git commit -m "feat: program management"
# Fasa 3
git commit -m "feat: qr code generation"
# Fasa 4
git commit -m "feat: participant management and csv import"
# Fasa 5
git commit -m "feat: public check-in flow and attendance"
# Fasa 6
git commit -m "feat: questionnaire management"
# Fasa 7
git commit -m "feat: certificate template upload"
git commit -m "feat: certificate generation and download"
# Fasa 8
git commit -m "feat: email certificate link"
# Fasa 9
git commit -m "feat: admin statistics dashboard"
# Fasa 10
git commit -m "feat: audit logging and security hardening"
# Fasa 11
git commit -m "test: core attendance and certificate flow"
# Production push (HANYA apabila diarahkan)
# git push -u origin master
```
---
## Risiko Teknikal
| # | Risiko | Kemungkinan | Impak | Mitigasi |
|---|--------|-------------|-------|----------|
| 1 | GD font rendering — perlu TTF, tidak ada jika lupa bundel | Sederhana | Tinggi | Bundel Noto Sans TTF dalam `resources/fonts/`, test awal |
| 2 | Queue worker tidak berjalan di Windows production | Tinggi | Tinggi | Guna Task Scheduler Windows atau switch ke Linux server |
| 3 | CSV import gagal handle UTF-8 BOM (Excel export) | Tinggi | Sederhana | Detect dan strip BOM dalam import service |
| 4 | SMTP kerajaan — TLS/SSL config berbeza | Sederhana | Tinggi | Test awal dengan real SMTP, fallback ke log driver |
| 5 | Concurrent check-in race condition | Rendah | Sederhana | DB unique constraint + try-catch pada insert |
| 6 | PDF certificate kualiti rendah jika gambar template kecil | Sederhana | Sederhana | Minta admin upload template minimum 1240px lebar |
| 7 | "Laravel Boost" tidak jelas | — | Rendah | Perlu penjelasan sebelum install |
| 8 | Session tidak persistent untuk public (no login) | — | Sederhana | Guna certificate token + signed URL, bukan session |
| 9 | Storage permissions pada Windows production | Sederhana | Tinggi | Pastikan `storage/` dan `bootstrap/cache/` writable |
| 10 | PDPA — no_kp exposure dalam log/debug | Rendah | Tinggi | Disable query log production, redact no_kp dalam audit_logs |