Files
eCert-MBIP/docs/database-design.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

13 KiB

eCert MBIP — Database Design (Final)

Entity Relationship Summary

users                    ← admin accounts only
programs                 ← program data
  ├── program_qr_codes   ← QR token per program
  ├── program_participants ← enrollment (pre-reg + walk-in)
  ├── attendances        ← actual check-in records
  ├── certificate_templates ← uploaded template image + config
  ├── certificates       ← generated certificates per participant
  └── program_questionnaires ← link program ke questionnaire set

participants             ← master data peserta (no_kp unique)
  ├── program_participants
  ├── attendances
  ├── certificates
  └── questionnaire_responses

questionnaire_sets       ← reusable question sets
  ├── questionnaire_questions
  ├── program_questionnaires
  └── questionnaire_responses
      └── questionnaire_answers

email_logs               ← semua email attempt
audit_logs               ← admin action tracking

Table Definitions

1. users

Laravel default. Tambah kolum berikut:

is_admin     boolean  default true   ← semua user = admin buat masa ini

2. programs

Kolum Type Keterangan
id bigint PK
uuid char(36) UNIQUE public identifier, pakai dalam URL
title varchar(255) nama program
description text nullable deskripsi program
organizer varchar(255) penganjur
location varchar(500) lokasi program
start_date date tarikh mula
end_date date tarikh tamat
checkin_start_at datetime nullable mula boleh check-in
checkin_end_at datetime nullable tamat check-in
ecert_download_start_at datetime nullable mula boleh download sijil
ecert_download_end_at datetime nullable tamat download sijil
status enum(draft,published,closed) default: draft
allow_walk_in boolean default: true
default_staff_session enum(pagi,petang,full_day) nullable sesi default untuk staff
default_external_session enum(pagi,petang,full_day) nullable sesi default untuk orang luar
created_by bigint FK users.id
timestamps created_at, updated_at

Index: status, uuid


3. program_qr_codes

Kolum Type Keterangan
id bigint PK
program_id bigint FK
token varchar(64) UNIQUE random token, bukan UUID biasa
qr_image_path varchar(500) nullable path dalam storage
is_active boolean default: true
timestamps

Index: token, program_id


4. participants

Kolum Type Keterangan
id bigint PK
uuid char(36) UNIQUE public identifier
name varchar(255) nama penuh
no_kp varchar(20) UNIQUE no kad pengenalan (tanpa sempang)
email varchar(255) nullable emel
phone varchar(20) nullable no telefon
agency varchar(255) nullable jabatan / agensi
participant_type enum(staff,external) default: external
timestamps

Index: no_kp, email, uuid

Nota PDPA: no_kp disimpan dalam DB tetapi TIDAK dipaparkan dalam URL. Akses public guna uuid atau certificate.token.


5. program_participants

Kolum Type Keterangan
id bigint PK
program_id bigint FK
participant_id bigint FK
registration_source enum(pre_registered,walk_in,admin_manual,import)
is_pre_registered boolean default: false
pre_registered_session enum(pagi,petang,full_day) nullable sesi yang ditetapkan semasa pre-reg
status enum(registered,checked_in,cancelled) default: registered
registered_at timestamp nullable
timestamps

Unique: (program_id, participant_id) Index: program_id, participant_id, status


6. attendances

Kolum Type Keterangan
id bigint PK
program_id bigint FK
participant_id bigint FK
program_participant_id bigint FK nullable link ke enrollment
attendance_source enum(pre_registered_staff,walk_in_external,admin_manual)
attendance_session enum(pagi,petang,full_day) sesi hadir
checked_in_at timestamp masa check-in
checked_in_ip varchar(45) nullable IP address
user_agent varchar(500) nullable browser/device
notes varchar(500) nullable nota tambahan
timestamps

Unique: (program_id, participant_id) Index: program_id, participant_id, attendance_source, attendance_session


7. certificate_templates

Kolum Type Keterangan
id bigint PK
program_id bigint FK
original_filename varchar(255) nama fail asal
image_path varchar(500) path dalam storage
config_json json koordinat dan style teks
is_active boolean default: true
uploaded_by bigint FK users.id
timestamps

Contoh config_json:

{
  "fields": {
    "name": {
      "x": 1240,
      "y": 850,
      "font_size": 72,
      "font_size_min": 36,
      "font_family": "NotoSans-Bold",
      "color": "#1a1a1a",
      "align": "center",
      "max_width": 1600
    },
    "no_kp": {
      "x": 1240,
      "y": 960,
      "font_size": 48,
      "font_size_min": 36,
      "font_family": "NotoSans-Regular",
      "color": "#333333",
      "align": "center",
      "max_width": 1200
    },
    "program_title": {
      "x": 1240,
      "y": 620,
      "font_size": 52,
      "font_size_min": 28,
      "font_family": "NotoSans-Bold",
      "color": "#1a1a1a",
      "align": "center",
      "max_width": 1800,
      "enabled": false
    },
    "program_date": {
      "x": 1240,
      "y": 700,
      "font_size": 36,
      "font_family": "NotoSans-Regular",
      "color": "#555555",
      "align": "center",
      "enabled": false
    }
  },
  "canvas_dpi": 150
}

8. certificates

