first
This commit is contained in:
272
DATABASE_DESIGN.md
Normal file
272
DATABASE_DESIGN.md
Normal 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.
|
||||
Reference in New Issue
Block a user