Appearance
Data Governance
Dokumen ini jadi panduan teknis soal bagaimana data diatur, di-validate, dan di-protect di Soul Map Atlas. Semua implementasi atau fix yang berurusan dengan business logic dan data governance wajib update dokumen ini dan bagian overview di overview/product.md.
Data Ownership & Access Control
Role-Based Access
System pakai 3 role sederhana:
| Role | Deskripsi |
|---|---|
member | Bisa akses data sendiri doang. Default role untuk user baru. |
admin | Bisa akses data user lain untuk moderation dan support. |
owner | Sama kayak admin — tidak ada level di atas admin. Dipakai untuk superuser. |
Role check dilakukan via string comparison di middleware. Tidak ada ACL system atau permission matrix:
ts
// ✅ Sederhana dan cukup
if (user.role !== "admin" && user.role !== "owner") {
throw new ORPCError("FORBIDDEN");
}Data Isolation
- Setiap user hanya bisa lihat dan edit data milik sendiri.
- Admin/owner bisa override isolation untuk moderation.
- AI sessions dan journal entries strictly tied ke user ID.
Schema Validation & Type Safety
Input Validation
Semua API input wajib divalidate pakai Zod sebelum masuk ke business logic:
ts
// Contract layer
const createJournalSchema = z.object({
text: z.string().min(5),
moodTrend: z.object({
stress: z.number().min(1).max(10),
energy: z.number().min(1).max(10),
mood: z.number().min(1).max(10),
}).optional(),
tags: z.array(z.string()).optional(),
});Database Schema Single Source of Truth
- Schema utama ada di
packages/db/src/schema/. - Auth schema (user, session, account, verification) ada di
packages/db/src/schema/auth.ts. - Tidak boleh ada schema definition duplikat di tempat lain.
Business Logic Layer
Layer Architecture
Route (apps/api/src/routes/)
↓ call
Middleware (auth, validation, context build)
↓ call
Service (apps/api/src/services/)
↓ pure business logic, no auth awarenessServices layer wajib:
- Pure function — tidak boleh access request context atau env langsung.
- Menerima semua data yang dibutuhkan via parameter.
- Return typed result, throw error kalau gagal.
Example Service Pattern
ts
// services/journal.ts
export async function createJournalEntry(
db: DbClient,
userId: string,
data: CreateJournalInput
): Promise<JournalEntry> {
// Validation sudah di-route layer
const entry = await db.insert(journalEntries).values({
userId,
...data,
createdAt: new Date(),
}).returning();
return entry[0];
}Subscription & Usage Tracking
Plan Tiers
| Plan | Fitur |
|---|---|
free | Basic numerology, Primbon Jawa, 1 personality test |
deep_insights | Akses Deep Insights untuk compatibility |
premium | Unlimited personality + journal |
premium_plus | Semua fitur + prioritas AI sessions |
Usage Counters
Usage tracking di-level user dengan counter sederhana:
ts
// Schema snippet
usage: {
personalityUsed: number; // default 0
personalityLimit: number; // free = 1, premium = unlimited
deepInsightCompatibilityUsed: number;
deepInsightCompatibilityCredits: number; // one-time purchases
}Counter Rules
- Counter di-increment saat user memakai fitur.
- Check limit sebelum eksekusi — jangan charge dulu baru ngecek.
- One-time purchases (credits) dipakai sebelum subscription limit.
- Reset counter bulanan untuk subscription users via cron (belum diimplement).
Data Privacy & Compliance
Consent Records
- Setiap user harus setujui privacy policy dan terms of service saat signup.
- Consent record disimpan dengan timestamp dan version policy.
- Admin bisa export consent records untuk audit.
Personal Data
- Birth date, birth time, birth city — data sensitive yang hanya boleh diakses user sendiri dan AI service (untuk kalkulasi).
- Compatibility result disimpan encrypted-at-rest (encryption key via env).
- Journal entries strictly private — tidak ada sharing feature.
Data Retention
- User account dan data bisa di-delete via request ke admin.
- Soft delete untuk audit trail (mark
deletedAt, tidak hapus row). - Backup D1 via Cloudflare automatic backups.
AI Feature Governance
AI Session Limits
| Plan | AI Sessions per Month |
|---|---|
| free | 0 (tidak tersedia) |
| deep_insights | 5 |
| premium | 20 |
| premium_plus | unlimited |
AI Message Tracking
ai_sessions_useddanai_messages_useddi-track per bulan.- Reset otomatis via cron job (belum diimplement, akan di Phase 3).
AI Response Handling
- AI response tidak disimpan permanent — hanya context window saja.
- Kalau AI service error, return friendly error ke user, log ke monitoring.
Payment & Purchase Tracking
One-Time Purchases
- Deep Insights Compatibility bisa dibeli one-time tanpa subscription.
- Purchase record disimpan di array
purchasesdengan status:paid,pending,failed. - Credits tidak refundable.
Payment Status Flow
pending → paid → used
↓
failed (bisa retry)Webhook Security
- Payment webhooks (Doku) wajib di-verify signature.
- Idempotency key wajib untuk setiap transaksi.
- Log semua webhook events untuk audit.
Audit Trail
What to Log
Semua operasi yang mengubah data wajib di-log:
- User signup, login, logout
- Subscription change (upgrade/downgrade/cancel)
- Payment events
- Admin actions (view user data, moderation)
- AI session usage
Log Format
ts
interface AuditLog {
actorId: string; // siapa yang melakukan
actorType: "user" | "admin" | "system";
action: string; // apa yang dilakukan
resourceType: string; // tipe data yang diubah
resourceId: string; // ID data yang diubah
metadata: object; // detail tambahan
timestamp: Date;
}Log Storage
- Audit logs disimpan di D1 table
audit_logs. - Retention: 1 tahun.
- Admin bisa export via API (owner-only).
Anti-Patterns
- ❌ Business logic di route handler — pindahkan ke services layer.
- ❌ Direct database access dari frontend — semua query lewat API.
- ❌ Hardcode limits di kode — gunakan config/env.
- ❌ Skip validation sebelum business logic — Zod wajib di semua input.
- ❌ Store plain text password — selalu hash pakai scrypt.
- ❌ Expose internal error ke user — return generic error, log detail.