Kolum Type Keterangan
id bigint PK
uuid char(36) UNIQUE
program_id bigint FK
participant_id bigint FK
certificate_template_id bigint FK
certificate_no varchar(100) UNIQUE nullable no sijil rasmi (auto-generate)
file_path varchar(500) nullable path PDF dalam storage
token varchar(64) UNIQUE token untuk download link
status enum(pending,generating,generated,emailed,failed) default: pending
error_message text nullable jika gagal
generated_at timestamp nullable
emailed_at timestamp nullable
downloaded_at timestamp nullable tarikh pertama download
download_count int default: 0
timestamps

Unique: (program_id, participant_id) Index: token, status, program_id, participant_id

Format certificate_no: MBIP/{YEAR}/{PROGRAM_ID}/{SEQUENCE} — contoh: MBIP/2025/001/0042


9. questionnaire_sets

Kolum Type Keterangan
id bigint PK
title varchar(255) tajuk set
description text nullable
status enum(draft,published,archived) default: draft
created_by bigint FK users.id
timestamps

10. questionnaire_questions

Kolum Type Keterangan
id bigint PK
questionnaire_set_id bigint FK
question_text text teks soalan
question_type enum(rating,single_choice,multiple_choice,short_text,long_text)
options_json json nullable untuk single/multiple choice dan rating
is_required boolean default: true
sort_order int default: 0
timestamps

Contoh options_json:

// single_choice / multiple_choice:
{ "options": ["Sangat Berpuas Hati", "Berpuas Hati", "Sederhana", "Tidak Berpuas Hati"] }

// rating:
{ "min": 1, "max": 5, "labels": { "1": "Sangat Lemah", "5": "Cemerlang" } }

11. program_questionnaires

Kolum Type Keterangan
id bigint PK
program_id bigint FK
questionnaire_set_id bigint FK
is_confirmed boolean default: false
confirmed_at timestamp nullable
confirmed_by bigint FK users.id nullable
timestamps

Unique: (program_id, questionnaire_set_id)


12. questionnaire_responses

Kolum Type Keterangan
id bigint PK
program_id bigint FK
participant_id bigint FK
questionnaire_set_id bigint FK
submitted_at timestamp
ip_address varchar(45) nullable
user_agent varchar(500) nullable
timestamps

Unique: (program_id, participant_id, questionnaire_set_id) Index: program_id, participant_id


13. questionnaire_answers

Kolum Type Keterangan
id bigint PK
questionnaire_response_id bigint FK
questionnaire_question_id bigint FK
answer_value json nullable nilai jawapan (flexibel untuk semua jenis)
timestamps

Contoh answer_value:

// rating:       { "value": 4 }
// single:       { "value": "Berpuas Hati" }
// multiple:     { "value": ["A", "B"] }
// short_text:   { "value": "Teks ringkas" }
// long_text:    { "value": "Teks panjang..." }

14. email_logs

Kolum Type Keterangan
id bigint PK
program_id bigint FK nullable
participant_id bigint FK nullable
certificate_id bigint FK nullable
recipient_email varchar(255) alamat emel penerima
subject varchar(500) subjek emel
email_type enum(certificate_ready,reminder,test) jenis emel
status enum(pending,sent,failed) default: pending
error_message text nullable ralat SMTP/queue
sent_at timestamp nullable
timestamps

Index: status, program_id, participant_id


15. audit_logs

Kolum Type Keterangan
id bigint PK
user_id bigint FK nullable admin yang buat action
action varchar(100) nama action: program.created, template.uploaded, dll
auditable_type varchar(255) nullable model class
auditable_id bigint nullable ID rekod berkaitan
old_values json nullable nilai sebelum (REDACT data sensitif)
new_values json nullable nilai selepas (REDACT data sensitif)
ip_address varchar(45) nullable
user_agent varchar(500) nullable
timestamps

Index: user_id, action, auditable_type + auditable_id

Actions yang perlu diaudit:

  • program.created, program.updated, program.deleted
  • template.uploaded, template.updated
  • questionnaire.confirmed, questionnaire.attached
  • certificate.generated, certificate.downloaded
  • participant.imported, participant.added

Migration Order (Dependency-safe)

1.  users                      (no dependency)
2.  programs                   (FK: users)
3.  program_qr_codes           (FK: programs)
4.  participants               (no dependency)
5.  program_participants       (FK: programs, participants)
6.  attendances                (FK: programs, participants, program_participants)
7.  certificate_templates      (FK: programs, users)
8.  questionnaire_sets         (FK: users)
9.  questionnaire_questions    (FK: questionnaire_sets)
10. program_questionnaires     (FK: programs, questionnaire_sets, users)
11. certificates               (FK: programs, participants, certificate_templates)
12. questionnaire_responses    (FK: programs, participants, questionnaire_sets)
13. questionnaire_answers      (FK: questionnaire_responses, questionnaire_questions)
14. email_logs                 (FK: programs, participants, certificates)
15. audit_logs                 (FK: users)

Data Integrity Rules

  1. participants.no_kp — UNIQUE global (satu orang = satu rekod).
  2. attendances (program_id, participant_id) — UNIQUE (tidak boleh check-in dua kali program sama).
  3. certificates (program_id, participant_id) — UNIQUE (satu sijil per peserta per program).
  4. questionnaire_responses (program_id, participant_id, questionnaire_set_id) — UNIQUE.
  5. program_participants (program_id, participant_id) — UNIQUE.
  6. Jika program status = closed atau published, rekod tidak boleh padam (soft delete sahaja jika perlu).
  7. no_kp dalam participants tidak dipaparkan dalam URL atau log — pakai uuid atau token.