# Dokumentasi Folder `prisma/` ## Gambaran Umum Folder `prisma/` berisi semua konfigurasi dan migration files untuk database management menggunakan Prisma ORM. Folder ini adalah "blueprint" dari database schema aplikasi. --- ## 📁 Struktur Direktori ``` prisma/ ├── migrations/ # Database Migration History │ ├── 20251228085634_add_users_model/ │ │ └── migration.sql # Initial database schema │ ├── 20251228091450_add_menu_client_assignment/ │ │ └── migration.sql # Menu assignment feature │ └── migration_lock.toml # Migration lock file ├── schema.prisma # Database Schema Definition └── seed.ts # Database Seeder Script ``` --- ## 📄 File Utama ### **`schema.prisma`** ⭐ **Fungsi**: Schema definition file - Blueprint utama database **Location**: `/prisma/schema.prisma` **Size**: ~2.6 KB #### **Generator Configuration**: ```prisma generator client { provider = "prisma-client" output = "../app/generated/client" } ``` - Generate Prisma Client TypeScript - Output ke `app/generated/client/` untuk import di aplikasi #### **Datasource Configuration**: ```prisma datasource db { provider = "postgresql" url = env("DATABASE_URL") } ``` - Database: PostgreSQL - Connection string dari environment variable `DATABASE_URL` --- ## 📊 Database Models ### **1. Model `users`** 👥 **Fungsi**: Menyimpan data pengguna (Coach & Client) **Fields**: | Field | Type | Description | Constraint | |-------|------|-------------|------------| | `id` | String | User ID unik | Primary Key, default: cuid() | | `name` | String | Nama lengkap | VARCHAR, NOT NULL | | `role` | String | Role pengguna | VARCHAR, "COACH" atau "CLIENT" | | `coach_id` | String? | ID coach (untuk client) | Foreign Key → users.id | | `created_at` | DateTime? | Waktu registrasi | Default: now() | **Relations**: - `coach` → Self-relation ke `users` (Many-to-One) - Client memiliki 1 coach - `clients` → Self-relation ke `users` (One-to-Many) - Coach memiliki banyak clients - `created_menus` → `training_menus[]` (One-to-Many) - Coach dapat membuat banyak menu - `assigned_menus` → `training_menus[]` (One-to-Many) - Client dapat memiliki banyak assigned menus - `recaps` → `user_recaps[]` (One-to-Many) - User dapat memiliki banyak workout recaps - `activity_logs` → `activity_logs[]` (One-to-Many) - User dapat memiliki banyak activity logs **Indexes**: - `ix_users_id` on `id` (Primary) - `ix_users_coach_id` on `coach_id` (Query optimization) **ID Format**: - Coach: `C00001`, `C00002`, etc. - Client: `U00001`, `U00002`, etc. **Business Logic**: ```typescript // Coach can have multiple clients Coach (C00001) └─> Clients: [U00001, U00002] // Client belongs to one coach Client (U00001) └─> Coach: C00001 ``` --- ### **2. Model `activity_logs`** 📝 **Fungsi**: Log aktivitas real-time (Standing/Sitting/Fall detection) **Fields**: | Field | Type | Description | |-------|------|-------------| | `id` | Int | Auto-increment ID (Primary Key) | | `timestamp` | DateTime? | Waktu log dibuat | | `status` | String? | "Standing", "Sitting", "Fall Detected" | | `confidence` | String? | Confidence score dari XGBoost | | `details` | Json? | Additional metadata | | `user_id` | String? | Foreign Key → users.id | **Relations**: - `user` → `users` (Many-to-One) **Indexes**: - `ix_activity_logs_id` on `id` - `ix_activity_logs_user_id` on `user_id` **Use Case**: - Real-time monitoring di `/client/monitor` - Historical activity tracking - Fall detection alerts **Example Data**: ```json { "id": 1, "timestamp": "2025-12-28T10:30:00Z", "status": "Standing", "confidence": "0.95", "details": { "exercise": "bicep_curl", "reps": 5 }, "user_id": "U00001" } ``` --- ### **3. Model `training_menus`** 📋 **Fungsi**: Menyimpan workout programs/menus yang dibuat coach **Fields**: | Field | Type | Description | |-------|------|-------------| | `id` | Int | Auto-increment ID (Primary Key) | | `name` | String? | Nama menu (e.g., "Upper Body Day 1") | | `exercises` | Json? | Array of exercise objects | | `created_at` | DateTime? | Waktu pembuatan | | `author_id` | String? | Foreign Key → users.id (Coach yang buat) | | `client_id` | String? | Foreign Key → users.id (Client assigned) | **Relations**: - `author` → `users` (Many-to-One, relation: "CreatedMenus") - Menu dibuat oleh 1 coach - `assigned_client` → `users` (Many-to-One, relation: "AssignedMenus") - Menu ditugaskan kepada 1 client - `user_recaps` → `user_recaps[]` (One-to-Many) - Menu bisa memiliki banyak recap results **Indexes**: - `ix_training_menus_id` on `id` - `ix_training_menus_name` on `name` - `ix_training_menus_author_id` on `author_id` **JSON Structure untuk `exercises` field**: ```json [ { "name": "Bicep Curl", "set_index": 1, "reps": 10, "weight": 15, "rest": 60 }, { "name": "Hammer Curl", "set_index": 1, "reps": 12, "weight": 12, "rest": 60 } ] ``` **Business Logic**: ```typescript // Coach creates menu Coach (C00001) └─> Creates Menu (id: 1, "Upper Body") └─> Assigns to Client (U00001) // Client sees assigned menu Client (U00001) └─> Assigned Menu: "Upper Body" (id: 1) ``` --- ### **4. Model `user_recaps`** 📈 **Fungsi**: Menyimpan hasil latihan (training recap) setelah workout selesai **Fields**: | Field | Type | Description | |-------|------|-------------| | `id` | Int | Auto-increment ID (Primary Key) | | `menu_id` | Int? | Foreign Key → training_menus.id | | `user_id` | String? | Foreign Key → users.id | | `summary` | Json? | Workout summary data | | `completed_at` | DateTime? | Waktu workout selesai | **Relations**: - `training_menus` → `training_menus` (Many-to-One) - Recap terkait dengan 1 menu - `user` → `users` (Many-to-One) - Recap milik 1 user **Indexes**: - `ix_user_recaps_id` on `id` - `ix_user_recaps_user_id` on `user_id` **JSON Structure untuk `summary` field**: ```json { "completed": true, "exercises": [ { "name": "Bicep Curl", "set_index": 1, "reps": 10, "weight": 15, "rest": 60 } ], "timestamp": "2025-12-28T11:00:00Z", "results": [ { "name": "Bicep Curl", "set": 1, "reps": 10, "weight": 15, "score": 12.5, "repDetails": [ { "rep": 1, "score": 8.2, "feedback": "Perfect" }, { "rep": 2, "score": 14.5, "feedback": "Elbow moving forward" } ] } ] } ``` **Key Features di Summary**: - ✅ Overall workout stats - ✅ Per-set average form scores - ✅ **Per-rep breakdown** dengan: - Rep number - MAE score - Specific feedback text --- ## 🔄 Database Relationships Diagram ```mermaid erDiagram users ||--o{ users : "coach-client" users ||--o{ training_menus : "creates (author)" users ||--o{ training_menus : "assigned to (client)" users ||--o{ user_recaps : "performs workout" users ||--o{ activity_logs : "generates logs" training_menus ||--o{ user_recaps : "tracked in recap" users { string id PK string name string role string coach_id FK } training_menus { int id PK string name json exercises string author_id FK string client_id FK } user_recaps { int id PK int menu_id FK string user_id FK json summary } activity_logs { int id PK string status string user_id FK } ``` --- ## 📁 Folder `migrations/` ### **Gambaran Umum** Folder yang berisi history semua perubahan database schema. ### **Migration Files**: #### **1. `20251228085634_add_users_model/migration.sql`** **Tanggal**: 28 Desember 2025, 08:56:34 **Fungsi**: Initial database schema creation **Changes**: - ✅ Create table `users` dengan self-referencing foreign key - ✅ Create table `activity_logs` dengan JSONB details - ✅ Create table `training_menus` dengan JSONB exercises - ✅ Create table `user_recaps` dengan JSONB summary - ✅ Create indexes untuk optimization: - User ID index - Coach ID index - Menu name index - Author ID index - User recap user_id index - ✅ Add foreign key constraints dengan proper ON DELETE/UPDATE actions **Key SQL**: ```sql CREATE TABLE "users" ( "id" TEXT NOT NULL, "name" VARCHAR NOT NULL, "role" VARCHAR NOT NULL, "coach_id" TEXT, "created_at" TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "users_pkey" PRIMARY KEY ("id") ); -- Self-referencing foreign key ALTER TABLE "users" ADD CONSTRAINT "users_coach_id_fkey" FOREIGN KEY ("coach_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; ``` **Size**: ~2.7 KB --- #### **2. `20251228091450_add_menu_client_assignment/migration.sql`** **Tanggal**: 28 Desember 2025, 09:14:50 **Fungsi**: (Merged into previous migration) **Status**: ⚠️ Skipped/No-op migration **Reason**: - Original purpose: Add `client_id` to `training_menus` - Already included in first migration to fix type mismatches - File kept untuk preserve migration history order **Content**: ```sql -- This migration is skipped because its changes (adding client_id) -- were manually merged into the previous migration to fix type mismatches -- This file is kept to preserve migration history order. ``` --- #### **`migration_lock.toml`** **Fungsi**: Lock file untuk ensure consistent database provider **Content**: ```toml # Please do not edit this file manually # It should be added in your version-control system (e.g., Git) provider = "postgresql" ``` **Purpose**: - Mencegah accidental switch ke database provider lain - Ensure semua developer/environment menggunakan PostgreSQL - Auto-generated oleh Prisma CLI --- ## 📄 File `seed.ts` **Fungsi**: Database seeder untuk populate initial test data **Location**: `/prisma/seed.ts` **Size**: ~1.6 KB ### **Seed Data**: **2 Coaches**: 1. `C00001` - "Coach One" 2. `C00002` - "Coach Two" **3 Clients**: 1. `U00001` - "Client One" (assigned to Coach One) 2. `U00002` - "Client Two" (assigned to Coach One) 3. `U00003` - "Client Three" (assigned to Coach Two) ### **Script Logic**: ```typescript import { PrismaClient } from "../app/generated/client/client"; const prisma = new PrismaClient(); async function main() { // Upsert coaches const coach1 = await prisma.users.upsert({ where: { id: "C00001" }, update: {}, create: { id: "C00001", name: "Coach One", role: "COACH", }, }); // Upsert clients with coach_id const client1 = await prisma.users.upsert({ where: { id: "U00001" }, update: {}, create: { id: "U00001", name: "Client One", role: "CLIENT", coach_id: coach1.id, }, }); } ``` ### **Run Seeder**: ```bash npx prisma db seed ``` **Use Case**: - Development/testing data - Demo accounts - Quick reset database dengan data awal --- ## 🛠️ Prisma Commands ### **Essential Commands**: #### **1. Generate Prisma Client** ```bash npx prisma generate ``` - Regenerate TypeScript client dari schema - Run setelah setiap perubahan `schema.prisma` - Output ke `app/generated/client/` #### **2. Create Migration** ```bash npx prisma migrate dev --name migration_name ``` - Create migration file baru - Apply migration ke database - Update Prisma Client #### **3. Apply Migration (Production)** ```bash npx prisma migrate deploy ``` - Apply pending migrations - Untuk production environment - Tidak auto-generate client #### **4. Reset Database** ```bash npx prisma migrate reset ``` - Drop database - Re-run all migrations - Run seed script - ⚠️ **DANGER**: Deletes all data! #### **5. Prisma Studio (GUI)** ```bash npx prisma studio ``` - Open web GUI untuk browse/edit data - URL: `http://localhost:5555` - Visual database management #### **6. Format Schema** ```bash npx prisma format ``` - Auto-format `schema.prisma` - Fix indentation dan spacing #### **7. Validate Schema** ```bash npx prisma validate ``` - Check schema for errors - Verify relations dan syntax --- ## 🔑 Key Concepts ### **Migration Strategy**: 1. **Development**: - Use `prisma migrate dev` - Creates migration + applies + generates client - Safe untuk experiments 2. **Production**: - Use `prisma migrate deploy` - Never use `migrate dev` in prod - Always test migrations di staging first ### **Schema Best Practices**: 1. **Naming Conventions**: - Tables: `snake_case` (e.g., `training_menus`) - Fields: `snake_case` (e.g., `coach_id`) - Relations: `camelCase` di Prisma model (e.g., `assignedClient`) 2. **Indexes**: - Add index untuk foreign keys - Add index untuk frequently queried fields - Consider composite indexes untuk complex queries 3. **JSONB Usage**: - ✅ Good for: Flexible nested data (exercises, recap summary) - ❌ Avoid for: Searchable/filterable data - Use `Json` type di Prisma, becomes `JSONB` di PostgreSQL 4. **ID Strategy**: - Users: Custom strings (`C00001`, `U00001`) - Other tables: Auto-increment integers - Consider UUID untuk distributed systems --- ## 📊 Database Size Estimates **Assuming Active Usage**: | Table | Rows/Month | Storage | | ---------------- | ---------- | --------------------------- | | `users` | ~10 | < 1 KB | | `training_menus` | ~50 | ~5 KB | | `user_recaps` | ~500 | ~500 KB (with per-rep data) | | `activity_logs` | ~10,000 | ~1 MB | **Total**: ~2 MB/month dengan moderate usage **Optimization Tips**: - Archive old `activity_logs` after 30 days - Compress old recaps - Add pagination untuk large queries --- ## 🔐 Security Considerations ### **Implemented**: - ✅ Foreign key constraints prevent orphaned data - ✅ Indexes prevent slow queries (avoid DOS) - ✅ ON DELETE SET NULL untuk soft deletes ### **TODO / Recommendations**: - [ ] Add `email` field dengan `@unique` constraint - [ ] Add `password_hash` field (currently not in schema!) - [ ] Add `role` as enum type instead of string - [ ] Add `soft_delete` timestamp instead of hard delete - [ ] Add row-level security (RLS) di PostgreSQL --- ## 🚀 Future Enhancements ### **Planned Schema Changes**: 1. **Add Authentication Fields**: ```prisma model users { email String @unique password_hash String email_verified Boolean @default(false) } ``` 2. **Add Workout Sessions** (untuk track progress over time): ```prisma model workout_sessions { id Int @id @default(autoincrement()) user_id String menu_id Int started_at DateTime ended_at DateTime? status String // "IN_PROGRESS" | "COMPLETED" | "ABANDONED" } ``` 3. **Add Exercise Library** (normalize exercises): ```prisma model exercises { id Int @id @default(autoincrement()) name String @unique category String // "UPPER" | "LOWER" | "CORE" instructions Json? } ``` 4. **Add Personal Records**: ```prisma model personal_records { id Int @id @default(autoincrement()) user_id String exercise String weight_kg Float reps Int achieved_at DateTime } ``` --- ## 💡 Troubleshooting ### **Common Issues**: #### **1. Migration Failed** ```bash Error: Migration failed to apply ``` **Solution**: ```bash # Reset database npx prisma migrate reset # Or fix manually npx prisma db push --skip-generate npx prisma generate ``` #### **2. Client Out of Sync** ```bash Error: Prisma Client is out of sync with schema ``` **Solution**: ```bash npx prisma generate ``` #### **3. Connection Error** ```bash Error: Can't reach database server ``` **Check**: 1. PostgreSQL running? (`sudo systemctl status postgresql`) 2. `DATABASE_URL` correct di `.env`? 3. Network/firewall issues? #### **4. Seed Script Fails** ```bash Error: Unique constraint violation ``` **Solution**: Data already exists ```bash # Clear database first npx prisma migrate reset ``` --- ## 📚 Resources **Official Docs**: - [Prisma Schema Reference](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference) - [Prisma Migrate](https://www.prisma.io/docs/concepts/components/prisma-migrate) - [Prisma Client API](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference) **Tutorials**: - [Prisma Quickstart](https://www.prisma.io/docs/getting-started/quickstart) - [PostgreSQL Best Practices](https://wiki.postgresql.org/wiki/Best_practices) --- ## ✅ Checklist Maintenance - [x] Schema documented - [x] Migrations tracked in Git - [x] Seed data available - [ ] Backup strategy defined - [ ] Migration rollback tested - [ ] Production deployment guide - [ ] Performance benchmarks