This commit is contained in:
Saufi
2026-06-02 17:35:45 +08:00
commit 4ef99b1f81
148 changed files with 21134 additions and 0 deletions

272
DATABASE_DESIGN.md Normal file
View File

@@ -0,0 +1,272 @@
# DATABASE_DESIGN.md — Speech2Text MBIP
## Senarai Jadual
1. `users`
2. `departments`
3. `transcription_projects`
4. `project_collaborators`
5. `transcript_versions`
6. `project_comments`
7. `audit_logs`
8. `sessions`
9. `jobs` / `failed_jobs`
10. `cache`
---
## Skema Jadual
### 1. `users`
```sql
CREATE TABLE users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role ENUM('admin', 'user') NOT NULL DEFAULT 'user',
department_id BIGINT UNSIGNED NULL,
is_active TINYINT(1) NOT NULL DEFAULT 1,
last_login_at TIMESTAMP NULL,
email_verified_at TIMESTAMP NULL,
remember_token VARCHAR(100) NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
deleted_at TIMESTAMP NULL, -- soft delete
FOREIGN KEY (department_id) REFERENCES departments(id) ON DELETE SET NULL
);
```
**Catatan:**
- `deleted_at` hanya digunakan jika pengguna belum pernah guna aplikasi (tiada projek, tiada audit).
- Jika pernah guna, hanya `is_active = 0` (deactivate), jangan hard delete.
- `role` enum mudah diurus; boleh upgrade ke `spatie/laravel-permission` kemudian jika perlu.
---
### 2. `departments`
```sql
CREATE TABLE departments (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
code VARCHAR(50) NULL UNIQUE,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL
);
```
---
### 3. `transcription_projects`
```sql
CREATE TABLE transcription_projects (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
uuid CHAR(36) NOT NULL UNIQUE, -- digunakan dalam URL
title VARCHAR(255) NOT NULL,
description TEXT NULL,
owner_user_id BIGINT UNSIGNED NOT NULL,
original_filename VARCHAR(500) NOT NULL,
stored_audio_path VARCHAR(1000) NOT NULL, -- path relatif dalam private disk
mime_type VARCHAR(100) NOT NULL,
file_size BIGINT UNSIGNED NOT NULL, -- bytes
duration_seconds INT UNSIGNED NULL,
language VARCHAR(10) NOT NULL DEFAULT 'ms',
transcription_status ENUM('pending','processing','completed','failed') NOT NULL DEFAULT 'pending',
transcription_engine VARCHAR(50) NULL, -- e.g. 'faster-whisper'
transcript_text LONGTEXT NULL, -- kandungan sensitif
transcript_confidence DECIMAL(5,4) NULL, -- 0.0000 - 1.0000
error_message TEXT NULL,
processed_at TIMESTAMP NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
deleted_at TIMESTAMP NULL, -- soft delete
FOREIGN KEY (owner_user_id) REFERENCES users(id) ON DELETE RESTRICT
);
```
**Catatan keselamatan:**
- `stored_audio_path` adalah path dalam private storage, bukan URL awam.
- `transcript_text` disimpan dalam database. Untuk keselamatan lanjut, boleh encrypt menggunakan Laravel `encrypted` cast.
- Admin **tidak** boleh SELECT `transcript_text`, `stored_audio_path` melalui policy/query scope.
---
### 4. `project_collaborators`
```sql
CREATE TABLE project_collaborators (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
project_id BIGINT UNSIGNED NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
role ENUM('editor', 'viewer') NOT NULL DEFAULT 'editor',
added_by BIGINT UNSIGNED NOT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
UNIQUE KEY unique_project_user (project_id, user_id),
FOREIGN KEY (project_id) REFERENCES transcription_projects(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (added_by) REFERENCES users(id) ON DELETE RESTRICT
);
```
---
### 5. `transcript_versions`
```sql
CREATE TABLE transcript_versions (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
project_id BIGINT UNSIGNED NOT NULL,
edited_by BIGINT UNSIGNED NOT NULL,
version_number INT UNSIGNED NOT NULL,
old_text LONGTEXT NULL,
new_text LONGTEXT NOT NULL,
change_summary VARCHAR(500) NULL,
created_at TIMESTAMP NULL,
FOREIGN KEY (project_id) REFERENCES transcription_projects(id) ON DELETE CASCADE,
FOREIGN KEY (edited_by) REFERENCES users(id) ON DELETE RESTRICT
);
```
**Catatan:**
- `old_text` dan `new_text` adalah snapshot penuh, bukan diff, untuk kemudahan restore.
- Jangan masukkan `transcript_text` dalam `audit_logs`; gunakan jadual ini sebagai ganti.
- Admin tidak boleh akses jadual ini.
---
### 6. `project_comments`
```sql
CREATE TABLE project_comments (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
project_id BIGINT UNSIGNED NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
message TEXT NOT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
deleted_at TIMESTAMP NULL, -- soft delete
FOREIGN KEY (project_id) REFERENCES transcription_projects(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT
);
```
---
### 7. `audit_logs`
```sql
CREATE TABLE audit_logs (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
actor_user_id BIGINT UNSIGNED NULL, -- NULL jika sistem/background job
actor_role VARCHAR(50) NULL,
action VARCHAR(100) NOT NULL, -- e.g. 'user_deactivated'
subject_type VARCHAR(100) NULL, -- e.g. 'App\Models\User'
subject_id BIGINT UNSIGNED NULL,
target_user_id BIGINT UNSIGNED NULL,
project_id BIGINT UNSIGNED NULL,
old_values JSON NULL, -- JANGAN masukkan transcript content
new_values JSON NULL, -- JANGAN masukkan transcript content
justification TEXT NULL,
ip_address VARCHAR(45) NULL, -- support IPv6
user_agent TEXT NULL,
created_at TIMESTAMP NULL,
INDEX idx_actor (actor_user_id),
INDEX idx_action (action),
INDEX idx_project (project_id),
INDEX idx_created (created_at)
);
```
**Tindakan yang diaudit:**
| action | Penerangan |
|---|---|
| `user_created` | Admin daftar pengguna baru |
| `user_deactivated` | Admin deactivate pengguna |
| `user_reactivated` | Admin aktifkan semula pengguna |
| `user_deleted` | Admin delete pengguna (hanya jika belum guna) |
| `user_email_changed` | Admin tukar emel pengguna |
| `project_created` | Pengguna cipta projek |
| `audio_uploaded` | Pengguna muat naik audio |
| `transcription_started` | Queue worker mula proses |
| `transcription_completed` | Queue worker selesai |
| `transcription_failed` | Queue worker gagal |
| `transcript_updated` | Owner/collaborator edit teks |
| `transcript_version_restored` | Restore versi lama |
| `collaborator_added` | Owner tambah collaborator |
| `collaborator_removed` | Owner buang collaborator |
| `comment_created` | Pengguna buat komen |
| `project_deleted` | Owner delete projek |
| `project_owner_transferred` | Admin transfer ownership |
---
## Hubungan Model (Eloquent Relationships)
```
User
├── hasMany: TranscriptionProject (as owner)
├── belongsToMany: TranscriptionProject (through ProjectCollaborator)
├── hasMany: TranscriptVersion (as editor)
├── hasMany: ProjectComment
├── belongsTo: Department
└── hasMany: AuditLog (as actor)
TranscriptionProject
├── belongsTo: User (owner)
├── hasMany: ProjectCollaborator
├── hasMany: TranscriptVersion
├── hasMany: ProjectComment
└── belongsToMany: User (collaborators)
Department
└── hasMany: User
```
---
## Indeks Penting
```sql
-- Cari projek mengikut status (untuk admin dashboard)
ALTER TABLE transcription_projects ADD INDEX idx_status (transcription_status);
-- Cari projek mengikut owner
ALTER TABLE transcription_projects ADD INDEX idx_owner (owner_user_id);
-- Cari versi mengikut projek (timeline)
ALTER TABLE transcript_versions ADD INDEX idx_project_version (project_id, version_number);
-- Audit log search
ALTER TABLE audit_logs ADD INDEX idx_target_user (target_user_id);
ALTER TABLE audit_logs ADD INDEX idx_subject (subject_type, subject_id);
```
---
## Nota Keselamatan Data
1. **`transcript_text`** — Kolum sensitif. Boleh encrypt menggunakan Laravel cast `encrypted`:
```php
protected $casts = [
'transcript_text' => 'encrypted',
];
```
Ini encrypt menggunakan `APP_KEY`. Pastikan `APP_KEY` disimpan dengan selamat.
2. **`stored_audio_path`** — Simpan path relatif sahaja, bukan absolute path. Contoh: `transcriptions/abc-uuid/audio/recording.mp3`.
3. **Audit log** — Jangan masukkan `transcript_text` dalam `old_values` atau `new_values`. Gunakan `transcript_versions` untuk simpan snapshot teks.
4. **Soft delete**`transcription_projects` dan `project_comments` menggunakan soft delete. Fail audio fizikal dikekalkan dalam private storage sehingga admin jalankan retention cleanup.