Entity Relationship Diagram (ERD) FPDP — Bahasa Indonesia
1. Tujuan dan cakupan
Dokumen ini menjelaskan:
1. skema fisik saat ini pada <code>database/schema.sql</code>; dan
2. model data logis target yang dibutuhkan oleh product requirements, kontrak OpenAPI, dan roadmap pengembangan.
Pembedaan tersebut penting: entity target merupakan rancangan dan belum tersedia pada aplikasi. Field pada diagram target menunjukkan field relasi minimum, bukan spesifikasi migration lengkap.
2. Legenda dan konvensi
| Penanda | Arti |
|---|---|
| Saat ini | Tabel tersedia pada database/schema.sql |
| Direncanakan | Tabel diperlukan produk target tetapi belum diimplementasikan |
| PK | Primary key |
| FK | Foreign key |
| UK | Unique key atau unique constraint |
| || | Tepat satu |
| o| | Nol atau satu |
| o{ | Nol atau banyak |
| |{ | Satu atau banyak |
Konvensi database yang disarankan untuk migration baru:
- Gunakan satu strategi ID secara konsisten. API mengekspos UUID; gunakan UUID sebagai primary key database atau gunakan numeric key internal dengan UUID publik yang unik.
- Simpan timestamp dalam UTC dan tampilkan sesuai timezone pengguna.
- Gunakan DECIMAL fixed-precision, bukan floating point, untuk uang.
- Enkripsi credential dan token provider ketika disimpan.
- Gunakan soft deletion hanya jika dibutuhkan oleh retention atau recovery.
- Tambahkan foreign key, unique constraint, dan index melalui migration berurutan.
- Jaga canonical source data tetap immutable ketika diperlukan untuk atribusi atau histori order.
3. ERD fisik saat ini
File SQL saat ini membuat sembilan tabel. Relasi berikut disimpulkan dari nama kolom karena schema belum mendeklarasikan foreign-key constraint.
erDiagram
PAYMENT_GATEWAYS ||--o{ PAYMENT_GATEWAY_CONFIGS : "memiliki konfigurasi"
PAYMENT_GATEWAYS ||--o{ PAYMENTS : "memproses berdasarkan kode (asumsi)"
PAYMENTS ||--o{ PAYMENT_TRANSACTIONS : "mencatat event"
EXTERNAL_ACCOUNTS o|--o{ EXTERNAL_FEED_SOURCES : "mengotorisasi"
EXTERNAL_ACCOUNTS o|--o{ EXTERNAL_POSTS : "memiliki identitas remote"
EXTERNAL_FEED_SOURCES ||--o{ EXTERNAL_POSTS : "mengimpor (asumsi)"
PAYMENT_GATEWAYS {
int id PK
varchar code
varchar name
varchar adapter_class
varchar status
boolean supports_refund
boolean supports_recurring
boolean supports_qris
boolean supports_va
boolean supports_credit_card
boolean supports_ewallet
}
PAYMENT_GATEWAY_CONFIGS {
int id PK
int gateway_id FK
varchar config_key
text encrypted_value
varchar environment
boolean is_active
}
PAYMENTS {
int id PK
char uuid
varchar order_id
varchar gateway_code
varchar external_transaction_id
varchar payment_method
char currency
decimal amount
decimal fee
varchar status
varchar payment_url
timestamp expired_at
timestamp paid_at
}
PAYMENT_TRANSACTIONS {
int id PK
int payment_id FK
varchar provider
varchar external_id
varchar event_type
varchar status
json payload
}
EXTERNAL_ACCOUNTS {
int id PK
int user_id FK
varchar provider
varchar external_account_id
varchar external_username
varchar display_name
varchar profile_url
text access_token
text refresh_token
timestamp token_expires_at
json permissions
varchar connection_status
timestamp last_sync_at
}
EXTERNAL_FEED_SOURCES {
int id PK
int user_id FK
varchar provider
varchar source_type
varchar source_url
int external_account_id FK
boolean sync_enabled
int sync_interval
timestamp last_sync_at
timestamp next_sync_at
varchar status
}
EXTERNAL_POSTS {
int id PK
int user_id FK
varchar provider
varchar external_post_id
int external_account_id FK
varchar post_type
varchar canonical_url
varchar title
longtext content
json media_json
varchar author_name
timestamp published_at
timestamp fetched_at
json raw_payload
varchar status
}
CONNECTOR_DEFINITIONS {
int id PK
varchar code
varchar name
varchar adapter_class
varchar auth_type
boolean supports_sync
boolean supports_webhook
boolean supports_profile
boolean supports_posts
boolean supports_products
varchar status
}
INTEGRATION_QUEUE {
int id PK
int user_id FK
varchar provider
varchar job_type
json payload
varchar status
int retry_count
timestamp next_retry_at
text last_error
}
Gap pada schema saat ini
- users dan orders belum tersedia walaupun user_id dan order_id digunakan.
- Belum ada foreign-key constraint eksplisit.
- Kode gateway dan connector belum memiliki unique constraint.
- Unique rule external post saat ini bersifat komposit: (provider, external_post_id).
- external_posts tidak memiliki external_feed_source_id, sehingga sumber pengimpor tidak dapat dipastikan.
- Payment menggunakan gateway_code dan bukan constrained gateway foreign key.
- Idempotency payment event belum dijamin oleh unique provider event key.
- Credential masih berupa kolom text generik; enkripsi pada application layer belum tersedia.
- Queue locking, attempt history, dan dead-letter state belum dimodelkan.
- Beberapa index untuk query operasional belum tersedia.
4. Ringkasan model domain target
| Domain | Entity direncanakan | Entity saat ini yang digunakan/migrasi |
|---|---|---|
| Identity dan node | nodes, users, profiles, auth_tokens, audit_events | Tidak ada |
| Konten lokal | posts, post_media | Tidak ada |
| Integrasi eksternal | external_accounts, external_feed_sources, external_posts, integration_jobs, integration_job_attempts, connector_definitions | Tabel integrasi yang ada |
| Commerce | products, orders, order_items | Tidak ada |
| Payment | payment_gateways, payment_gateway_configs, payments, payment_events, refunds, idempotency_keys | Tabel payment yang ada |
| Federasi | remote_nodes, remote_actors, federated_objects, federation_activities, follows, moderation_rules, reports | Tidak ada |
5. ERD target — identity dan konten lokal
erDiagram
NODES ||--|{ USERS : "memiliki anggota"
NODES ||--o{ POSTS : "menjadi host"
USERS ||--|| PROFILES : "memiliki"
USERS ||--o{ AUTH_TOKENS : "menggunakan"
USERS ||--o{ POSTS : "menulis"
POSTS ||--o{ POST_MEDIA : "berisi"
USERS ||--o{ AUDIT_EVENTS : "melakukan aksi"
NODES ||--o{ AUDIT_EVENTS : "mencatat"
NODES {
bigint id PK
uuid public_id UK
varchar domain UK
varchar name
varchar default_locale
varchar timezone
varchar status
timestamp created_at
}
USERS {
bigint id PK
uuid public_id UK
bigint node_id FK
varchar email UK
varchar password_hash
varchar role
varchar status
timestamp created_at
}
PROFILES {
bigint id PK
uuid public_id UK
bigint user_id FK,UK
varchar handle
varchar display_name
text bio
varchar avatar_url
varchar visibility
json links
timestamp updated_at
}
AUTH_TOKENS {
bigint id PK
bigint user_id FK
char token_hash UK
varchar token_type
json scopes
timestamp expires_at
timestamp revoked_at
}
POSTS {
bigint id PK
uuid public_id UK
bigint node_id FK
bigint author_user_id FK
varchar slug
varchar post_type
varchar title
longtext content
varchar status
varchar visibility
varchar canonical_url UK
timestamp published_at
timestamp deleted_at
}
POST_MEDIA {
bigint id PK
bigint post_id FK
varchar media_type
varchar storage_key
varchar public_url
varchar alt_text
int sort_order
}
AUDIT_EVENTS {
bigint id PK
bigint node_id FK
bigint actor_user_id FK
varchar action
varchar subject_type
varchar subject_public_id
json metadata
timestamp created_at
}
Constraint penting:
- profiles.user_id unik: satu profil aktif untuk setiap user.
- (node_id, handle) dan (node_id, slug) unik.
- Password dan bearer token tidak pernah disimpan sebagai plaintext.
- Canonical URL post lokal harus stabil setelah publikasi.
- Post private atau soft-deleted harus dikecualikan oleh visibility rule pada repository.
6. ERD target — integrasi eksternal
erDiagram
USERS ||--o{ EXTERNAL_ACCOUNTS : "menghubungkan"
USERS ||--o{ EXTERNAL_FEED_SOURCES : "mengatur"
CONNECTOR_DEFINITIONS ||--o{ EXTERNAL_ACCOUNTS : "menentukan adapter"
CONNECTOR_DEFINITIONS ||--o{ EXTERNAL_FEED_SOURCES : "menentukan adapter"
EXTERNAL_ACCOUNTS o|--o{ EXTERNAL_FEED_SOURCES : "mengotorisasi"
EXTERNAL_FEED_SOURCES ||--o{ EXTERNAL_POSTS : "mengimpor"
EXTERNAL_FEED_SOURCES ||--o{ INTEGRATION_JOBS : "menjadwalkan"
INTEGRATION_JOBS ||--o{ INTEGRATION_JOB_ATTEMPTS : "memiliki percobaan"
CONNECTOR_DEFINITIONS {
bigint id PK
varchar code UK
varchar adapter_class
varchar auth_type
json capabilities
varchar status
}
EXTERNAL_ACCOUNTS {
bigint id PK
uuid public_id UK
bigint user_id FK
bigint connector_id FK
varchar provider_account_id
varchar username
text encrypted_access_token
text encrypted_refresh_token
timestamp token_expires_at
varchar status
}
EXTERNAL_FEED_SOURCES {
bigint id PK
uuid public_id UK
bigint user_id FK
bigint connector_id FK
bigint external_account_id FK
varchar source_url
char source_url_hash
int sync_interval_seconds
varchar default_visibility
boolean sync_enabled
varchar status
timestamp last_sync_at
timestamp next_sync_at
}
EXTERNAL_POSTS {
bigint id PK
uuid public_id UK
bigint source_id FK
varchar provider_post_id
varchar post_type
varchar title
longtext content
varchar canonical_url
json media
json author
timestamp published_at
timestamp fetched_at
char content_hash
varchar status
}
INTEGRATION_JOBS {
bigint id PK
uuid public_id UK
bigint source_id FK
varchar job_type
varchar status
int attempt_count
timestamp available_at
timestamp locked_at
varchar locked_by
text last_error
}
INTEGRATION_JOB_ATTEMPTS {
bigint id PK
bigint job_id FK
int attempt_number
varchar status
timestamp started_at
timestamp finished_at
text sanitized_error
}
Constraint penting:
- (connector_id, provider_account_id, user_id) unik ketika account ID tersedia.
- (user_id, connector_id, source_url_hash) mencegah konfigurasi sumber duplikat.
- (source_id, provider_post_id) unik dan menjadi kunci deduplikasi impor utama.
- Hanya credential terenkripsi yang disimpan; admin hanya menerima nilai yang dimasking.
- Job claiming harus memakai strategi lock/update atomik.
- Retention raw payload harus dibatasi dan tidak boleh menyimpan secret.
7. ERD target — commerce dan payment
erDiagram
NODES ||--o{ PRODUCTS : "menjual"
USERS ||--o{ PRODUCTS : "memiliki"
NODES ||--o{ ORDERS : "menerima"
USERS o|--o{ ORDERS : "membuat"
ORDERS ||--|{ ORDER_ITEMS : "berisi"
PRODUCTS ||--o{ ORDER_ITEMS : "disalin ke"
ORDERS ||--o{ PAYMENTS : "memiliki percobaan"
PAYMENT_GATEWAYS ||--o{ PAYMENT_GATEWAY_CONFIGS : "dikonfigurasi"
PAYMENT_GATEWAYS ||--o{ PAYMENTS : "memproses"
PAYMENTS ||--o{ PAYMENT_EVENTS : "menerima"
PAYMENTS ||--o{ REFUNDS : "memiliki refund"
IDEMPOTENCY_KEYS o|--o| PAYMENTS : "melindungi pembuatan"
IDEMPOTENCY_KEYS o|--o| REFUNDS : "melindungi pembuatan"
PRODUCTS {
bigint id PK
uuid public_id UK
bigint node_id FK
bigint owner_user_id FK
varchar slug
varchar name
text description
decimal price_amount
char currency
int stock_quantity
varchar status
varchar source_type
varchar canonical_url
}
ORDERS {
bigint id PK
uuid public_id UK
bigint node_id FK
bigint buyer_user_id FK
char guest_token_hash UK
varchar customer_email
varchar customer_name
decimal total_amount
char currency
varchar status
timestamp created_at
}
ORDER_ITEMS {
bigint id PK
bigint order_id FK
bigint product_id FK
varchar product_name_snapshot
decimal unit_price_amount
char currency
int quantity
decimal subtotal_amount
}
PAYMENT_GATEWAYS {
bigint id PK
varchar code UK
varchar adapter_class
varchar status
json capabilities
}
PAYMENT_GATEWAY_CONFIGS {
bigint id PK
bigint gateway_id FK
bigint node_id FK
varchar environment
json encrypted_configuration
boolean is_active
}
PAYMENTS {
bigint id PK
uuid public_id UK
bigint order_id FK
bigint gateway_id FK
varchar external_transaction_id
varchar payment_method
decimal amount
decimal fee
char currency
varchar status
varchar payment_url
timestamp expires_at
timestamp paid_at
}
PAYMENT_EVENTS {
bigint id PK
bigint payment_id FK
bigint gateway_id FK
varchar provider_event_id
varchar event_type
varchar normalized_status
char payload_hash
json sanitized_payload
timestamp processed_at
}
REFUNDS {
bigint id PK
uuid public_id UK
bigint payment_id FK
varchar external_refund_id
decimal amount
char currency
varchar status
varchar reason
}
IDEMPOTENCY_KEYS {
bigint id PK
bigint node_id FK
varchar scope
varchar key_hash
char request_hash
varchar resource_type
uuid resource_public_id
timestamp expires_at
}
Constraint penting:
- Order item menyimpan snapshot nama dan harga; histori order tidak bergantung pada harga produk yang dapat berubah.
- Seluruh item dalam satu order menggunakan currency order kecuali multi-currency settlement dirancang kemudian.
- (gateway_id, external_transaction_id) unik ketika provider memberikan ID.
- (gateway_id, provider_event_id) unik agar pemrosesan webhook idempotent.
- (node_id, scope, key_hash) unik untuk operasi API idempotent.
- Perubahan status payment dan order dijalankan dalam satu transaksi database jika relevan.
- Total refund tidak boleh melebihi captured payment amount.
8. ERD target — federasi dan moderasi
erDiagram
NODES ||--o{ REMOTE_NODES : "menemukan"
REMOTE_NODES ||--o{ REMOTE_ACTORS : "menjadi host"
REMOTE_ACTORS ||--o{ FEDERATED_OBJECTS : "menulis"
REMOTE_NODES ||--o{ FEDERATION_ACTIVITIES : "bertukar"
USERS ||--o{ FOLLOWS : "memulai"
REMOTE_ACTORS ||--o{ FOLLOWS : "diikuti"
NODES ||--o{ MODERATION_RULES : "menerapkan"
REMOTE_NODES o|--o{ MODERATION_RULES : "menjadi target"
REMOTE_ACTORS o|--o{ MODERATION_RULES : "menjadi target"
USERS ||--o{ REPORTS : "mengirim"
REMOTE_ACTORS o|--o{ REPORTS : "dilaporkan"
FEDERATED_OBJECTS o|--o{ REPORTS : "dilaporkan"
REMOTE_NODES {
bigint id PK
varchar domain UK
varchar protocol_version
varchar inbox_url
text public_key
json capabilities
varchar trust_status
timestamp last_seen_at
}
REMOTE_ACTORS {
bigint id PK
uuid public_id UK
bigint remote_node_id FK
varchar actor_uri UK
varchar handle
varchar display_name
varchar profile_url
varchar status
}
FEDERATED_OBJECTS {
bigint id PK
uuid public_id UK
bigint remote_actor_id FK
varchar object_uri UK
varchar object_type
json normalized_content
varchar canonical_url
timestamp published_at
timestamp deleted_at
}
FEDERATION_ACTIVITIES {
bigint id PK
uuid public_id UK
bigint remote_node_id FK
varchar direction
varchar activity_uri
varchar activity_type
char payload_hash
varchar status
int attempt_count
timestamp processed_at
}
FOLLOWS {
bigint id PK
bigint local_user_id FK
bigint remote_actor_id FK
varchar direction
varchar status
timestamp created_at
}
MODERATION_RULES {
bigint id PK
bigint node_id FK
bigint remote_node_id FK
bigint remote_actor_id FK
varchar action
varchar reason
timestamp expires_at
}
REPORTS {
bigint id PK
uuid public_id UK
bigint reporter_user_id FK
bigint remote_actor_id FK
bigint federated_object_id FK
varchar reason_code
text details
varchar status
}
Constraint penting:
- Remote URI unik secara global dan tidak dipercaya hanya karena formatnya valid.
- Incoming activity dideduplikasi melalui stable activity URI atau strategi sender + payload hash yang terdokumentasi.
- Tepat satu moderation target wajib tersedia jika rule menargetkan remote node atau actor.
- Tombstone menyimpan identitas minimum agar object remote yang sudah dihapus tidak diimpor kembali.
- Retention raw signed activity dan personal data membutuhkan policy eksplisit.
9. Kebijakan relasi dan penghapusan
| Relasi | Perilaku penghapusan yang disarankan |
|---|---|
| Node → user/profil/post/produk/order | Batasi penghapusan node; gunakan workflow export dan retirement terkontrol |
| User → profil | Cascade hanya dalam workflow hard-delete yang terverifikasi |
| User → post | Pertahankan atau anonimisasi sesuai policy ownership/export |
| Sumber eksternal → external post | Default soft disconnect; purge hanya melalui aksi eksplisit |
| Order → order item/payment | Batasi hard delete; pertahankan sesuai policy keuangan/audit |
| Payment → event/refund | Batasi hard delete |
| Remote node → actor/object | Gunakan perubahan trust state atau tombstone daripada delete |
| Token/session | Hard delete atau revoke setelah retention window |
| Integration job → attempt | Cascade setelah masa retention operasional berakhir |
10. Checklist index dan constraint
Index minimum harus mendukung:
- login melalui email ternormalisasi;
- profil publik melalui (node_id, handle);
- post publik melalui (node_id, slug) dan timeline melalui (visibility, published_at, id);
- feed yang jatuh tempo melalui (sync_enabled, status, next_sync_at);
- deduplikasi external post melalui (source_id, provider_post_id);
- job tersedia melalui (status, available_at) dan stale lock melalui locked_at;
- produk melalui (node_id, status);
- order melalui (node_id, status, created_at);
- payment melalui order_id, status, dan external transaction ID yang unik;
- payment event melalui provider event ID yang unik;
- remote actor dan object melalui URI unik;
- delivery federation activity melalui (direction, status, available_at) ketika scheduling ditambahkan.
11. Urutan migration yang disarankan
1. Migration framework dan baseline tabel saat ini.
2. nodes, users, profiles, auth_tokens, dan audit_events.
3. posts dan post_media.
4. Refactor tabel eksternal agar mereferensikan user, connector, dan feed source secara eksplisit.
5. Ganti integration_queue dengan job serta attempt history yang kuat, atau lakukan migration kompatibel.
6. Tambahkan product, order, dan order item immutable.
7. Refactor payment, lalu tambahkan event, refund, dan idempotency key.
8. Tambahkan entity federasi dan moderasi hanya setelah pemilihan protokol.
9. Backfill data, validasi constraint, kemudian aktifkan foreign-key enforcement.
Setiap migration wajib memiliki forward test, rollback strategy, data-backfill plan jika diperlukan, dan pembaruan ERD ini.
12. Addendum — Koneksi Federasi pada Profil Publik
Model target ini mendukung discovery koneksi publik dan preview post terbaru dari cache. Bagian ini adalah desain target, bukan klaim bahwa persistence federasi sudah diimplementasikan.
erDiagram
PROFILES ||--o{ FEDERATED_CONNECTIONS : memiliki
REMOTE_NODES ||--o{ REMOTE_ACTORS : menaungi
REMOTE_ACTORS ||--o{ FEDERATED_CONNECTIONS : direferensikan
REMOTE_ACTORS ||--o{ FEDERATED_POSTS : menerbitkan
FEDERATED_CONNECTIONS }o--o| FEDERATED_POSTS : preview_terbaru
REMOTE_NODES {
bigint id PK
uuid public_id UK
varchar domain UK
varchar status
varchar trust_state
timestamp last_seen_at
}
REMOTE_ACTORS {
bigint id PK
bigint remote_node_id FK
varchar actor_uri UK
varchar federated_address UK
varchar display_name
varchar avatar_url
varchar canonical_url
timestamp fetched_at
}
FEDERATED_CONNECTIONS {
bigint id PK
bigint profile_id FK
bigint remote_actor_id FK
varchar relationship_status
boolean show_on_profile
timestamp accepted_at
timestamp updated_at
}
FEDERATED_POSTS {
bigint id PK
bigint remote_actor_id FK
varchar object_uri UK
varchar canonical_url
text content
varchar visibility
timestamp published_at
timestamp fetched_at
timestamp deleted_at
}
Constraint dan aturan query:
- Unique (profile_id, remote_actor_id) mencegah edge duplikat; edge bersifat directional dan siklus tetap sah.
- relationship_status: PENDING, FOLLOWING, CONNECTED, MUTED, BLOCKED, atau DISCONNECTED.
- Query publik mensyaratkan show_on_profile=true, status hubungan yang diizinkan, trust state node yang diizinkan, dan post publik yang belum dihapus.
- Preview terbaru dipilih dari cache lokal; render profil publik tidak melakukan remote fetch yang blocking.
- Index (profile_id, show_on_profile, relationship_status, id) mendukung cursor pagination; index (remote_actor_id, published_at, id) mendukung lookup post terbaru.