Commit 120a50c9 authored by M. Hafidz 's avatar M. Hafidz

.

parent 7488e8f9
...@@ -7,71 +7,7 @@ ...@@ -7,71 +7,7 @@
Diagram ini menunjukkan arsitektur keseluruhan sistem Ortax Omnichannel dari perspektif high-level, mencakup semua layer dari pengguna hingga database. Diagram ini menunjukkan arsitektur keseluruhan sistem Ortax Omnichannel dari perspektif high-level, mencakup semua layer dari pengguna hingga database.
```mermaid ![](./images/1.1.png)
flowchart TB
subgraph USERS["PENGGUNA"]
AGENT["Agent / Staff<br/>Role: member"]
ADMIN["Admin / Owner<br/>Role: admin, owner"]
CUSTOMER["Customer<br/>External User"]
end
subgraph CHANNELS["EXTERNAL CHANNELS<br/>(Platform API)"]
WA["WhatsApp Business API<br/>(Meta Graph API)"]
IG["Instagram Graph API<br/>(Meta Graph API)"]
TG["Telegram Bot API<br/>(Telegram)"]
FB["Facebook Messenger<br/>(Meta Graph API)"]
TT["TikTok<br/>(Planned)"]
end
subgraph FE["FRONTEND APPLICATION<br/>React 19 + Vite + MUI v7"]
direction TB
FE_AUTH["Auth Module<br/>sign-in, sign-up, forgot/reset password"]
FE_CHAT["Chat Module<br/>82 files - conversations, messages, real-time"]
FE_CAMP["Campaign Module<br/>33 files - templates, recipients, execution"]
FE_TICKET["Ticket Module<br/>25 files - CRUD, lifecycle, attachments"]
FE_SETTINGS["Settings Module<br/>20+ pages - admin panels"]
end
subgraph BE["BACKEND APPLICATION<br/>Hono + Drizzle ORM + Bun"]
direction TB
BE_API["API Gateway<br/>/api/v1"]
BE_AUTH_ENGINE["Better-Auth Engine<br/>session, RBAC, API keys"]
BE_SERVICES["47 Service Modules<br/>business logic layer"]
BE_WEBHOOK["Webhook Receivers<br/>WhatsApp, Instagram, Telegram"]
BE_QUEUE["RabbitMQ Worker<br/>distributed processing"]
end
subgraph REALTIME["REAL-TIME LAYER"]
PUSHER["Pusher<br/>private-room, private-channel, private-post"]
SOCKET["Socket.IO<br/>new-message, new-conversation"]
end
subgraph DB_LAYER["DATABASE & STORAGE"]
PG["PostgreSQL<br/>schema: omnichannel<br/>30+ tables"]
UPLOAD["File Storage<br/>Local / S3 / GCS / Azure"]
end
USERS -->|"Browser<br/>HTTPS"| FE
CUSTOMER -->|"Send Message"| CHANNELS
CHANNELS -->|"Webhook POST"| BE_WEBHOOK
FE -->|"REST API<br/>/api/v1/*"| BE_API
BE_API --> BE_AUTH_ENGINE
BE_API --> BE_SERVICES
BE_SERVICES --> PG
BE_SERVICES --> UPLOAD
BE_WEBHOOK --> BE_QUEUE
BE_QUEUE --> BE_SERVICES
BE_SERVICES -->|"Trigger Events"| REALTIME
REALTIME -->|"Push to Client"| FE
CHANNELS -->|"API Response"| BE_SERVICES
style USERS fill:#E8F5E9,stroke:#4CAF50,color:#1B5E20
style CHANNELS fill:#FFF3E0,stroke:#FF9800,color:#E65100
style FE fill:#E3F2FD,stroke:#2196F3,color:#0D47A1
style BE fill:#FCE4EC,stroke:#E91E63,color:#880E4F
style REALTIME fill:#F3E5F5,stroke:#9C27B0,color:#4A148C
style DB_LAYER fill:#E0F7FA,stroke:#00BCD4,color:#006064
```
--- ---
...@@ -79,50 +15,7 @@ flowchart TB ...@@ -79,50 +15,7 @@ flowchart TB
Diagram ini menunjukkan alur perjalanan pengguna dari pertama kali membuka aplikasi hingga menggunakan fitur-fitur utama. Diagram ini menunjukkan alur perjalanan pengguna dari pertama kali membuka aplikasi hingga menggunakan fitur-fitur utama.
```mermaid ![](./images/1.2.png)
flowchart TD
START([User Opens App]) --> HAS_SESSION{Has Active<br/>Session Cookie?}
HAS_SESSION -->|"Yes"| AUTH_CHECK{Session Valid?<br/>better-auth verify}
HAS_SESSION -->|"No"| LOGIN["Login Page<br/>/auth/sign-in"]
AUTH_CHECK -->|"Valid"| HAS_WORKSPACE{Has Active<br/>Workspace?}
AUTH_CHECK -->|"Invalid / Expired"| LOGIN
LOGIN --> SIGNIN_PAGE["Sign-In Page<br/>(BetterAuth.SignInPage)"]
SIGNIN_PAGE --> INPUT_CREDS["Input Email & Password"]
INPUT_CREDS --> SUBMIT["POST /api/v1/auth/sign-in<br/>better-auth client"]
SUBMIT --> VALID{Credentials Valid?}
VALID -->|"No"| SHOW_ERR["Show Error Message<br/>'Invalid email or password'"]
SHOW_ERR --> SIGNIN_PAGE
VALID -->|"Yes"| SET_COOKIE["Session Cookie Set<br/>User Context Loaded"]
SET_COOKIE --> HAS_WORKSPACE
HAS_WORKSPACE -->|"No"| CREATE_WS["Create Workspace<br/>/dashboard/workspace/create"]
HAS_WORKSPACE -->|"Yes"| DASHBOARD["Dashboard<br/>Redirect to /dashboard/chat"]
CREATE_WS --> WS_FORM["Workspace Creation Form<br/>Input: workspace name"]
WS_FORM --> WS_API["authClient.organization.create()<br/>+ setActive(organization)"]
WS_API --> DASHBOARD
DASHBOARD --> SIDEBAR{{"Sidebar Navigation<br/>MainLayout"}}
SIDEBAR -->|"Chat"| CHAT["/dashboard/chat<br/>Real-time Conversations"]
SIDEBAR -->|"Social Media"| SOCIAL["/dashboard/social-media<br/>Instagram Posts"]
SIDEBAR -->|"Campaign"| CAMP["/dashboard/campaign<br/>Marketing Campaigns"]
SIDEBAR -->|"Contact"| CONTACT["/dashboard/contact<br/>Contact Management"]
SIDEBAR -->|"Tickets"| TICKET["/dashboard/ticket<br/>Issue Tracking"]
SIDEBAR -->|"Settings"| SETTINGS["/dashboard/settings<br/>Configuration Panel"]
CHAT --> CHAT_FLOW["Chat Flow<br/>(see FE Chat Detail)"]
CAMP --> CAMP_FLOW["Campaign Flow<br/>(see FE Campaign Detail)"]
TICKET --> TICKET_FLOW["Ticket Flow<br/>(see FE Ticket Detail)"]
SETTINGS --> SETTINGS_FLOW["Settings Flow<br/>(see FE Settings Detail)"]
style START fill:#C8E6C9,stroke:#4CAF50
style DASHBOARD fill:#BBDEFB,stroke:#2196F3
style SHOW_ERR fill:#FFCDD2,stroke:#F44336
```
--- ---
...@@ -130,151 +23,16 @@ flowchart TD ...@@ -130,151 +23,16 @@ flowchart TD
Struktur navigasi lengkap yang tersedia di sidebar dashboard. Struktur navigasi lengkap yang tersedia di sidebar dashboard.
```mermaid ![](./images/1.3.png)
flowchart LR
subgraph NAV["Dashboard Navigation (Sidebar)"]
direction TB
NAV_MGMT["Management"]
NAV_CHAT["Chat<br/>/dashboard/chat"]
NAV_SOCIAL["Sosial Media<br/>/dashboard/social-media"]
NAV_CAMP["Campaign<br/>/dashboard/campaign"]
NAV_CONTACT["Contact<br/>/dashboard/contact"]
NAV_TICKET["Tickets<br/>/dashboard/ticket"]
NAV_SETTINGS["Settings ▸<br/>/dashboard/settings"]
end
NAV_MGMT --> NAV_CHAT
NAV_MGMT --> NAV_SOCIAL
NAV_MGMT --> NAV_CAMP
NAV_MGMT --> NAV_CONTACT
NAV_MGMT --> NAV_TICKET
NAV_MGMT --> NAV_SETTINGS
NAV_SETTINGS --> SET_TAG["Tags<br/>[admin, owner]"]
NAV_SETTINGS --> SET_USER["Users<br/>[admin, owner]"]
NAV_SETTINGS --> SET_CH["Channels<br/>[admin, owner]"]
NAV_SETTINGS --> SET_WS["Workspaces<br/>[owner only]"]
NAV_SETTINGS --> SET_ACC["My Account<br/>[all roles]"]
NAV_SETTINGS --> SET_OH["Office Hour & Auto Response<br/>[admin, owner]"]
NAV_SETTINGS --> SET_QR["Quick Reply<br/>[admin, owner]"]
NAV_SETTINGS --> SET_API["API Keys<br/>[admin, owner]"]
NAV_SETTINGS --> SET_RC["Category Resolve<br/>[admin, owner]"]
style NAV fill:#F5F5F5,stroke:#9E9E9E
style NAV_SETTINGS fill:#E8EAF6,stroke:#3F51B5
```
--- ---
## 1.4 High-Level Data Flow (Aliran Data Keseluruhan) ## 1.4 High-Level Data Flow (Aliran Data Keseluruhan)
```mermaid ![](./images/1.4.png)
flowchart LR
subgraph INPUT["Input Sources"]
USER_ACTION["User Actions<br/>(Click, Type, Submit)"]
WEBHOOK_IN["Channel Webhooks<br/>(WA, IG, TG)"]
end
subgraph PROCESSING["Processing Layer"]
FE_REACT["React App<br/>State Management (SWR)"]
BE_HONO["Hono Server<br/>Route → Service → Repository"]
MQ["RabbitMQ<br/>Work Queue"]
end
subgraph STORAGE["Storage Layer"]
PG["PostgreSQL<br/>30+ Tables"]
FILES["File Storage<br/>Uploads"]
CACHE["SWR Cache<br/>Client-side"]
end
subgraph OUTPUT["Output Channels"]
UI["UI Rendering<br/>React Components"]
PUSH_OUT["Pusher + Socket.IO<br/>Real-time Events"]
PLATFORM_OUT["Platform APIs<br/>Send Messages"]
EMAIL_OUT["Email<br/>Nodemailer"]
end
USER_ACTION --> FE_REACT
WEBHOOK_IN --> BE_HONO
WEBHOOK_IN --> MQ
MQ --> BE_HONO
FE_REACT -->|"HTTP Request"| BE_HONO
BE_HONO --> PG
BE_HONO --> FILES
FE_REACT --> CACHE
BE_HONO --> PUSH_OUT
BE_HONO --> PLATFORM_OUT
BE_HONO --> EMAIL_OUT
PUSH_OUT --> FE_REACT
FE_REACT --> UI
style INPUT fill:#E8F5E9,stroke:#4CAF50
style PROCESSING fill:#E3F2FD,stroke:#2196F3
style STORAGE fill:#FFF3E0,stroke:#FF9800
style OUTPUT fill:#F3E5F5,stroke:#9C27B0
```
--- ---
## 1.5 Multi-Channel Message Flow ## 1.5 Multi-Channel Message Flow
```mermaid ![](./images/1.5.png)
flowchart TD
subgraph INCOMING["Pesan Masuk"]
direction LR
C1["WhatsApp Customer"]
C2["Instagram User"]
C3["Telegram User"]
end
subgraph WEBHOOKS["Webhook Processing"]
WH_WA["/api/webhook/whatsapp"]
WH_IG["/api/webhook/instagram"]
WH_TG["/api/webhook/telegram"]
end
subgraph QUEUE["Message Queue"]
RABBIT["RabbitMQ<br/>omnichannel_work_queue"]
end
subgraph BACKEND["Backend Processing"]
PARSE["Parse & Normalize Message"]
FIND_CONTACT["Find or Create Contact"]
FIND_CONV["Find or Create Conversation"]
SAVE_MSG["Save to conversation_messages"]
CHECK_OH{"Within<br/>Office Hours?"}
AUTO_RESP["Send Auto Response<br/>(if configured)"]
NOTIFY["Pusher + Socket.IO<br/>Notify Agents"]
end
subgraph AGENT_UI["Agent Interface"]
FE_RECEIVE["Real-time Update<br/>via Pusher/Socket.IO"]
SWR_UPDATE["SWR Revalidation<br/>Update Conversation List"]
SHOW_MSG["Display New Message<br/>in Chat UI"]
end
C1 -->|"Message"| WH_WA
C2 -->|"DM/Comment"| WH_IG
C3 -->|"Message"| WH_TG
WH_WA -->|"Publish"| RABBIT
WH_IG -->|"Direct"| PARSE
WH_TG -->|"Direct"| PARSE
RABBIT -->|"Consume"| PARSE
PARSE --> FIND_CONTACT --> FIND_CONV --> SAVE_MSG
SAVE_MSG --> CHECK_OH
CHECK_OH -->|"No"| AUTO_RESP
CHECK_OH -->|"Yes"| NOTIFY
AUTO_RESP --> NOTIFY
NOTIFY --> FE_RECEIVE --> SWR_UPDATE --> SHOW_MSG
style INCOMING fill:#E8F5E9,stroke:#4CAF50
style WEBHOOKS fill:#FFF3E0,stroke:#FF9800
style QUEUE fill:#FCE4EC,stroke:#E91E63
style BACKEND fill:#E3F2FD,stroke:#2196F3
style AGENT_UI fill:#F3E5F5,stroke:#9C27B0
```
...@@ -7,51 +7,7 @@ ...@@ -7,51 +7,7 @@
Diagram ini menunjukkan mapping lengkap antara Frontend modules, API endpoints, dan Backend services untuk setiap fitur utama. Diagram ini menunjukkan mapping lengkap antara Frontend modules, API endpoints, dan Backend services untuk setiap fitur utama.
```mermaid ![](./images/2.1.png)
flowchart LR
subgraph FE["FRONTEND (React 19)"]
direction TB
FE1["🔐 Auth Pages<br/>sign-in, sign-up,<br/>forgot/reset password"]
FE2["💬 Chat Pages<br/>82 files<br/>conversations, messages"]
FE3["📢 Campaign Pages<br/>33 files<br/>templates, recipients"]
FE4["🎫 Ticket Pages<br/>25 files<br/>CRUD, lifecycle"]
FE5["⚙️ Settings Pages<br/>20+ pages<br/>admin configuration"]
end
subgraph API["API LAYER (Hono /api/v1)"]
direction TB
API1["/auth/*<br/>sign-in, sign-up,<br/>forget-password,<br/>change-password,<br/>admin/*, organization/*"]
API2["/u/conversations<br/>/u/conversation-messages<br/>/u/channels<br/>/u/contacts<br/>/u/notes"]
API3["/u/campaigns<br/>/u/campaign-recipient-lists<br/>/u/campaign-recipient-list-items<br/>/u/templates"]
API4["/u/tickets<br/>/u/ticket-history"]
API5["/u/workspaces<br/>/u/workspace-members<br/>/u/workspace-api-keys<br/>/u/channels<br/>/u/tags<br/>/u/user<br/>/u/role-permissions<br/>/u/message-templates<br/>/u/office-hour-*<br/>/u/conversation-resolve-category"]
end
subgraph BE["BACKEND SERVICES"]
direction TB
BS1["better-auth<br/>session, RBAC,<br/>API keys"]
BS2["conversation<br/>conversation-message<br/>channel<br/>contact<br/>note<br/>tag"]
BS3["campaign<br/>campaign-recipient-list<br/>template<br/>whatsapp-template"]
BS4["ticket<br/>ticket-history<br/>ticket-attachment"]
BS5["workspace<br/>workspace-members<br/>workspace-api-key<br/>role-permission<br/>channel<br/>tag<br/>message-template<br/>office-hour-schedule<br/>office-hour-response<br/>conv-resolve-category"]
end
FE1 -->|"POST /auth/sign-in<br/>POST /auth/forget-password<br/>POST /auth/change-password<br/>POST /auth/admin/*<br/>POST /auth/organization/*"| API1
FE2 -->|"GET /conversations<br/>POST /conversation-messages<br/>PATCH /conversations/:id/status<br/>POST /conversations/:id/read<br/>GET /conversation-messages<br/>POST /conversation-messages/template<br/>POST /conversation-messages/upload-media"| API2
FE3 -->|"CRUD /campaigns<br/>POST /campaigns/:id/execute<br/>CRUD /templates<br/>POST /templates/:id/sync<br/>CRUD /campaign-recipient-lists<br/>POST /templates/upload-media"| API3
FE4 -->|"CRUD /tickets<br/>GET /ticket-history/ticket/:id<br/>POST /tickets/upload-media<br/>DELETE /tickets/remove-media/:id"| API4
FE5 -->|"CRUD /workspaces<br/>CRUD /workspace-members<br/>CRUD /channels<br/>CRUD /tags<br/>PUT /user/update-user<br/>POST /auth/change-password<br/>CRUD /role-permissions<br/>CRUD /message-templates<br/>CRUD /office-hour-*<br/>CRUD /conversation-resolve-category<br/>CRUD /workspace-api-keys"| API5
API1 --> BS1
API2 --> BS2
API3 --> BS3
API4 --> BS4
API5 --> BS5
style FE fill:#E3F2FD,stroke:#1976D2
style API fill:#FFF8E1,stroke:#F9A825
style BE fill:#FCE4EC,stroke:#C2185B
```
--- ---
...@@ -59,62 +15,7 @@ flowchart LR ...@@ -59,62 +15,7 @@ flowchart LR
Alur komunikasi real-time dari webhook masuk hingga update UI di browser agent. Alur komunikasi real-time dari webhook masuk hingga update UI di browser agent.
```mermaid ![](./images/2.2.png)
flowchart TD
subgraph EXTERNAL["EXTERNAL CHANNELS"]
WA_MSG["📱 WhatsApp<br/>Message from Customer"]
IG_MSG["📸 Instagram<br/>DM / Comment"]
TG_MSG["✈️ Telegram<br/>Message from User"]
end
subgraph BE_RECEIVE["BACKEND - Webhook Receivers"]
WH_WA["POST /api/webhook/whatsapp"]
WH_IG["POST /api/webhook/instagram"]
WH_TG["POST /api/webhook/telegram"]
end
subgraph BE_PROCESS["BACKEND - Processing"]
RABBIT["RabbitMQ Queue<br/>omnichannel_work_queue"]
PROCESSOR["Message Processor Service"]
DB_WRITE["PostgreSQL<br/>INSERT conversations<br/>INSERT conversation_messages"]
PUSHER_S["Pusher Server<br/>Trigger Events"]
SOCKET_S["Socket.IO Server<br/>Emit Events"]
end
subgraph FE_RECEIVE["FRONTEND - Real-time Clients"]
PUSHER_C["Pusher Client<br/>use-pusher-chat.ts<br/>Subscribe: private-room:*<br/>private-channel:*"]
SOCKET_C["Socket.IO Client<br/>use-socket-io.ts<br/>Listen: new-message<br/>new-conversation"]
SWR_CACHE["SWR Cache<br/>Automatic Revalidation<br/>mutate() on events"]
UI["UI Components<br/>ChatNav, ChatMessageList<br/>Auto Update"]
end
WA_MSG -->|"HTTP POST"| WH_WA
IG_MSG -->|"HTTP POST"| WH_IG
TG_MSG -->|"HTTP POST"| WH_TG
WH_WA -->|"Publish to Queue"| RABBIT
WH_IG -->|"Direct Process"| PROCESSOR
WH_TG -->|"Direct Process"| PROCESSOR
RABBIT -->|"Consume"| PROCESSOR
PROCESSOR --> DB_WRITE
PROCESSOR --> PUSHER_S
PROCESSOR --> SOCKET_S
PUSHER_S -->|"private-room:{convId}<br/>Event: new-message"| PUSHER_C
PUSHER_S -->|"private-channel:{chId}<br/>Event: new-conversation"| PUSHER_C
SOCKET_S -->|"Event: new-message"| SOCKET_C
SOCKET_S -->|"Event: conversation-updated"| SOCKET_C
PUSHER_C --> SWR_CACHE
SOCKET_C --> SWR_CACHE
SWR_CACHE --> UI
style EXTERNAL fill:#E8F5E9,stroke:#4CAF50
style BE_RECEIVE fill:#FFF3E0,stroke:#FF9800
style BE_PROCESS fill:#FCE4EC,stroke:#E91E63
style FE_RECEIVE fill:#E3F2FD,stroke:#2196F3
```
--- ---
...@@ -122,34 +23,7 @@ flowchart TD ...@@ -122,34 +23,7 @@ flowchart TD
Tabel event real-time yang digunakan dalam sistem. Tabel event real-time yang digunakan dalam sistem.
```mermaid ![](./images/2.3.png)
flowchart TD
subgraph EVENTS["Real-Time Events"]
direction TB
E1["new-message<br/>Pesan baru masuk/terkirim"]
E2["new-conversation<br/>Conversation baru dibuat"]
E3["message-updated<br/>Status pesan berubah<br/>(SENT → DELIVERED → READ)"]
E4["conversation-updated<br/>Status/assignee conversation berubah"]
E5["new-post-comment<br/>Komentar baru di Instagram post"]
E6["post-updated<br/>Instagram post diupdate"]
end
subgraph CHANNELS["Pusher Channels"]
C1["private-room:{conversationId}<br/>Event: new-message, message-updated"]
C2["private-channel:{channelId}<br/>Event: new-conversation, conversation-updated"]
C3["private-post:{postId}<br/>Event: new-post-comment, post-updated"]
end
E1 --> C1
E2 --> C2
E3 --> C1
E4 --> C2
E5 --> C3
E6 --> C3
style EVENTS fill:#F3E5F5,stroke:#9C27B0
style CHANNELS fill:#E8EAF6,stroke:#3F51B5
```
--- ---
...@@ -157,79 +31,13 @@ flowchart TD ...@@ -157,79 +31,13 @@ flowchart TD
Alur integrasi autentikasi antara FE dan BE. Alur integrasi autentikasi antara FE dan BE.
```mermaid ![](./images/2.4.png)
flowchart TD
subgraph FE_AUTH["FRONTEND - Auth Context"]
BA_CLIENT["better-auth client<br/>/src/lib/better-auth.ts<br/>Base URL: /api/v1/auth"]
AUTH_PROVIDER["AuthProvider<br/>/src/auth/context/better-auth/"]
AUTH_GUARD["AuthGuard<br/>GuestGuard<br/>WorkspaceGuard<br/>RouteRoleGuard"]
end
subgraph API_AUTH["API ENDPOINTS"]
SIGN_IN["POST /api/v1/auth/sign-in/email-password"]
SIGN_UP["POST /api/v1/auth/sign-up/email-password"]
FORGOT["POST /api/v1/auth/forget-password"]
RESET["POST /api/v1/auth/reset-password"]
CHANGE["POST /api/v1/auth/change-password"]
SESSION["GET /api/v1/auth/get-session"]
ADMIN_API["POST /api/v1/auth/admin/*"]
ORG_API["POST /api/v1/auth/organization/*"]
end
subgraph BE_AUTH["BACKEND - better-auth Engine"]
BA_HANDLER["better-auth handler<br/>/src/services/better-auth/"]
SESSION_MW["requireAuth Middleware<br/>Session verification"]
PERM_MW["requirePermission Middleware<br/>RBAC check"]
APIKEY_MW["requireWorkspaceApiKey Middleware<br/>x-api-key verification"]
end
BA_CLIENT -->|"HTTP Request"| API_AUTH
AUTH_PROVIDER --> BA_CLIENT
AUTH_GUARD --> AUTH_PROVIDER
API_AUTH --> BA_HANDLER
BA_HANDLER -->|"Set Session Cookie"| BA_CLIENT
SESSION_MW -->|"Verify Session"| BA_HANDLER
PERM_MW -->|"Check Role + Permission"| BA_HANDLER
APIKEY_MW -->|"Verify API Key"| BA_HANDLER
style FE_AUTH fill:#E3F2FD,stroke:#1976D2
style API_AUTH fill:#FFF8E1,stroke:#F9A825
style BE_AUTH fill:#FCE4EC,stroke:#C2185B
```
--- ---
## 2.5 File Upload Integration Flow ## 2.5 File Upload Integration Flow
```mermaid ![](./images/2.5.png)
flowchart TD
FE_UPLOAD["Frontend<br/>File Upload Component<br/>(multipart/form-data)"] --> ROUTE_CHECK{Upload Type?}
ROUTE_CHECK -->|"Chat Media"| CHAT_API["POST /api/v1/u/conversation-messages/upload-media<br/>body: {file, mediaType, conversationId}"]
ROUTE_CHECK -->|"Ticket Media"| TICKET_API["POST /api/v1/u/tickets/upload-media<br/>body: {file, ticketId?}"]
ROUTE_CHECK -->|"Template Media"| TEMPL_API["POST /api/v1/u/templates/upload-media<br/>body: {file, mediaType, channelId}<br/>(Meta Resumable API)"]
ROUTE_CHECK -->|"Channel Icon"| ICON_API["POST /api/v1/u/channels/:id/upload-icon<br/>body: {file}"]
ROUTE_CHECK -->|"Profile Photo"| PROF_API["PUT /api/v1/u/user/update-user<br/>body: {photo}"]
CHAT_API --> FILE_SERVICE["File Storage Service"]
TICKET_API --> FILE_SERVICE
TEMPL_API --> META_API["Meta Graph API<br/>Resumable Upload"]
ICON_API --> FILE_SERVICE
PROF_API --> FILE_SERVICE
FILE_SERVICE --> STORAGE_CHECK{Storage Type?}
STORAGE_CHECK -->|"LOCAL"| LOCAL["Local Filesystem<br/>/uploads/*"]
STORAGE_CHECK -->|"S3"| S3["AWS S3"]
STORAGE_CHECK -->|"GCS"| GCS["Google Cloud Storage"]
STORAGE_CHECK -->|"AZURE"| AZURE["Azure Blob Storage"]
META_API --> META_STORE["Meta Servers"]
style FE_UPLOAD fill:#E3F2FD,stroke:#1976D2
style STORAGE_CHECK fill:#FFF8E1,stroke:#F9A825
```
--- ---
...@@ -237,31 +45,4 @@ flowchart TD ...@@ -237,31 +45,4 @@ flowchart TD
Diagram ini menunjukkan urutan middleware yang dilalui setiap request dari FE ke BE. Diagram ini menunjukkan urutan middleware yang dilalui setiap request dari FE ke BE.
```mermaid ![](./images/2.6.png)
flowchart TD
REQUEST["Incoming HTTP Request"] --> CORS["① CORS Middleware<br/>cors.config.ts<br/>Allowed origins validation"]
CORS --> TIMING["② Timing Middleware<br/>Response time header"]
TIMING --> REQ_ID["③ Request ID<br/>Unique request identifier"]
REQ_ID --> LOGGER["④ Logger Middleware<br/>Request logging"]
LOGGER --> BODY_LIMIT["⑤ Body Limit Middleware<br/>5MB default<br/>100MB WhatsApp uploads"]
BODY_LIMIT --> TIMEOUT["⑥ Timeout Middleware<br/>5min default<br/>15min uploads"]
TIMEOUT --> ROUTE_MATCH{Route Match?}
ROUTE_MATCH -->|"/api/v1/auth/*"| AUTH_ROUTE["better-auth handler<br/>(self-handled)"]
ROUTE_MATCH -->|"/api/webhook/*"| WEBHOOK_ROUTE["Webhook Routes<br/>(WhatsApp CORS only)"]
ROUTE_MATCH -->|"/api/v1/u/*"| PROTECTED["Protected Route Chain"]
ROUTE_MATCH -->|"/uploads/*"| UPLOAD_ROUTE["File Storage Route<br/>(+ upload timeout)"]
ROUTE_MATCH -->|"/api/v1/swagger"| SWAGGER["Swagger UI"]
PROTECTED --> AUTH_MW["⑦ requireAuth<br/>Session verification"]
AUTH_MW --> PERM_MW["⑧ requirePermission<br/>RBAC: resource:action"]
PERM_MW --> HANDLER["Route Handler<br/>→ Service → Repository → DB"]
AUTH_ROUTE --> HANDLER2["better-auth internal handler"]
WEBHOOK_ROUTE --> HANDLER3["Webhook processor"]
style REQUEST fill:#C8E6C9,stroke:#4CAF50
style HANDLER fill:#BBDEFB,stroke:#2196F3
style HANDLER2 fill:#BBDEFB,stroke:#2196F3
style HANDLER3 fill:#BBDEFB,stroke:#2196F3
```
...@@ -7,57 +7,7 @@ ...@@ -7,57 +7,7 @@
Diagram lengkap struktur routing Frontend dari root hingga setiap halaman. Diagram lengkap struktur routing Frontend dari root hingga setiap halaman.
```mermaid ![](./images/3.1.png)
flowchart TD
ROOT["/ (Root Route)<br/>src/app.tsx"] --> MAIN_LAYOUT["MainLayout<br/>(Public Pages)"]
ROOT --> AUTH_LAYOUT["AuthSplitLayout<br/>(Auth Pages)"]
ROOT --> SIMPLE_LAYOUT["SimpleLayout<br/>(Minimal Pages)"]
ROOT --> DASH_LAYOUT["DashboardLayout<br/>(Protected Pages)"]
subgraph PUBLIC["Public Routes"]
MAIN_LAYOUT --> HOME["/ — HomePage"]
MAIN_LAYOUT --> ABOUT["/about-us"]
MAIN_LAYOUT --> PRIVACY["/privacy-policy"]
MAIN_LAYOUT --> TERMS["/terms-of-service"]
SIMPLE_LAYOUT --> MAINT["/maintenance"]
end
subgraph AUTH_ROUTES["Auth Routes (GuestGuard)"]
AUTH_LAYOUT --> GUEST["GuestGuard<br/>(Block if logged in)"]
GUEST --> SIGNIN["/auth/sign-in<br/>BetterAuth.SignInPage"]
GUEST --> FORGOT["/auth/forgot-password<br/>BetterAuth.ForgotPasswordPage"]
GUEST --> RESET["/auth/reset-password<br/>BetterAuth.ResetPasswordPage"]
GUEST --> SIGNUP["/auth/sign-up<br/>(Hidden - Invite Only)"]
GUEST --> ACCEPT_INV["/accept-invitation/:id<br/>(+ ReCAPTCHA)"]
end
subgraph ERROR_ROUTES["Error Routes"]
ROOT --> E403["/error/403 — Forbidden"]
ROOT --> E404["/error/404 — Not Found"]
ROOT --> E500["/error/500 — Server Error"]
end
subgraph DASH["Dashboard Routes (AuthGuard + WorkspaceGuard)"]
DASH_LAYOUT --> AUTH_G["AuthGuard<br/>Check Session"]
AUTH_G --> WS_G["WorkspaceGuard<br/>Check Active Workspace"]
WS_G --> DASH_NAV{{"Dashboard Navigation"}}
DASH_NAV --> CHAT_R["/dashboard/chat"]
DASH_NAV --> SOCIAL["/dashboard/social-media"]
DASH_NAV --> WS_CREATE["/dashboard/workspace/create"]
DASH_NAV --> TAG_NAV["/dashboard/tag<br/>/new /:id/edit"]
DASH_NAV --> CONTACT["/dashboard/contact"]
DASH_NAV --> CAMP_NAV["/dashboard/campaign/*"]
DASH_NAV --> TICK_NAV["/dashboard/ticket/*"]
DASH_NAV --> SET_NAV["/dashboard/settings/*"]
end
style ROOT fill:#C8E6C9,stroke:#4CAF50
style DASH fill:#E3F2FD,stroke:#1976D2
style AUTH_ROUTES fill:#FFF3E0,stroke:#FF9800
style PUBLIC fill:#F3E5F5,stroke:#9C27B0
```
--- ---
...@@ -65,56 +15,7 @@ flowchart TD ...@@ -65,56 +15,7 @@ flowchart TD
Alur autentikasi lengkap dari halaman login hingga masuk dashboard. Alur autentikasi lengkap dari halaman login hingga masuk dashboard.
```mermaid ![](./images/3.2.png)
flowchart TD
START([User Opens App]) --> VISIT{"Current URL?"}
VISIT -->|"/auth/*"| GUEST_CHECK{GuestGuard<br/>Is Logged In?}
VISIT -->|"/dashboard/*"| AUTH_CHECK{AuthGuard<br/>Has Session?}
VISIT -->|Other| PUBLIC_PAGE["Show Public Page"]
GUEST_CHECK -->|Yes| REDIRECT_DASH["Redirect to<br/>/dashboard/chat"]
GUEST_CHECK -->|No| AUTH_PAGE["Show Auth Page"]
AUTH_CHECK -->|No| REDIRECT_LOGIN["Redirect to<br/>/auth/sign-in"]
AUTH_CHECK -->|Yes| WS_CHECK{WorkspaceGuard<br/>Has Workspace?}
WS_CHECK -->|No| REDIRECT_WS["Redirect to<br/>/dashboard/workspace/create"]
WS_CHECK -->|Yes| ROLE_CHECK{RouteRoleGuard<br/>Has Permission?}
ROLE_CHECK -->|No| REDIRECT_403["Redirect to<br/>/error/403"]
ROLE_CHECK -->|Yes| SHOW_PAGE["Show Dashboard Page"]
subgraph SIGNIN_FLOW["Sign-In Process"]
AUTH_PAGE --> SIGNIN["BetterAuth.SignInPage"]
SIGNIN --> INPUT["Input Email + Password"]
INPUT --> CLICK["Click Sign In"]
CLICK --> API_CALL["authClient.signIn.email()<br/>POST /api/v1/auth/sign-in/email-password"]
API_CALL --> SUCCESS{Success?}
SUCCESS -->|No| ERR_MSG["Show Error<br/>'Invalid credentials'"]
SUCCESS -->|Yes| SESSION_SET["Session Cookie Set<br/>AuthProvider Context Updated"]
SESSION_SET --> REDIRECT_DASH
ERR_MSG --> INPUT
end
subgraph INVITE_FLOW["Invitation Flow"]
ACCEPT_INV["/accept-invitation/:id"] --> CAPTCHA["ReCAPTCHA Verification"]
CAPTCHA --> VERIFY{Captcha Valid?}
VERIFY -->|No| CAPTCHA
VERIFY -->|Yes| ACCEPT_API["authClient.organization<br/>.acceptInvitation(id)"]
ACCEPT_API --> SUCCESS_INV{Success?}
SUCCESS_INV -->|No| INV_ERR["Show Error"]
SUCCESS_INV -->|Yes| SET_ACTIVE["SetActive Organization"]
SET_ACTIVE --> REDIRECT_DASH
INV_ERR --> CAPTCHA
end
style START fill:#C8E6C9,stroke:#4CAF50
style REDIRECT_LOGIN fill:#FFCDD2,stroke:#F44336
style REDIRECT_403 fill:#FFCDD2,stroke:#F44336
style SHOW_PAGE fill:#BBDEFB,stroke:#2196F3
style SIGNIN fill:#FFF8E1,stroke:#F9A825
```
--- ---
...@@ -128,95 +29,7 @@ Modul Chat adalah modul terbesar (82 files). Diagram ini menunjukkan alur lengka ...@@ -128,95 +29,7 @@ Modul Chat adalah modul terbesar (82 files). Diagram ini menunjukkan alur lengka
> - **Infinite Scroll**: Hook `use-message-list-infinite-scroll.ts` dan `use-message-list-scroll-persistence.ts` masih di-import di `chat-message-list.tsx`, tapi **tidak pernah ter-trigger** karena `hasMore` selalu `false` (karena response pagination dummy). Hook `use-infinite-scroll.ts` dan `use-messages-scroll.ts` adalah **dead code** (tidak di-import di mana pun). > - **Infinite Scroll**: Hook `use-message-list-infinite-scroll.ts` dan `use-message-list-scroll-persistence.ts` masih di-import di `chat-message-list.tsx`, tapi **tidak pernah ter-trigger** karena `hasMore` selalu `false` (karena response pagination dummy). Hook `use-infinite-scroll.ts` dan `use-messages-scroll.ts` adalah **dead code** (tidak di-import di mana pun).
> - **Auto-Scroll**: Hook `use-message-list-initial-load.ts` tetap aktif untuk auto-scroll to bottom saat initial load. > - **Auto-Scroll**: Hook `use-message-list-initial-load.ts` tetap aktif untuk auto-scroll to bottom saat initial load.
```mermaid ![](./images/3.3.png)
flowchart TD
CHAT_PAGE["Chat Page<br/>/dashboard/chat"] --> INIT["Initialize ChatContext"]
INIT --> LOAD_CHANNELS["Load Channels<br/>GET /channels/with-unread-count"]
INIT --> LOAD_COUNTS["Load Chat Counts<br/>GET /conversations/total-counts-chat"]
INIT --> CONNECT_RT["Connect Real-time<br/>Pusher + Socket.IO"]
LOAD_CHANNELS --> RENDER_LAYOUT["Render ChatLayout<br/>(3-Column Layout)"]
LOAD_COUNTS --> RENDER_LAYOUT
CONNECT_RT --> RENDER_LAYOUT
subgraph LEFT_PANEL["LEFT PANEL — ChatNav"]
RENDER_LAYOUT --> CH_NAV["chat-nav.tsx"]
CH_NAV --> CH_FOOTER["chat-channel-list-footer.tsx<br/>Channel filter tabs"]
CH_FOOTER --> CH_SEARCH["chat-nav-search-results.tsx<br/>Search conversations"]
CH_SEARCH --> CH_ITEM["chat-nav-item.tsx<br/>Conversation list item"]
CH_ITEM --> CH_LIST["Conversation List (Full GET)<br/>use-conversation-pagination.ts<br/>⚠️ BE route terima page/limit<br/>tapi repository tidak pakai"]
end
subgraph FILTERS["Conversation Filters"]
FILTER_GROUP["Filter Options:"] --> F1["Channel Filter<br/>(WhatsApp, Instagram, etc.)"]
FILTER_GROUP --> F2["Status Filter<br/>All / My / Unassigned / Assigned / Resolved"]
FILTER_GROUP --> F3["Tag Filter"]
FILTER_GROUP --> F4["Date Range Filter"]
FILTER_GROUP --> F5["24h Window Filter<br/>(WhatsApp only)"]
FILTER_GROUP --> F6["First Response Filter"]
end
CH_LIST --> FILTERS
subgraph CENTER_PANEL["CENTER PANEL — ChatRoom"]
CH_LIST -->|"Select Conversation"| LOAD_MSG["Load All Messages (Full GET)<br/>GET /conversation-messages<br/>use-message-list-initial-load.ts<br/>⚠️ BE limit/offset di-comment out<br/>→ returns all messages"]
LOAD_MSG --> MSG_LIST["chat-message-list.tsx<br/>Message list<br/>(auto-scroll to bottom on load)"]
MSG_LIST --> MSG_ITEM["chat-message-item.tsx<br/>Individual message bubble"]
MSG_ITEM --> MSG_TYPES["Message Types:<br/>TEXT, IMAGE, VIDEO, AUDIO,<br/>DOCUMENT, TEMPLATE, SYSTEM,<br/>INTERNAL_NOTE"]
MSG_TYPES --> DATE_SEP["chat-date-separator.tsx<br/>Date separators between messages"]
end
subgraph INPUT_AREA["MESSAGE INPUT"]
MSG_LIST --> MSG_INPUT["chat-message-input.tsx"]
MSG_INPUT --> TEXT_INPUT["bubble-editor.tsx<br/>(TipTap Rich Editor)"]
MSG_INPUT --> FILE_BTN["File Upload<br/>use-file-upload.ts"]
MSG_INPUT --> EMOJI_BTN["Emoji Picker<br/>use-emoji-picker.ts"]
MSG_INPUT --> QUICK_BTN["Quick Message<br/>quick-message-overlay.tsx<br/>use-quick-message.ts"]
MSG_INPUT --> TEMPL_BTN["WhatsApp Template<br/>chat-template-dialog.tsx<br/>use-template-dialog.ts"]
MSG_INPUT --> REPLY_BTN["Reply to Message"]
MSG_INPUT --> SEND_BTN["Send Button"]
end
subgraph SEND_FLOW["SEND MESSAGE FLOW"]
TEXT_INPUT --> SEND_BTN
FILE_BTN --> UPLOAD["use-file-upload.ts<br/>POST /upload-media"]
UPLOAD --> SEND_BTN
QUICK_BTN --> INSERT_QM["Insert Quick Message Text"]
TEMPL_BTN --> SEND_TEMPL["POST /conversation-messages/template"]
SEND_BTN -->|"POST /conversation-messages<br/>(JSON or Multipart)"| API_SEND
INSERT_QM --> TEXT_INPUT
end
subgraph RT_UPDATE["REAL-TIME UPDATE"]
API_SEND -->|"Message Sent"| PUSHER_RECV["use-pusher-chat.ts<br/>Listen: new-message"]
PUSHER_RECV --> SWR_MUTATE["SWR mutate()<br/>Revalidate messages cache"]
SWR_MUTATE --> UI_UPDATE["Update Message List<br/>(Optimistic UI)"]
end
subgraph RIGHT_PANEL["RIGHT PANEL — Details Sidebar"]
RENDER_LAYOUT --> INFO_TAB["Information Tab<br/>chat-room-information.tsx<br/>Contact details, tags, channels"]
RENDER_LAYOUT --> NOTE_TAB["Notes Tab<br/>note-list, note-item,<br/>note-detail, note-form<br/>CRUD: POST /notes"]
RENDER_LAYOUT --> TICKET_TAB["Ticket Tab<br/>ticket-list, ticket-item,<br/>ticket-detail, ticket-form<br/>Linked tickets to conversation"]
end
subgraph HEADER_ACTIONS["HEADER ACTIONS"]
RENDER_LAYOUT --> CH_HEADER["chat-header-details.tsx"]
CH_HEADER --> ASSIGN["Assign Conversation<br/>chat-assign-dialog.tsx<br/>PATCH /conversations/:id/status"]
CH_HEADER --> RESOLVE["Resolve Conversation<br/>chat-resolve-dialog.tsx<br/>PATCH /conversations/:id/status"]
CH_HEADER --> HISTORY["View History<br/>chat-room-history.tsx<br/>GET /conversations/:id/history"]
CH_HEADER --> GET_NEW["Get New Chat<br/>POST /conversations/get-new-chat<br/>(Claim next unassigned)"]
CH_HEADER --> SEARCH_MSG["Search Messages<br/>chat-search-dialog.tsx<br/>GET /conversation-messages/search"]
CH_HEADER --> BULK_RESOLVE["Bulk Resolve<br/>bulk-resolve-modal.tsx<br/>POST /conversations/bulk-resolve"]
end
style CHAT_PAGE fill:#E3F2FD,stroke:#1976D2
style LEFT_PANEL fill:#E8F5E9,stroke:#4CAF50
style CENTER_PANEL fill:#FFF3E0,stroke:#FF9800
style RIGHT_PANEL fill:#F3E5F5,stroke:#9C27B0
style SEND_FLOW fill:#FFEBEE,stroke:#F44336
style RT_UPDATE fill:#E0F7FA,stroke:#00BCD4
```
--- ---
...@@ -224,88 +37,7 @@ flowchart TD ...@@ -224,88 +37,7 @@ flowchart TD
Alur lengkap modul Campaign dari pembuatan template hingga eksekusi. Alur lengkap modul Campaign dari pembuatan template hingga eksekusi.
```mermaid ![](./images/3.4.png)
flowchart TD
CAMP_PAGE["Campaign Page<br/>/dashboard/campaign"] --> CAMP_LAYOUT["campaign-layout.tsx<br/>(Tab Navigation)"]
CAMP_LAYOUT --> TAB_CAMPAIGNS["Tab: Campaigns"]
CAMP_LAYOUT --> TAB_TEMPLATES["Tab: Templates"]
CAMP_LAYOUT --> TAB_RECIPIENTS["Tab: Recipient Lists"]
subgraph CAMPAIGNS_TAB["CAMPAIGNS MANAGEMENT"]
TAB_CAMPAIGNS --> CAMP_LIST["campaign-view.tsx<br/>Campaign List DataTable"]
CAMP_LIST --> CAMP_TOOLBAR["campaign-table-toolbar.tsx<br/>Filters + Search"]
CAMP_LIST --> CAMP_ROW["campaign-table-row.tsx<br/>Campaign Row"]
CAMP_ROW --> CAMP_ACTIONS["Campaign Actions"]
CAMP_ACTIONS --> VIEW_DETAIL["View Detail<br/>/dashboard/campaign/:id<br/>campaign-detail-view.tsx"]
CAMP_ACTIONS --> EDIT["Edit Campaign<br/>/dashboard/campaign/:id/edit<br/>campaign-create-view.tsx"]
CAMP_ACTIONS --> EXECUTE["Execute Campaign<br/>POST /campaigns/:id/execute"]
CAMP_ACTIONS --> DELETE["Delete Campaign<br/>DELETE /campaigns/:id"]
CAMP_LIST --> CREATE_BTN["+ New Campaign"]
CREATE_BTN --> CREATE_FORM["campaign-create-view.tsx<br/>campaign-create-form.tsx"]
CREATE_FORM --> FORM_STEP1["Step 1: Campaign Name<br/>& Description"]
FORM_STEP1 --> FORM_STEP2["Step 2: Select Channel<br/>(WhatsApp channel only)"]
FORM_STEP2 --> FORM_STEP3["Step 3: Select Template<br/>(from approved templates)"]
FORM_STEP3 --> FORM_STEP4["Step 4: Select Recipient List<br/>(from recipient lists)"]
FORM_STEP4 --> SAVE_DRAFT["Save as DRAFT<br/>POST /campaigns"]
end
subgraph TEMPLATES_TAB["TEMPLATE MANAGEMENT"]
TAB_TEMPLATES --> TEMPL_LIST["campaign-templates-view.tsx<br/>Template List"]
TEMPL_LIST --> TEMPL_TOOLBAR["template-table-toolbar.tsx"]
TEMPL_LIST --> TEMPL_ROW["template-table-row.tsx"]
TEMPL_ROW --> TEMPL_ACTIONS["Template Actions"]
TEMPL_ACTIONS --> VIEW_TEMPL["View Detail<br/>campaign-templates-detail.tsx"]
TEMPL_ACTIONS --> EDIT_TEMPL["Edit Template<br/>campaign-template-edit.tsx"]
TEMPL_ACTIONS --> SYNC_TEMPL["Sync to WhatsApp<br/>POST /templates/:id/sync"]
TEMPL_ACTIONS --> DEL_TEMPL["Delete Template<br/>DELETE /templates/:id"]
TEMPL_LIST --> CREATE_TEMPL["+ New Template"]
CREATE_TEMPL --> TEMPL_CREATE["campaign-template-create.tsx"]
TEMPL_CREATE --> TEMPL_FORM["template-form-fields.tsx<br/>template-form-schema.ts"]
TEMPL_FORM --> TEMPL_HEADER["Header Component<br/>(Text / Image / Video)"]
TEMPL_FORM --> TEMPL_BODY["Body Component<br/>(Text + Variables {{1}}, {{2}}...)"]
TEMPL_FORM --> TEMPL_FOOTER["Footer Component<br/>(Text)"]
TEMPL_FORM --> TEMPL_BUTTONS["Buttons Component<br/>(Quick Reply / URL / Phone)"]
TEMPL_BODY --> UPLOAD_MEDIA["Upload Header Media<br/>POST /templates/upload-media<br/>(Meta Resumable API)"]
TEMPL_FORM --> SAVE_TEMPL["Create Template<br/>POST /templates"]
SAVE_TEMPL --> SYNC_WA["Sync to WhatsApp<br/>POST /templates/:id/sync"]
end
subgraph RECIPIENTS_TAB["RECIPIENT LIST MANAGEMENT"]
TAB_RECIPIENTS --> RL_LIST["campaign-recipient-lists-view.tsx"]
RL_LIST --> RL_TOOLBAR["recipient-list-table-toolbar.tsx"]
RL_LIST --> RL_ROW["recipient-list-table-row.tsx"]
RL_ROW --> RL_ACTIONS["List Actions"]
RL_ACTIONS --> VIEW_RL["View List Detail<br/>campaign-recipient-list-detail.tsx"]
RL_ACTIONS --> EDIT_RL["Edit List"]
RL_ACTIONS --> DEL_RL["Delete List<br/>DELETE /campaign-recipient-lists/:id"]
RL_LIST --> CREATE_RL["+ New Recipient List"]
CREATE_RL --> WIZARD["recipient-list-create-wizard.tsx<br/>(Step-by-step Wizard)"]
WIZARD --> W_STEP1["Step 1: Name & Description<br/>+ Variable Definitions<br/>(e.g. {{nama}}, {{no_pesanan}})"]
W_STEP1 --> W_STEP2["Step 2: Select Contacts<br/>recipient-list-select-contacts-dialog.tsx<br/>(From contact database)"]
W_STEP2 --> W_STEP3["Step 3: Input Variable Values<br/>recipient-list-input-variables-dialog.tsx<br/>(Per contact: nama=John, no_pesanan=123)"]
W_STEP3 --> SAVE_RL["Save Recipient List<br/>POST /campaign-recipient-lists"]
VIEW_RL --> ITEMS_LIST["List Items<br/>campaign-recipient-list-item-table-row.tsx"]
ITEMS_LIST --> ADD_CONTACTS["Add More Contacts<br/>recipient-list-add-contacts-dialog.tsx<br/>POST /campaign-recipient-lists/:id/contacts"]
ITEMS_LIST --> RM_CONTACTS["Remove Contacts<br/>DELETE /campaign-recipient-lists/:id/contacts"]
end
subgraph EXECUTION_FLOW["CAMPAIGN EXECUTION"]
EXECUTE --> EXEC_API["POST /campaigns/:id/execute"]
EXEC_API --> BE_PROCESS["Backend Processing<br/>(See BE Campaign Flow)"]
BE_PROCESS --> DELIVERY_WEBHOOK["WhatsApp Delivery Webhooks<br/>→ Update per-recipient status"]
DELIVERY_WEBHOOK --> VIEW_STATS["View Campaign Stats<br/>sent / delivered / read / failed"]
end
style CAMPAIGNS_TAB fill:#E8F5E9,stroke:#4CAF50
style TEMPLATES_TAB fill:#FFF3E0,stroke:#FF9800
style RECIPIENTS_TAB fill:#F3E5F5,stroke:#9C27B0
style EXECUTION_FLOW fill:#FFEBEE,stroke:#F44336
```
--- ---
...@@ -313,75 +45,7 @@ flowchart TD ...@@ -313,75 +45,7 @@ flowchart TD
Alur lengkap modul Ticket dari pembuatan hingga lifecycle management. Alur lengkap modul Ticket dari pembuatan hingga lifecycle management.
```mermaid ![](./images/3.5.png)
flowchart TD
TICKET_PAGE["Ticket Page<br/>/dashboard/ticket"] --> TICKET_LIST["ticket-list-view.tsx<br/>Ticket DataTable"]
TICKET_LIST --> TOOLBAR["ticket-table-toolbar.tsx<br/>Status Filter | Assignee Filter<br/>Contact Search"]
TICKET_LIST --> TABLE["Ticket Table Columns:<br/>Number, Title, Status,<br/>Assignee, Created, Updated"]
TABLE --> CREATE_BTN["+ New Ticket"]
TABLE --> SELECT_ROW["Select Ticket → Detail View"]
TABLE --> BATCH_ACTIONS["Batch Actions<br/>(Checkbox Selection)"]
subgraph CREATE_FLOW["CREATE TICKET"]
CREATE_BTN --> CREATE_VIEW["ticket-create-view.tsx<br/>(Standalone)"]
CREATE_VIEW --> CREATE_FORM["ticket-create-form.tsx"]
CREATE_FORM --> FORM_TITLE["Ticket Title *"]
CREATE_FORM --> FORM_DESC["Description *"]
CREATE_FORM --> FORM_CONV["Link Conversation<br/>(optional - select from list)"]
CREATE_FORM --> FORM_CONTACT["Link Contact<br/>(optional - select from list)"]
CREATE_FORM --> FORM_ATTACH["Attachments<br/>POST /tickets/upload-media"]
CREATE_FORM --> FORM_TEMPL["Use Template<br/>/dashboard/ticket/templates"]
FORM_ATTACH --> SUBMIT["POST /tickets<br/>→ Status: TODO"]
FORM_TITLE --> SUBMIT
FORM_DESC --> SUBMIT
end
subgraph DETAIL_FLOW["TICKET DETAIL VIEW"]
SELECT_ROW --> DETAIL["ticket-detail-view.tsx<br/>/dashboard/ticket/:id"]
DETAIL --> INFO["Ticket Info<br/>Number, Title, Description,<br/>Status, Assignee, Dates"]
DETAIL --> ATTACHMENTS["ticket-attachment-preview.tsx<br/>File attachments"]
DETAIL --> HISTORY["ticket-history-timeline.tsx<br/>Status change history"]
DETAIL --> ACTION_BUTTONS["Status Action Buttons"]
end
subgraph ACTIONS["TICKET LIFECYCLE ACTIONS"]
ACTION_BUTTONS --> ASSIGN["Assign to Agent<br/>ticket-assign-dialog.tsx<br/>PUT /tickets/:id<br/>assignedTo, assignedBy"]
ACTION_BUTTONS --> START["Start Progress<br/>PUT /tickets/:id<br/>status: IN_PROGRESS"]
ACTION_BUTTONS --> RESOLVE["Resolve<br/>ticket-resolve-dialog.tsx<br/>PUT /tickets/:id<br/>status: RESOLVED + resolvedNote"]
ACTION_BUTTONS --> DONE["Mark Done<br/>ticket-done-dialog.tsx<br/>PUT /tickets/:id<br/>status: DONE"]
ACTION_BUTTONS --> REJECT["Reject<br/>ticket-reject-dialog.tsx<br/>PUT /tickets/:id<br/>status: REJECTED + rejectReason"]
ACTION_BUTTONS --> EDIT["Edit Ticket<br/>ticket-edit-view.tsx<br/>PUT /tickets/:id"]
end
subgraph BATCH_OP["BATCH OPERATIONS"]
BATCH_ACTIONS --> BATCH_ASSIGN["Batch Assign<br/>ticket-batch-assign-dialog.tsx<br/>Loop PUT /tickets/:id"]
BATCH_ACTIONS --> BATCH_STATUS["Batch Update Status<br/>→ IN_PROGRESS<br/>Loop PUT /tickets/:id"]
end
subgraph TICKET_TEMPLATES["TICKET TEMPLATES"]
TICKET_PAGE --> TEMPL_NAV["/dashboard/ticket/templates"]
TEMPL_NAV --> TEMPL_LIST_T["ticket-template-list-view.tsx"]
TEMPL_LIST_T --> TEMPL_CREATE_T["+ New Ticket Template<br/>ticket-template-create-view.tsx"]
TEMPL_CREATE_T --> TEMPL_FORM_T["ticket-template-form-fields.tsx"]
TEMPL_FORM_T --> TEMPL_TYPE["Template Type:<br/>TICKET_START / TICKET_END"]
TEMPL_FORM_T --> SAVE_TEMPL_T["POST /templates/ticket"]
end
subgraph EMBEDDED["TICKET FROM CHAT"]
CHAT_PANEL["Chat Room<br/>Right Panel → Ticket Tab"] --> TICKET_TAB["chat-room-ticket.tsx"]
TICKET_TAB --> TICKET_FORM_CHAT["ticket-form.tsx<br/>(Inline Create)"]
TICKET_FORM_CHAT --> CREATE_FROM_CHAT["Create Ticket<br/>(auto-links current conversation)"]
TICKET_TAB --> TICKET_LIST_CHAT["ticket-list.tsx<br/>(Linked tickets list)"]
end
style CREATE_FLOW fill:#E8F5E9,stroke:#4CAF50
style DETAIL_FLOW fill:#FFF3E0,stroke:#FF9800
style ACTIONS fill:#FFEBEE,stroke:#F44336
style TICKET_TEMPLATES fill:#E3F2FD,stroke:#1976D2
style EMBEDDED fill:#F3E5F5,stroke:#9C27B0
```
--- ---
...@@ -389,87 +53,7 @@ flowchart TD ...@@ -389,87 +53,7 @@ flowchart TD
Alur lengkap halaman Settings beserta role guard pada setiap sub-menu. Alur lengkap halaman Settings beserta role guard pada setiap sub-menu.
```mermaid ![](./images/3.6.png)
flowchart TD
SETTINGS_ENTRY["/dashboard/settings"] --> REDIRECT["Redirect to<br/>/dashboard/settings/user/list"]
SETTINGS_ENTRY --> SETTINGS_NAV{{"Settings Navigation"}}
subgraph USER_MGMT["USER MANAGEMENT [admin, owner]"]
SETTINGS_NAV --> SET_USER["/settings/user/list<br/>SettingsUserListPage"]
SETTINGS_NAV --> SET_USER_NEW["/settings/user/new<br/>UserCreatePage"]
SETTINGS_NAV --> SET_USER_EDIT["/settings/user/:id/edit<br/>UserEditPage"]
SETTINGS_NAV --> SET_ROLE_PERM["/settings/user/role-permission<br/>SettingsUserRolePermissionPage"]
SET_USER --> USER_TABLE["User List Table<br/>GET /auth/admin/list-users"]
USER_TABLE --> USER_ACTIONS["User Actions"]
USER_ACTIONS --> CREATE_USER["Create User<br/>POST /auth/admin/create-user"]
USER_ACTIONS --> EDIT_USER["Edit User<br/>POST /auth/admin/update-user"]
USER_ACTIONS --> SET_ROLE["Set Role<br/>POST /auth/admin/set-role"]
USER_ACTIONS --> BAN_USER["Ban User"]
USER_ACTIONS --> UNBAN["Unban User<br/>POST /auth/admin/unban-user"]
USER_ACTIONS --> INVITE["Invite Member<br/>POST /auth/organization/invite-member"]
end
subgraph CHANNEL_MGMT["CHANNEL MANAGEMENT [admin, owner]"]
SETTINGS_NAV --> SET_CH["/settings/channel<br/>ChannelPage"]
SETTINGS_NAV --> SET_CH_NEW["/settings/channel/new<br/>ChannelCreatePage"]
SETTINGS_NAV --> SET_CH_EDIT["/settings/channel/:id/edit<br/>ChannelEditPage"]
SET_CH --> CH_LIST["Channel List<br/>GET /channels"]
CH_LIST --> CH_ACTIONS["Channel Actions"]
CH_ACTIONS --> CONNECT_WA["Connect WhatsApp<br/>POST /channels/whatsapp<br/>(Embedded Signup Flow)"]
CH_ACTIONS --> CONNECT_IG["Connect Instagram<br/>POST /channels/instagram<br/>(Business Login Flow)"]
CH_ACTIONS --> EDIT_CH["Edit Channel<br/>PUT /channels/:id"]
CH_ACTIONS --> GEN_WEBHOOK["Generate Webhook Token<br/>POST /channels/:id/generate-webhook"]
CH_ACTIONS --> DEL_CH["Delete Channel<br/>DELETE /channels/:id"]
end
subgraph WS_MGMT["WORKSPACE MANAGEMENT [owner only]"]
SETTINGS_NAV --> SET_WS["/settings/workspace<br/>WorkspacePage"]
SETTINGS_NAV --> SET_WS_NEW["/settings/workspace/new<br/>WorkspaceCreatePage"]
SETTINGS_NAV --> SET_WS_EDIT["/settings/workspace/:id/edit<br/>WorkspaceEditPage"]
SET_WS --> WS_LIST["Workspace List<br/>authClient.organization.list()"]
WS_LIST --> WS_ACTIONS["Workspace Actions"]
WS_ACTIONS --> CREATE_WS["Create Workspace<br/>authClient.organization.create()"]
WS_ACTIONS --> EDIT_WS["Edit Workspace<br/>authClient.organization.update()"]
WS_ACTIONS --> DEL_WS["Delete Workspace<br/>authClient.organization.delete()"]
WS_ACTIONS --> SESSION_CFG["Session Time Config<br/>PUT /workspaces/:id/session-time<br/>Options: 5min, 30min, 1hour, 24hour, never"]
end
subgraph ACCOUNT["MY ACCOUNT [all roles]"]
SETTINGS_NAV --> SET_ACCOUNT["/settings/account<br/>AccountGeneralPage"]
SETTINGS_NAV --> SET_PWD["/settings/account/change-password<br/>AccountChangePasswordPage"]
SET_ACCOUNT --> PROFILE["Profile Settings<br/>PUT /user/update-user<br/>(username, fullname, phone,<br/>photo, image_url, status)"]
SET_PWD --> CHANGE_PWD["Change Password<br/>POST /auth/change-password<br/>(currentPassword, newPassword,<br/>revokeOtherSessions)"]
end
subgraph OTHER_SETTINGS["OTHER SETTINGS [admin, owner]"]
SETTINGS_NAV --> SET_TAG["/settings/tag<br/>Tag Management<br/>CRUD /tags"]
SETTINGS_NAV --> SET_OH["/settings/office-hours<br/>Office Hours & Auto Response<br/>CRUD /office-hour-*"]
SETTINGS_NAV --> SET_QM["/settings/quick-message<br/>Quick Reply Templates<br/>CRUD /message-templates"]
SETTINGS_NAV --> SET_API["/settings/api-keys<br/>API Key Management<br/>CRUD /workspace-api-keys"]
SETTINGS_NAV --> SET_RC["/settings/resolve-category<br/>Resolve Categories<br/>CRUD /conversation-resolve-category"]
end
subgraph OFFICE_HOURS_DETAIL["OFFICE HOURS DETAIL"]
SET_OH --> OH_SCHEDULE["Schedule Configuration<br/>Per Channel"]
OH_SCHEDULE --> OH_DAYS["Days: Mon-Sun<br/>Start Time, End Time<br/>Timezone"]
OH_SCHEDULE --> OH_STATUS["useOfficeHoursStatus hook<br/>Auto-check every minute<br/>→ Online / Offline"]
SET_OH --> OH_RESPONSE["Auto Response Configuration<br/>Per Channel"]
OH_RESPONSE --> OH_DURING["During Office Hours<br/>Auto-response message"]
OH_RESPONSE --> OH_OUTSIDE["Outside Office Hours<br/>Auto-response message"]
end
style USER_MGMT fill:#E8F5E9,stroke:#4CAF50
style CHANNEL_MGMT fill:#FFF3E0,stroke:#FF9800
style WS_MGMT fill:#FFEBEE,stroke:#F44336
style ACCOUNT fill:#E3F2FD,stroke:#1976D2
style OTHER_SETTINGS fill:#F3E5F5,stroke:#9C27B0
```
--- ---
...@@ -477,36 +61,4 @@ flowchart TD ...@@ -477,36 +61,4 @@ flowchart TD
Diagram yang menunjukkan pola data fetching menggunakan SWR di seluruh aplikasi. Diagram yang menunjukkan pola data fetching menggunakan SWR di seluruh aplikasi.
```mermaid ![](./images/3.7.png)
flowchart TD
COMPONENT["React Component<br/>(Page / Section)"] --> HOOK["Custom Hook<br/>(use-conversations,<br/>use-messages, etc.)"]
HOOK --> SWR_HOOK["useSWR()<br/>SWR Hook"]
SWR_HOOK --> FETCHER["Axios Fetcher<br/>/src/lib/fetcher.ts"]
FETCHER --> API["API Request<br/>GET /api/v1/u/*"]
API --> RESPONSE{"Response?"}
RESPONSE -->|200 OK| CACHE["SWR Cache<br/>(Key: URL + Params)"]
RESPONSE -->|Error| ERROR_HANDLER["Error Handling"]
ERROR_HANDLER --> SHOW_TOAST["Snackbar Toast<br/>Error Message"]
CACHE --> RENDER["Render Component<br/>with Cached Data"]
RENDER --> IDLE{"Data Stale?"}
IDLE -->|Yes| REVALIDATE["Auto Revalidation<br/>(revalidateOnFocus,<br/>revalidateOnReconnect)"]
IDLE -->|No| WAIT["Wait for Event"]
REVALIDATE --> FETCHER
subgraph RT_EVENTS["Real-Time Triggered Revalidation"]
PUSHER_EV["Pusher Event Received"] --> MUTATE["SWR mutate()<br/>Invalidate specific key"]
SOCKET_EV["Socket.IO Event"] --> MUTATE
MUTATE --> FETCHER
end
subgraph MANUAL["Manual Mutations"]
OPTIMISTIC["Optimistic Update"] --> LOCAL_UPDATE["Update local cache first"]
MANUAL_CALL["Manual API Call<br/>(POST/PUT/DELETE)"] --> MANUAL_MUTATE["mutate() after success"]
MANUAL_CALL -->|"Error"| ROLLBACK["Rollback optimistic update"]
LOCAL_UPDATE --> MANUAL_MUTATE
end
```
...@@ -7,63 +7,7 @@ ...@@ -7,63 +7,7 @@
Arsitektur umum Backend menggunakan pattern **Repository → Service → Route** (Layered Architecture). Arsitektur umum Backend menggunakan pattern **Repository → Service → Route** (Layered Architecture).
```mermaid ![](./images/4.1.png)
flowchart TD
ENTRY["index.ts<br/>Bun HTTP Server<br/>Port: 8000"] --> APP_SVC["app.service.ts<br/>createApp()"]
APP_SVC --> INIT_PUSH["Initialize Pusher"]
APP_SVC --> INIT_RMQ["Initialize RabbitMQ"]
APP_SVC --> DEV_INIT{"Development?"}
DEV_INIT -->|Yes| DB_INIT["Initialize DB<br/>Test Connection"]
subgraph GLOBAL_MW["Global Middleware Stack"]
GM1["CORS (cors.config.ts)<br/>Allowed origins validation"]
GM2["Timing (response time header)"]
GM3["Request ID (unique per request)"]
GM4["Logger (request logging)"]
GM5["Body Limit (5MB default)"]
GM6["Timeout (5min default)"]
GM1 --> GM2 --> GM3 --> GM4 --> GM5 --> GM6
end
APP_SVC --> GLOBAL_MW
subgraph ROUTES["Route Registration (v1.ts)"]
R_AUTH["/api/v1/auth/*<br/>better-auth handler"]
R_WEBHOOK["/api/webhook/*<br/>Webhook receivers"]
R_PROTECTED["/api/v1/u/*<br/>requireAuth middleware"]
R_UPLOADS["/api/uploads<br/>File storage"]
R_SWAGGER["/api/v1/swagger<br/>OpenAPI docs"]
end
GLOBAL_MW --> ROUTES
subgraph AUTH_MW_LAYER["Auth Middleware Chain"]
AM1["requireAuth<br/>(Session verification)"]
AM2["optionalAuth<br/>(No block if missing)"]
AM3["requirePermission<br/>(RBAC check)"]
AM4["requireWorkspaceApiKey<br/>(x-api-key header)"]
AM5["requireAuthOrWorkspaceApiKey<br/>(Either session or API key)"]
end
R_PROTECTED --> AUTH_MW_LAYER
AUTH_MW_LAYER --> SERVICE["Service Layer<br/>(47 Modules)"]
SERVICE --> REPO["Repository Layer<br/>(Drizzle ORM queries)"]
REPO --> DB["PostgreSQL<br/>schema: omnichannel<br/>30+ tables"]
SERVICE --> NOTIFY["Real-time Notification<br/>notifyHybrid()<br/>Socket.IO + Pusher"]
SERVICE --> EXT_API["External APIs<br/>WhatsApp / Instagram / Telegram"]
R_WEBHOOK --> RABBIT["RabbitMQ Publisher"]
RABBIT --> WORKER["Worker Consumer"]
WORKER --> SERVICE
style ENTRY fill:#C8E6C9,stroke:#4CAF50
style GLOBAL_MW fill:#FFF3E0,stroke:#FF9800
style AUTH_MW_LAYER fill:#FFEBEE,stroke:#F44336
style SERVICE fill:#E3F2FD,stroke:#1976D2
style REPO fill:#F3E5F5,stroke:#9C27B0
```
--- ---
...@@ -71,72 +15,7 @@ flowchart TD ...@@ -71,72 +15,7 @@ flowchart TD
Alur lengkap autentikasi dan authorization di Backend. Alur lengkap autentikasi dan authorization di Backend.
```mermaid ![](./images/4.2.png)
flowchart TD
REQUEST["Incoming Request"] --> ROUTE_TYPE{Route Type?}
subgraph PUBLIC_ROUTES["Public Routes (No Auth)"]
ROUTE_TYPE -->|"/api/v1/swagger"| SWAGGER["Swagger UI"]
ROUTE_TYPE -->|"/api/webhook/whatsapp<br/>(GET verify)"| WH_VERIFY["Webhook Verification"]
end
subgraph BETTER_AUTH["Better-Auth Self-Handled"]
ROUTE_TYPE -->|"/api/v1/auth/*"| BA_HANDLER["better-auth handler"]
BA_HANDLER --> BA_SIGNIN["sign-in/email-password"]
BA_HANDLER --> BA_SIGNUP["sign-up/email-password"]
BA_HANDLER --> BA_FORGET["forget-password"]
BA_HANDLER --> BA_RESET["reset-password"]
BA_HANDLER --> BA_CHANGE["change-password"]
BA_HANDLER --> BA_SESSION["get-session"]
BA_HANDLER --> BA_ADMIN["admin/list-users"]
BA_HANDLER --> BA_CREATE["admin/create-user"]
BA_HANDLER --> BA_UPDATE["admin/update-user"]
BA_HANDLER --> BA_SET_ROLE["admin/set-role"]
BA_HANDLER --> BA_UNBAN["admin/unban-user"]
BA_HANDLER --> BA_ORG_LIST["organization/list-members"]
BA_HANDLER --> BA_ORG_INVITE["organization/invite-member"]
BA_HANDLER --> BA_ORG_UPDATE["organization/update-member-role"]
BA_HANDLER --> BA_ORG_REMOVE["organization/remove-member"]
BA_HANDLER --> BA_ORG_ACCEPT["organization/accept-invitation"]
BA_HANDLER --> BA_APIKEY["api-key/create"]
end
subgraph PROTECTED["Protected Routes (Auth Required)"]
ROUTE_TYPE -->|"/api/v1/u/*"| AUTH_CHECK
AUTH_CHECK["Which Auth Method?"]
AUTH_CHECK -->|"Default"| REQUIRE_AUTH["requireAuth middleware"]
AUTH_CHECK -->|"Tickets"| EITHER["requireAuthOrWorkspaceApiKey"]
AUTH_CHECK -->|"Optional"| OPTIONAL["optionalAuth"]
REQUIRE_AUTH --> SESSION_CHECK{Valid Session<br/>Cookie?}
SESSION_CHECK -->|No| ERR_401["401 Unauthorized<br/>response.util.ts"]
SESSION_CHECK -->|Yes| SET_CTX["Set c.set('user', user)<br/>c.set('session', session)"]
EITHER --> CHECK_EITHER{Session OR<br/>API Key?}
CHECK_EITHER -->|Neither| ERR_401
CHECK_EITHER -->|Session| SET_CTX
CHECK_EITHER -->|API Key| CHECK_API{Valid API Key?<br/>x-api-key header}
CHECK_API -->|No| ERR_401
CHECK_API -->|Yes| SET_API_CTX["Set user + workspace<br/>from key metadata"]
SET_CTX --> RBAC_CHECK
SET_API_CTX --> RBAC_CHECK
RBAC_CHECK["requirePermission({resource: action})"]
RBAC_CHECK --> CHECK_ROLE{User has<br/>permission?}
CHECK_ROLE -->|No| ERR_403["403 Forbidden"]
CHECK_ROLE -->|Yes| HANDLER["Route Handler"]
end
HANDLER --> SVC["Service → Repository → DB"]
SVC --> RESP["Response<br/>response.util.ts<br/>success / error / paginated"]
SVC --> NOTIFY["notifyHybrid()<br/>Pusher + Socket.IO"]
style PUBLIC_ROUTES fill:#E8F5E9,stroke:#4CAF50
style BETTER_AUTH fill:#FFF3E0,stroke:#FF9800
style PROTECTED fill:#FFEBEE,stroke:#F44336
```
--- ---
...@@ -144,91 +23,7 @@ flowchart TD ...@@ -144,91 +23,7 @@ flowchart TD
Alur lengkap mesin chat Backend, baik inbound maupun outbound. Alur lengkap mesin chat Backend, baik inbound maupun outbound.
```mermaid ![](./images/4.3.png)
flowchart TD
subgraph INBOUND["INBOUND MESSAGE FLOW"]
WEBHOOK["Webhook Received"] --> PARSE["Parse Platform Data"]
PARSE --> EXTRACT["Extract:<br/>sender_phone, content, type,<br/>channel_code, raw_id"]
EXTRACT --> PROFANITY{"Profanity<br/>Filter?"}
PROFANITY -->|Flagged| MARK_FLAGGED["Mark as flagged"]
PROFANITY -->|Clean| FIND_CONTACT
MARK_FLAGGED --> FIND_CONTACT{Contact<br/>Exists?<br/>MATCH by phone + channel_id}
FIND_CONTACT -->|No| CREATE_CONTACT["Create Contact<br/>INSERT contacts<br/>contact_name=phone<br/>contact_phone=phone"]
FIND_CONTACT -->|Yes| GET_CONTACT["SELECT contact<br/>WHERE phone + channel_id"]
CREATE_CONTACT --> FIND_CONV
GET_CONTACT --> FIND_CONV
FIND_CONV{Active Conversation?<br/>MATCH by contact_id<br/>+ channel_id<br/>+ status NOT CLOSED/RESOLVED}
FIND_CONV -->|No| CREATE_CONV["INSERT conversations<br/>status: UNASSIGNED<br/>unread_counts: 1"]
FIND_CONV -->|Yes Chain| UPDATE_CHAIN["Link previous_conversation_id"]
FIND_CONV -->|Yes Active| UPDATE_CONV
UPDATE_CHAIN --> UPDATE_CONV["UPDATE conversations<br/>last_message, last_message_at<br/>unread_counts++<br/>conversation_24h_window<br/>(WhatsApp only)"]
UPDATE_CONV --> SAVE_MSG["INSERT conversation_messages<br/>from=customer, to=system<br/>type based on content"]
SAVE_MSG --> SAVE_ATTACH{"Has<br/>Attachments?"}
SAVE_ATTACH -->|Yes| SAVE_ATT["INSERT message_attachments<br/>Download media from<br/>platform API"]
SAVE_ATTACH -->|No| CHECK_OH
SAVE_ATT --> CHECK_OH
CHECK_OH{"Within<br/>Office Hours?<br/>office-hours-schedule"}
CHECK_OH -->|No| AUTO_RESP["Send Auto Response<br/>office-hours-auto-responses<br/>→ Platform Send API"]
CHECK_OH -->|Yes| NOTIFY
AUTO_RESP --> NOTIFY["notifyHybrid()"]
NOTIFY --> P1["Pusher: private-room:{convId}<br/>Event: new-message"]
NOTIFY --> P2["Pusher: private-channel:{chId}<br/>Event: new-conversation<br/>(if new)"]
NOTIFY --> S1["Socket.IO: emit new-message"]
end
subgraph OUTBOUND["OUTBOUND MESSAGE FLOW"]
SEND_API["POST /conversation-messages<br/>(Agent sends message)"] --> VALIDATE["Validate with Zod<br/>conversation-message.validation.ts"]
VALIDATE --> CHECK_TYPE{Message Type?}
CHECK_TYPE -->|"TEXT"| PREPARE_TEXT["Prepare text content"]
CHECK_TYPE -->|"IMAGE/VIDEO/AUDIO/DOC"| UPLOAD_MEDIA["Check media<br/>Already uploaded or new"]
CHECK_TYPE -->|"TEMPLATE"| PREPARE_TEMPL["Prepare template<br/>components + variables"]
PREPARE_TEXT --> PROFANITY_OUT{"Profanity?"}
UPLOAD_MEDIA --> PROFANITY_OUT
PREPARE_TEMPL --> SKIP_PROFANE
PROFANITY_OUT -->|Flagged| REJECT["400 Bad Request<br/>'Message contains inappropriate content'"]
PROFANITY_OUT -->|Clean| CHECK_WINDOW
SKIP_PROFANE --> CHECK_WINDOW{"WhatsApp 24h<br/>Window Active?<br/>(if WhatsApp channel)"}
CHECK_WINDOW -->|Expired| ERR_WINDOW["Error:<br/>'24-hour messaging window expired'"]
CHECK_WINDOW -->|Active or Non-WA| SEND_PLATFORM
SEND_PLATFORM["Send to Platform API"]
SEND_PLATFORM --> WA_API["WhatsApp API<br/>POST /messages"]
SEND_PLATFORM --> IG_API["Instagram API<br/>POST /messages"]
SEND_PLATFORM --> TG_API["Telegram API<br/>sendMessage"]
WA_API --> SAVE_OUT["INSERT conversation_messages<br/>from=agent, to=customer<br/>status: SENT"]
IG_API --> SAVE_OUT
TG_API --> SAVE_OUT
SAVE_OUT --> UPDATE_CONV_OUT["UPDATE conversations<br/>last_message, last_message_at"]
UPDATE_CONV_OUT --> NOTIFY_OUT["notifyHybrid()<br/>private-room:{convId}<br/>Event: new-message"]
end
subgraph DELIVERY["DELIVERY STATUS WEBHOOK"]
DELIVERY_WB["Platform Delivery Webhook"] --> FIND_MSG{"Find Message<br/>by platform_msg_id"}
FIND_MSG --> UPDATE_STATUS["UPDATE conversation_messages<br/>status: DELIVERED / READ / FAILED"]
UPDATE_STATUS --> NOTIFY_DELIVERY["notifyHybrid()<br/>Event: message-updated"]
end
style INBOUND fill:#E8F5E9,stroke:#4CAF50
style OUTBOUND fill:#FFF3E0,stroke:#FF9800
style DELIVERY fill:#E3F2FD,stroke:#1976D2
```
--- ---
...@@ -266,67 +61,7 @@ stateDiagram-v2 ...@@ -266,67 +61,7 @@ stateDiagram-v2
Alur lengkap eksekusi campaign di Backend. Alur lengkap eksekusi campaign di Backend.
```mermaid ![](./images/4.5.png)
flowchart TD
TRIGGER["POST /campaigns/:id/execute"] --> LOAD["Load Campaign<br/>+ Template + Channel + Recipient List"]
LOAD --> STATUS_CHECK{Campaign Status?}
STATUS_CHECK -->|"DRAFT"| SET_RUNNING["UPDATE campaigns<br/>status: RUNNING<br/>started_at: NOW()"]
STATUS_CHECK -->|"SCHEDULED"| CHECK_SCHEDULE{Scheduled time<br/>reached?}
STATUS_CHECK -->|"RUNNING"| ALREADY["Return: Already running"]
STATUS_CHECK -->|"COMPLETED/CANCELLED"| ERR["Return Error"]
CHECK_SCHEDULE -->|"Not yet"| SCHEDULE_WAIT["Wait for cron/scheduler"]
CHECK_SCHEDULE -->|"Yes"| SET_RUNNING
SET_RUNNING --> LOAD_TEMPLATE["Load Template<br/>templates table<br/>template_components (JSON)"]
LOAD_TEMPLATE --> LOAD_LIST["Load Recipient List Items<br/>campaign_recipient_list_items<br/>+ contacts"]
LOAD_LIST --> INIT_COUNTS["Reset campaign counts:<br/>total_recipients, sent, delivered,<br/>read, failed = 0"]
INIT_COUNTS --> LOOP_START["Start Loop"]
subgraph LOOP["PER RECIPIENT LOOP"]
LOOP_START --> GET_NEXT{Next<br/>Recipient?}
GET_NEXT -->|No| COMPLETE
GET_NEXT -->|Yes| RENDER["Render Template<br/>Replace {{1}}, {{2}}...<br/>with recipient variables"]
RENDER --> FIND_CONV{Has Active<br/>Conversation?<br/>contact_id + channel_id}
FIND_CONV -->|No| CREATE_CONV["INSERT conversations<br/>status: UNASSIGNED"]
FIND_CONV -->|Yes| USE_CONV["Use existing conversation"]
CREATE_CONV --> SEND_WA
USE_CONV --> SEND_WA
SEND_WA["Send via WhatsApp API<br/>POST /{phone_id}/messages<br/>template type message"]
SEND_WA --> WA_RESPONSE{WhatsApp<br/>API Response?}
WA_RESPONSE -->|Success| RECORD_SENT["INSERT campaign_recipients<br/>status: SENT<br/>conversation_id, message_id"]
WA_RESPONSE -->|Rate Limit| WAIT_RETRY["Wait + Retry<br/>(Exponential backoff)"]
WA_RESPONSE -->|Error| RECORD_FAIL["INSERT campaign_recipients<br/>status: FAILED"]
RECORD_SENT --> INC_SENT["UPDATE campaigns<br/>sent++"]
RECORD_FAIL --> INC_FAIL["UPDATE campaigns<br/>failed++"]
INC_SENT --> NEXT["Continue Loop"]
INC_FAIL --> NEXT
WAIT_RETRY --> SEND_WA
NEXT --> GET_NEXT
end
COMPLETE["All Recipients Processed"] --> SET_COMPLETE["UPDATE campaigns<br/>status: COMPLETED<br/>completed_at: NOW()"]
subgraph DELIVERY["DELIVERY WEBHOOKS"]
WA_DELIVERY["WhatsApp Delivery Callback"] --> FIND_RECIP{Find campaign_recipient<br/>by conversation_message_id}
FIND_RECIP --> UPDATE_RECIP["UPDATE campaign_recipients<br/>status: DELIVERED / READ / FAILED"]
UPDATE_RECIP --> UPDATE_CAMP_COUNTS["UPDATE campaigns<br/>delivered++ / read++ / failed++"]
UPDATE_CAMP_COUNTS --> NOTIFY_STATS["Optional: Push notification<br/>to campaign dashboard"]
end
style LOOP fill:#E8F5E9,stroke:#4CAF50
style DELIVERY fill:#FFF3E0,stroke:#FF9800
```
--- ---
...@@ -334,60 +69,7 @@ flowchart TD ...@@ -334,60 +69,7 @@ flowchart TD
Alur lengkap lifecycle ticket di Backend. Alur lengkap lifecycle ticket di Backend.
```mermaid ![](./images/4.6.png)
flowchart TD
CREATE["POST /tickets<br/>Create Ticket"] --> VALIDATE_T["Validate with Zod<br/>ticket.validation.ts"]
VALIDATE_T --> INSERT_TICKET["INSERT tickets<br/>ticket_number: auto-generated<br/>status: TODO"]
INSERT_TICKET --> INSERT_ATTACH{"Has<br/>Attachments?"}
INSERT_ATTACH -->|Yes| INSERT_ATT["INSERT ticket_attachments<br/>(link to ticket_id)"]
INSERT_ATTACH -->|No| DONE_CREATE
INSERT_ATT --> DONE_CREATE["Ticket Created<br/>201 Created Response"]
subgraph LIFECYCLE["TICKET LIFECYCLE"]
DONE_CREATE --> TODO_ST["Status: TODO"]
TODO_ST -->|"Assign to Agent<br/>PUT /tickets/:id<br/>assignedTo, assignedBy"| IN_PROGRESS["Status: IN_PROGRESS"]
TODO_ST -->|"Soft Delete<br/>DELETE /tickets/:id"| DELETED["Status: DELETED<br/>(deleted_at IS NOT NULL)"]
IN_PROGRESS -->|"Reassign<br/>assignedTo changed"| IN_PROGRESS
IN_PROGRESS -->|"Resolve<br/>status: RESOLVED<br/>resolvedBy, resolvedAt<br/>resolvedNote"| RESOLVED["Status: RESOLVED"]
IN_PROGRESS -->|"Reject<br/>status: REJECTED<br/>rejectedBy, rejectedAt<br/>rejectReason"| REJECTED["Status: REJECTED"]
RESOLVED -->|"Reopen<br/>status back to<br/>IN_PROGRESS"| IN_PROGRESS
RESOLVED -->|"Mark Done<br/>status: DONE<br/>closedBy, closedAt"| DONE["Status: DONE"]
REJECTED --> END_STATE["Terminal State"]
DONE --> END_STATE
DELETED --> END_STATE
end
subgraph HISTORY["STATUS HISTORY TRACKING"]
STATUS_CHANGE["Any Status Change"] --> RECORD_HIST["INSERT ticket_status_history<br/>ticket_id, old_status, new_status<br/>updated_by, assigned_to<br/>resolved_by, rejected_by"]
RECORD_HIST --> NOTIFY_TICKET["notifyHybrid()<br/>(if real-time needed)"]
end
LIFECYCLE --> HISTORY
subgraph BATCH["BATCH OPERATIONS"]
BATCH_ASSIGN["Batch Assign<br/>(FE loops PUT calls)"] --> LOOP_PUT1["PUT /tickets/:id<br/>assignedTo: userId<br/>× N tickets"]
BATCH_STATUS["Batch Update Status<br/>(FE loops PUT calls)"] --> LOOP_PUT2["PUT /tickets/:id<br/>status: IN_PROGRESS<br/>× N tickets"]
end
subgraph FROM_CHAT["TICKET FROM CONVERSATION"]
CHAT_LINK["conversation_id provided"] --> VERIFY_CONV{"Conversation<br/>Exists?"}
VERIFY_CONV -->|Yes| LINK_CONV["Set ticket.conversation_id"]
VERIFY_CONV -->|No| ERR_CONV["Error: Conversation not found"]
CONTACT_LINK["contact_id provided"] --> VERIFY_CONTACT{"Contact<br/>Exists?"}
VERIFY_CONTACT -->|Yes| LINK_CONTACT["Set ticket.contact_id"]
VERIFY_CONTACT -->|No| ERR_CONTACT["Error: Contact not found"]
end
style LIFECYCLE fill:#E8F5E9,stroke:#4CAF50
style HISTORY fill:#FFF3E0,stroke:#FF9800
style BATCH fill:#E3F2FD,stroke:#1976D2
style FROM_CHAT fill:#F3E5F5,stroke:#9C27B0
```
--- ---
...@@ -395,76 +77,7 @@ flowchart TD ...@@ -395,76 +77,7 @@ flowchart TD
Arsitektur pemrosesan webhook dari external channels. Arsitektur pemrosesan webhook dari external channels.
```mermaid ![](./images/4.7.png)
flowchart TD
subgraph INCOMING["WEBHOOK RECEIVERS"]
WH_WA_GET["GET /api/webhook/whatsapp<br/>Verification<br/>(hub.mode, hub.verify_token,<br/>hub.challenge)"]
WH_WA_POST["POST /api/webhook/whatsapp<br/>Receive messages<br/>(WhatsApp payload)"]
WH_IG["POST /api/webhook/instagram<br/>Receive DMs + Comments<br/>(Instagram payload)"]
WH_TG["POST /api/webhook/telegram<br/>Receive messages<br/>(Telegram payload)"]
end
subgraph WHATSAPP_FLOW["WHATSAPP FLOW"]
WH_WA_GET --> VERIFY{Verify Token<br/>Matches?}
VERIFY -->|Yes| RETURN_CHALLENGE["Return hub.challenge<br/>200 OK"]
VERIFY -->|No| ERR_403["403 Forbidden"]
WH_WA_POST --> VERIFY_WA_HUB{"Hub Mode = 'subscribe'?"}
%% Perbaikan Baris 15 & 16: Dipisah ke baris baru
VERIFY_WA_HUB -->|Yes| RETURN_200["200 OK No processing"]
VERIFY_WA_HUB -->|No| PARSE_WA["Parse WhatsApp Payload<br/>Extract: entry.changes<br/>messages, statuses"]
PARSE_WA --> MSG_TYPE{Message Type?}
MSG_TYPE -->|"messages"| TO_QUEUE["Publish to RabbitMQ<br/>Queue: omnichannel_work_queue<br/>Route: whatsapp"]
MSG_TYPE -->|"statuses"| PROCESS_STATUS["Process Delivery Status<br/>UPDATE conversation_messages<br/>status: SENT → DELIVERED → READ"]
TO_QUEUE --> RABBIT_MQ["RabbitMQ<br/>amqplib connection"]
end
subgraph RABBIT_WORKER["RABBITMQ WORKER"]
RABBIT_MQ --> CONSUME["Consume from Queue"]
CONSUME --> ACK{"Process<br/>Success?"}
ACK -->|Yes| ACK_MSG["ack message"]
ACK -->|No| NACK["nack + requeue<br/>(or dead-letter)"]
NACK --> CONSUME
end
subgraph DIRECT_PROCESSING["DIRECT PROCESSING<br/>(Instagram, Telegram)"]
WH_IG --> PARSE_IG["Parse Instagram Payload"]
PARSE_IG --> IG_TYPE{Event Type?}
%% Perbaikan: Menghapus karakter asteris (*) yang bisa mengganggu parser
IG_TYPE -->|messages| INSTA_DM["Process Instagram DM<br/>→ conversation engine"]
IG_TYPE -->|comments| INSTA_COMMENT["Process Comment<br/>→ conversation engine<br/>context: POST_COMMENT"]
WH_TG --> PARSE_TG["Parse Telegram Payload"]
PARSE_TG --> TG_PROCESS["Process Telegram Message<br/>→ conversation engine"]
end
subgraph SHARED_PROCESSOR["SHARED MESSAGE PROCESSOR"]
TO_QUEUE --> PROCESSOR["Message Processor Service"]
INSTA_DM --> PROCESSOR
INSTA_COMMENT --> PROCESSOR
TG_PROCESS --> PROCESSOR
PROCESSOR --> CONTACT_RESOLVE["Resolve/Create Contact"]
PROCESSOR --> CONV_RESOLVE["Resolve/Create Conversation"]
PROCESSOR --> SAVE_MESSAGE["Save Message + Attachments"]
PROCESSOR --> CHECK_AUTO_RESP["Check Auto Response"]
PROCESSOR --> NOTIFY_RT["Real-time Notification"]
end
PROCESS_STATUS --> DB_UPDATE["Direct DB Update"]
DB_UPDATE --> DB[(PostgreSQL)]
SHARED_PROCESSOR --> DB
style INCOMING fill:#E8F5E9,stroke:#4CAF50
style WHATSAPP_FLOW fill:#FFF3E0,stroke:#FF9800
style RABBIT_WORKER fill:#FFEBEE,stroke:#F44336
style DIRECT_PROCESSING fill:#E3F2FD,stroke:#1976D2
style SHARED_PROCESSOR fill:#F3E5F5,stroke:#9C27B0
```
--- ---
...@@ -472,76 +85,11 @@ flowchart TD ...@@ -472,76 +85,11 @@ flowchart TD
Pattern yang digunakan di setiap service module Backend. Pattern yang digunakan di setiap service module Backend.
```mermaid ![](./images/4.8.png)
flowchart TD
subgraph MODULE["Service Module Structure"]
direction TB
ROUTE["*.route.ts<br/>───────────────<br/>Define Hono routes<br/>Apply middleware<br/>Call service functions<br/>Return responses"]
SVC["*.service.ts<br/>───────────────<br/>Business logic<br/>Orchestrate repos<br/>Handle errors<br/>Trigger notifications"]
REPO["*.repository.ts<br/>───────────────<br/>Drizzle ORM queries<br/>INSERT / SELECT<br/>UPDATE / DELETE<br/>Transactions"]
VALID["*.validation.ts<br/>───────────────<br/>Zod schemas<br/>Input validation<br/>Output parsing"]
ERR["*.error.ts<br/>───────────────<br/>Custom error classes<br/>Error codes<br/>Error messages"]
TYPE["*.type.ts<br/>───────────────<br/>TypeScript types<br/>Interfaces<br/>Enums"]
end
ROUTE -->|"Validate input"| VALID
ROUTE -->|"Call business logic"| SVC
SVC -->|"Query database"| REPO
SVC -->|"Throw errors"| ERR
REPO -->|"Read types"| TYPE
ROUTE -->|"Read types"| TYPE
SVC -->|"Read types"| TYPE
subgraph EXAMPLE["Example: conversation-message"]
EX_ROUTE["conversation-message.route.ts<br/>GET / → listMessages()<br/>POST / → sendMessage()<br/>POST /template → sendTemplate()"]
EX_SVC["conversation-message.service.ts<br/>sendMessage():<br/> 1. Validate profanity<br/> 2. Check 24h window<br/> 3. Send to platform API<br/> 4. Save to DB<br/> 5. Notify real-time"]
EX_REPO["conversation-message.repository.ts<br/>findById()<br/>findByConversationId()<br/>create()<br/>updateStatus()"]
EX_VALID["conversation-message.validation.ts<br/>sendMessageSchema<br/>sendTemplateSchema<br/>listMessagesSchema"]
end
EX_ROUTE --> EX_SVC
EX_SVC --> EX_REPO
EX_ROUTE --> EX_VALID
style MODULE fill:#F5F5F5,stroke:#9E9E9E
style EXAMPLE fill:#E3F2FD,stroke:#1976D2
```
--- ---
## 4.9 BE Error Handling Flow ## 4.9 BE Error Handling Flow
```mermaid ![](./images/4.9.png)
flowchart TD
REQUEST["Request Handler"] --> TRY["try { ... }"]
TRY --> HANDLER["Business Logic"]
HANDLER --> SUCCESS{Success?}
SUCCESS -->|Yes| RETURN_OK["response.util.success(data)<br/>200 OK"]
SUCCESS -->|No| ERROR_CATCH["catch (error)"]
ERROR_CATCH --> ERROR_TYPE{Error Type?}
ERROR_TYPE -->|"AppError"| APP_ERR["Custom App Error<br/>status + message + code"]
ERROR_TYPE -->|"HTTPException"| HTTP_ERR["Hono HTTP Exception"]
ERROR_TYPE -->|"DrizzleQueryError"| DB_ERR["Database Query Error"]
ERROR_TYPE -->|"PostgresError"| PG_ERR["PostgreSQL Error<br/>(unique violation, fk, etc.)"]
ERROR_TYPE -->|"ZodError"| ZOD_ERR["Validation Error<br/>Input format invalid"]
ERROR_TYPE -->|"Unknown"| UNKNOWN_ERR["Unknown Error"]
APP_ERR --> MAP_STATUS["Map to HTTP Status"]
HTTP_ERR --> MAP_STATUS
DB_ERR --> LOG_ERR["Log Error<br/>console.error"]
PG_ERR --> LOG_ERR
ZOD_ERR --> MAP_400["400 Bad Request<br/>Validation error details"]
UNKNOWN_ERR --> LOG_ERR
LOG_ERR --> MAP_500["500 Internal Server Error"]
MAP_STATUS --> RESPONSE["Return Error Response<br/>response.util.error()"]
MAP_400 --> RESPONSE
MAP_500 --> RESPONSE
style REQUEST fill:#C8E6C9,stroke:#4CAF50
style ERROR_CATCH fill:#FFCDD2,stroke:#F44336
style RESPONSE fill:#BBDEFB,stroke:#2196F3
```
# ORTAX OMNICHANNEL APPLICATION
# COMPREHENSIVE FLOWCHART & ARCHITECTURE REPORT
---
| **Dokumen** | Laporan Analisis Arsitektur & Flowchart |
|---|---|
| **Versi Aplikasi** | Backend v0.5.7 · Frontend v0.4.6 |
| **Tanggal Laporan** | 21 April 2026 |
| **Disusun oleh** | Tim Analisis Arsitektur |
| **Status** | Final |
---
## DAFTAR ISI
| # | Dokumen | File | Deskripsi |
|---|---------|------|-----------|
| 1 | Executive Summary | `00-EXECUTIVE-SUMMARY.md` | Ringkasan eksekutif (file ini) |
| 2 | Arsitektur Umum Aplikasi | `01-OVERALL-ARCHITECTURE.md` | System architecture, user journey, navigasi |
| 3 | Integrasi FE-BE | `02-INTEGRATION-FLOW.md` | API mapping, real-time flow, database ERD |
| 4 | Frontend Flowcharts | `03-FE-FLOWCHARTS.md` | Routing, Auth, Chat, Campaign, Ticket, Settings |
| 5 | Backend Flowcharts | `04-BE-FLOWCHARTS.md` | Service architecture, Auth RBAC, Chat engine, Campaign engine, Ticket lifecycle |
| 6 | Ringkasan & Tabel | `05-SUMMARY-TABLES.md` | Role matrix, endpoint catalog, tech stack |
---
## RINGKASAN EKSEKUTIF
### Tentang Aplikasi
**Ortax Omnichannel** adalah platform komunikasi pelanggan multi-channel yang memungkinkan bisnis mengelola percakapan dari WhatsApp, Instagram, Telegram, dan channel lainnya dalam satu dashboard terpusat.
### 5 Fitur Utama
| # | Fitur | Deskripsi |
|---|-------|-----------|
| 1 | **Authentication (Auth)** | Login/register via Better-Auth, session management, RBAC (owner/admin/member), forgot/reset password |
| 2 | **Chat** | Real-time conversation management, multi-channel messaging (WhatsApp/Instagram/Telegram), message templates, file attachments, internal notes, conversation assignment & resolution |
| 3 | **Campaign** | WhatsApp marketing campaigns dengan template messages, recipient list management, variable personalization, delivery tracking |
| 4 | **Ticket** | Issue tracking linked to conversations/contacts, full lifecycle (TODO → IN_PROGRESS → RESOLVED → DONE), assignment, batch operations |
| 5 | **Settings** | User management, channel configuration, workspace management, office hours & auto-response, quick reply templates, API keys, tags, resolve categories, role & permission management |
### Technology Stack
| Layer | Technology |
|-------|-----------|
| **Frontend** | React 19, TypeScript 5.9, Vite 7, MUI v7, SWR, Axios |
| **Backend** | Hono v4, Bun runtime, Drizzle ORM, TypeScript |
| **Database** | PostgreSQL (schema: omnichannel, 30+ tables) |
| **Auth** | better-auth v1.3 |
| **Real-time** | Pusher + Socket.IO (hybrid) |
| **Message Queue** | RabbitMQ (webhook processing) |
| **Validation** | Zod (FE + BE) |
| **External APIs** | Meta WhatsApp Business API, Instagram Graph API, Telegram Bot API |
### Arsitektur Utama
- **Frontend**: React SPA dengan route guards (AuthGuard, WorkspaceGuard, GuestGuard, RouteRoleGuard)
- **Backend**: Hono HTTP server dengan Repository-Service-Route pattern (47 service modules)
- **Real-time**: Hybrid Pusher + Socket.IO untuk notifikasi real-time
- **Multi-tenancy**: Workspace-based dengan RBAC permission system
- **Webhook Queue**: RabbitMQ untuk distributed webhook processing
- **Soft Delete**: Semua entity menggunakan soft delete pattern
### Statistik Kode
| Metrik | FE | BE |
|--------|-----|-----|
| Total Service Modules | - | 47 modules |
| Chat Section Files | 82 files | ~8 services |
| Campaign Section Files | 33 files | ~5 services |
| Ticket Section Files | 25 files | ~3 services |
| API Endpoints | ~80+ action functions | ~60+ route endpoints |
| Database Tables | - | 30+ tables |
| Real-time Channels | 3 (Pusher/Socket.IO/WS) | 3 (Pusher/Socket.IO/RabbitMQ) |
---
> **Catatan**: Semua diagram flowchart menggunakan sintaks **Mermaid** yang dapat dirender di GitHub, GitLab, Notion, VS Code (dengan extension), atau [Mermaid Live Editor](https://mermaid.live).
# BAGIAN 1: ARSITEKTUR UMUM APLIKASI
## (Overall Application Architecture)
---
## 1.1 System Architecture Overview
Diagram ini menunjukkan arsitektur keseluruhan sistem Ortax Omnichannel dari perspektif high-level, mencakup semua layer dari pengguna hingga database.
```mermaid
flowchart TB
subgraph USERS["PENGGUNA"]
AGENT["Agent / Staff<br/>Role: member"]
ADMIN["Admin / Owner<br/>Role: admin, owner"]
CUSTOMER["Customer<br/>External User"]
end
subgraph CHANNELS["EXTERNAL CHANNELS<br/>(Platform API)"]
WA["WhatsApp Business API<br/>(Meta Graph API)"]
IG["Instagram Graph API<br/>(Meta Graph API)"]
TG["Telegram Bot API<br/>(Telegram)"]
FB["Facebook Messenger<br/>(Meta Graph API)"]
TT["TikTok<br/>(Planned)"]
end
subgraph FE["FRONTEND APPLICATION<br/>React 19 + Vite + MUI v7"]
direction TB
FE_AUTH["Auth Module<br/>sign-in, sign-up, forgot/reset password"]
FE_CHAT["Chat Module<br/>82 files - conversations, messages, real-time"]
FE_CAMP["Campaign Module<br/>33 files - templates, recipients, execution"]
FE_TICKET["Ticket Module<br/>25 files - CRUD, lifecycle, attachments"]
FE_SETTINGS["Settings Module<br/>20+ pages - admin panels"]
end
subgraph BE["BACKEND APPLICATION<br/>Hono + Drizzle ORM + Bun"]
direction TB
BE_API["API Gateway<br/>/api/v1"]
BE_AUTH_ENGINE["Better-Auth Engine<br/>session, RBAC, API keys"]
BE_SERVICES["47 Service Modules<br/>business logic layer"]
BE_WEBHOOK["Webhook Receivers<br/>WhatsApp, Instagram, Telegram"]
BE_QUEUE["RabbitMQ Worker<br/>distributed processing"]
end
subgraph REALTIME["REAL-TIME LAYER"]
PUSHER["Pusher<br/>private-room, private-channel, private-post"]
SOCKET["Socket.IO<br/>new-message, new-conversation"]
end
subgraph DB_LAYER["DATABASE & STORAGE"]
PG["PostgreSQL<br/>schema: omnichannel<br/>30+ tables"]
UPLOAD["File Storage<br/>Local / S3 / GCS / Azure"]
end
USERS -->|"Browser<br/>HTTPS"| FE
CUSTOMER -->|"Send Message"| CHANNELS
CHANNELS -->|"Webhook POST"| BE_WEBHOOK
FE -->|"REST API<br/>/api/v1/*"| BE_API
BE_API --> BE_AUTH_ENGINE
BE_API --> BE_SERVICES
BE_SERVICES --> PG
BE_SERVICES --> UPLOAD
BE_WEBHOOK --> BE_QUEUE
BE_QUEUE --> BE_SERVICES
BE_SERVICES -->|"Trigger Events"| REALTIME
REALTIME -->|"Push to Client"| FE
CHANNELS -->|"API Response"| BE_SERVICES
style USERS fill:#E8F5E9,stroke:#4CAF50,color:#1B5E20
style CHANNELS fill:#FFF3E0,stroke:#FF9800,color:#E65100
style FE fill:#E3F2FD,stroke:#2196F3,color:#0D47A1
style BE fill:#FCE4EC,stroke:#E91E63,color:#880E4F
style REALTIME fill:#F3E5F5,stroke:#9C27B0,color:#4A148C
style DB_LAYER fill:#E0F7FA,stroke:#00BCD4,color:#006064
```
---
## 1.2 User Journey Flowchart (Alur Pengguna Umum)
Diagram ini menunjukkan alur perjalanan pengguna dari pertama kali membuka aplikasi hingga menggunakan fitur-fitur utama.
```mermaid
flowchart TD
START([User Opens App]) --> HAS_SESSION{Has Active<br/>Session Cookie?}
HAS_SESSION -->|"Yes"| AUTH_CHECK{Session Valid?<br/>better-auth verify}
HAS_SESSION -->|"No"| LOGIN["Login Page<br/>/auth/sign-in"]
AUTH_CHECK -->|"Valid"| HAS_WORKSPACE{Has Active<br/>Workspace?}
AUTH_CHECK -->|"Invalid / Expired"| LOGIN
LOGIN --> SIGNIN_PAGE["Sign-In Page<br/>(BetterAuth.SignInPage)"]
SIGNIN_PAGE --> INPUT_CREDS["Input Email & Password"]
INPUT_CREDS --> SUBMIT["POST /api/v1/auth/sign-in<br/>better-auth client"]
SUBMIT --> VALID{Credentials Valid?}
VALID -->|"No"| SHOW_ERR["Show Error Message<br/>'Invalid email or password'"]
SHOW_ERR --> SIGNIN_PAGE
VALID -->|"Yes"| SET_COOKIE["Session Cookie Set<br/>User Context Loaded"]
SET_COOKIE --> HAS_WORKSPACE
HAS_WORKSPACE -->|"No"| CREATE_WS["Create Workspace<br/>/dashboard/workspace/create"]
HAS_WORKSPACE -->|"Yes"| DASHBOARD["Dashboard<br/>Redirect to /dashboard/chat"]
CREATE_WS --> WS_FORM["Workspace Creation Form<br/>Input: workspace name"]
WS_FORM --> WS_API["authClient.organization.create()<br/>+ setActive(organization)"]
WS_API --> DASHBOARD
DASHBOARD --> SIDEBAR{{"Sidebar Navigation<br/>MainLayout"}}
SIDEBAR -->|"Chat"| CHAT["/dashboard/chat<br/>Real-time Conversations"]
SIDEBAR -->|"Social Media"| SOCIAL["/dashboard/social-media<br/>Instagram Posts"]
SIDEBAR -->|"Campaign"| CAMP["/dashboard/campaign<br/>Marketing Campaigns"]
SIDEBAR -->|"Contact"| CONTACT["/dashboard/contact<br/>Contact Management"]
SIDEBAR -->|"Tickets"| TICKET["/dashboard/ticket<br/>Issue Tracking"]
SIDEBAR -->|"Settings"| SETTINGS["/dashboard/settings<br/>Configuration Panel"]
CHAT --> CHAT_FLOW["Chat Flow<br/>(see FE Chat Detail)"]
CAMP --> CAMP_FLOW["Campaign Flow<br/>(see FE Campaign Detail)"]
TICKET --> TICKET_FLOW["Ticket Flow<br/>(see FE Ticket Detail)"]
SETTINGS --> SETTINGS_FLOW["Settings Flow<br/>(see FE Settings Detail)"]
style START fill:#C8E6C9,stroke:#4CAF50
style DASHBOARD fill:#BBDEFB,stroke:#2196F3
style SHOW_ERR fill:#FFCDD2,stroke:#F44336
```
---
## 1.3 Application Navigation Structure
Struktur navigasi lengkap yang tersedia di sidebar dashboard.
```mermaid
flowchart LR
subgraph NAV["Dashboard Navigation (Sidebar)"]
direction TB
NAV_MGMT["Management"]
NAV_CHAT["Chat<br/>/dashboard/chat"]
NAV_SOCIAL["Sosial Media<br/>/dashboard/social-media"]
NAV_CAMP["Campaign<br/>/dashboard/campaign"]
NAV_CONTACT["Contact<br/>/dashboard/contact"]
NAV_TICKET["Tickets<br/>/dashboard/ticket"]
NAV_SETTINGS["Settings ▸<br/>/dashboard/settings"]
end
NAV_MGMT --> NAV_CHAT
NAV_MGMT --> NAV_SOCIAL
NAV_MGMT --> NAV_CAMP
NAV_MGMT --> NAV_CONTACT
NAV_MGMT --> NAV_TICKET
NAV_MGMT --> NAV_SETTINGS
NAV_SETTINGS --> SET_TAG["Tags<br/>[admin, owner]"]
NAV_SETTINGS --> SET_USER["Users<br/>[admin, owner]"]
NAV_SETTINGS --> SET_CH["Channels<br/>[admin, owner]"]
NAV_SETTINGS --> SET_WS["Workspaces<br/>[owner only]"]
NAV_SETTINGS --> SET_ACC["My Account<br/>[all roles]"]
NAV_SETTINGS --> SET_OH["Office Hour & Auto Response<br/>[admin, owner]"]
NAV_SETTINGS --> SET_QR["Quick Reply<br/>[admin, owner]"]
NAV_SETTINGS --> SET_API["API Keys<br/>[admin, owner]"]
NAV_SETTINGS --> SET_RC["Category Resolve<br/>[admin, owner]"]
style NAV fill:#F5F5F5,stroke:#9E9E9E
style NAV_SETTINGS fill:#E8EAF6,stroke:#3F51B5
```
---
## 1.4 High-Level Data Flow (Aliran Data Keseluruhan)
```mermaid
flowchart LR
subgraph INPUT["Input Sources"]
USER_ACTION["User Actions<br/>(Click, Type, Submit)"]
WEBHOOK_IN["Channel Webhooks<br/>(WA, IG, TG)"]
end
subgraph PROCESSING["Processing Layer"]
FE_REACT["React App<br/>State Management (SWR)"]
BE_HONO["Hono Server<br/>Route → Service → Repository"]
MQ["RabbitMQ<br/>Work Queue"]
end
subgraph STORAGE["Storage Layer"]
PG["PostgreSQL<br/>30+ Tables"]
FILES["File Storage<br/>Uploads"]
CACHE["SWR Cache<br/>Client-side"]
end
subgraph OUTPUT["Output Channels"]
UI["UI Rendering<br/>React Components"]
PUSH_OUT["Pusher + Socket.IO<br/>Real-time Events"]
PLATFORM_OUT["Platform APIs<br/>Send Messages"]
EMAIL_OUT["Email<br/>Nodemailer"]
end
USER_ACTION --> FE_REACT
WEBHOOK_IN --> BE_HONO
WEBHOOK_IN --> MQ
MQ --> BE_HONO
FE_REACT -->|"HTTP Request"| BE_HONO
BE_HONO --> PG
BE_HONO --> FILES
FE_REACT --> CACHE
BE_HONO --> PUSH_OUT
BE_HONO --> PLATFORM_OUT
BE_HONO --> EMAIL_OUT
PUSH_OUT --> FE_REACT
FE_REACT --> UI
style INPUT fill:#E8F5E9,stroke:#4CAF50
style PROCESSING fill:#E3F2FD,stroke:#2196F3
style STORAGE fill:#FFF3E0,stroke:#FF9800
style OUTPUT fill:#F3E5F5,stroke:#9C27B0
```
---
## 1.5 Multi-Channel Message Flow
```mermaid
flowchart TD
subgraph INCOMING["Pesan Masuk"]
direction LR
C1["WhatsApp Customer"]
C2["Instagram User"]
C3["Telegram User"]
end
subgraph WEBHOOKS["Webhook Processing"]
WH_WA["/api/webhook/whatsapp"]
WH_IG["/api/webhook/instagram"]
WH_TG["/api/webhook/telegram"]
end
subgraph QUEUE["Message Queue"]
RABBIT["RabbitMQ<br/>omnichannel_work_queue"]
end
subgraph BACKEND["Backend Processing"]
PARSE["Parse & Normalize Message"]
FIND_CONTACT["Find or Create Contact"]
FIND_CONV["Find or Create Conversation"]
SAVE_MSG["Save to conversation_messages"]
CHECK_OH{"Within<br/>Office Hours?"}
AUTO_RESP["Send Auto Response<br/>(if configured)"]
NOTIFY["Pusher + Socket.IO<br/>Notify Agents"]
end
subgraph AGENT_UI["Agent Interface"]
FE_RECEIVE["Real-time Update<br/>via Pusher/Socket.IO"]
SWR_UPDATE["SWR Revalidation<br/>Update Conversation List"]
SHOW_MSG["Display New Message<br/>in Chat UI"]
end
C1 -->|"Message"| WH_WA
C2 -->|"DM/Comment"| WH_IG
C3 -->|"Message"| WH_TG
WH_WA -->|"Publish"| RABBIT
WH_IG -->|"Direct"| PARSE
WH_TG -->|"Direct"| PARSE
RABBIT -->|"Consume"| PARSE
PARSE --> FIND_CONTACT --> FIND_CONV --> SAVE_MSG
SAVE_MSG --> CHECK_OH
CHECK_OH -->|"No"| AUTO_RESP
CHECK_OH -->|"Yes"| NOTIFY
AUTO_RESP --> NOTIFY
NOTIFY --> FE_RECEIVE --> SWR_UPDATE --> SHOW_MSG
style INCOMING fill:#E8F5E9,stroke:#4CAF50
style WEBHOOKS fill:#FFF3E0,stroke:#FF9800
style QUEUE fill:#FCE4EC,stroke:#E91E63
style BACKEND fill:#E3F2FD,stroke:#2196F3
style AGENT_UI fill:#F3E5F5,stroke:#9C27B0
```
# BAGIAN 2: INTEGRASI FE-BE
## (Frontend-Backend Integration Flow)
---
## 2.1 API Integration Map — 5 Fitur Utama
Diagram ini menunjukkan mapping lengkap antara Frontend modules, API endpoints, dan Backend services untuk setiap fitur utama.
```mermaid
flowchart LR
subgraph FE["FRONTEND (React 19)"]
direction TB
FE1["🔐 Auth Pages<br/>sign-in, sign-up,<br/>forgot/reset password"]
FE2["💬 Chat Pages<br/>82 files<br/>conversations, messages"]
FE3["📢 Campaign Pages<br/>33 files<br/>templates, recipients"]
FE4["🎫 Ticket Pages<br/>25 files<br/>CRUD, lifecycle"]
FE5["⚙️ Settings Pages<br/>20+ pages<br/>admin configuration"]
end
subgraph API["API LAYER (Hono /api/v1)"]
direction TB
API1["/auth/*<br/>sign-in, sign-up,<br/>forget-password,<br/>change-password,<br/>admin/*, organization/*"]
API2["/u/conversations<br/>/u/conversation-messages<br/>/u/channels<br/>/u/contacts<br/>/u/notes"]
API3["/u/campaigns<br/>/u/campaign-recipient-lists<br/>/u/campaign-recipient-list-items<br/>/u/templates"]
API4["/u/tickets<br/>/u/ticket-history"]
API5["/u/workspaces<br/>/u/workspace-members<br/>/u/workspace-api-keys<br/>/u/channels<br/>/u/tags<br/>/u/user<br/>/u/role-permissions<br/>/u/message-templates<br/>/u/office-hour-*<br/>/u/conversation-resolve-category"]
end
subgraph BE["BACKEND SERVICES"]
direction TB
BS1["better-auth<br/>session, RBAC,<br/>API keys"]
BS2["conversation<br/>conversation-message<br/>channel<br/>contact<br/>note<br/>tag"]
BS3["campaign<br/>campaign-recipient-list<br/>template<br/>whatsapp-template"]
BS4["ticket<br/>ticket-history<br/>ticket-attachment"]
BS5["workspace<br/>workspace-members<br/>workspace-api-key<br/>role-permission<br/>channel<br/>tag<br/>message-template<br/>office-hour-schedule<br/>office-hour-response<br/>conv-resolve-category"]
end
FE1 -->|"POST /auth/sign-in<br/>POST /auth/forget-password<br/>POST /auth/change-password<br/>POST /auth/admin/*<br/>POST /auth/organization/*"| API1
FE2 -->|"GET /conversations<br/>POST /conversation-messages<br/>PATCH /conversations/:id/status<br/>POST /conversations/:id/read<br/>GET /conversation-messages<br/>POST /conversation-messages/template<br/>POST /conversation-messages/upload-media"| API2
FE3 -->|"CRUD /campaigns<br/>POST /campaigns/:id/execute<br/>CRUD /templates<br/>POST /templates/:id/sync<br/>CRUD /campaign-recipient-lists<br/>POST /templates/upload-media"| API3
FE4 -->|"CRUD /tickets<br/>GET /ticket-history/ticket/:id<br/>POST /tickets/upload-media<br/>DELETE /tickets/remove-media/:id"| API4
FE5 -->|"CRUD /workspaces<br/>CRUD /workspace-members<br/>CRUD /channels<br/>CRUD /tags<br/>PUT /user/update-user<br/>POST /auth/change-password<br/>CRUD /role-permissions<br/>CRUD /message-templates<br/>CRUD /office-hour-*<br/>CRUD /conversation-resolve-category<br/>CRUD /workspace-api-keys"| API5
API1 --> BS1
API2 --> BS2
API3 --> BS3
API4 --> BS4
API5 --> BS5
style FE fill:#E3F2FD,stroke:#1976D2
style API fill:#FFF8E1,stroke:#F9A825
style BE fill:#FCE4EC,stroke:#C2185B
```
---
## 2.2 Real-Time Integration Flow
Alur komunikasi real-time dari webhook masuk hingga update UI di browser agent.
```mermaid
flowchart TD
subgraph EXTERNAL["EXTERNAL CHANNELS"]
WA_MSG["📱 WhatsApp<br/>Message from Customer"]
IG_MSG["📸 Instagram<br/>DM / Comment"]
TG_MSG["✈️ Telegram<br/>Message from User"]
end
subgraph BE_RECEIVE["BACKEND - Webhook Receivers"]
WH_WA["POST /api/webhook/whatsapp"]
WH_IG["POST /api/webhook/instagram"]
WH_TG["POST /api/webhook/telegram"]
end
subgraph BE_PROCESS["BACKEND - Processing"]
RABBIT["RabbitMQ Queue<br/>omnichannel_work_queue"]
PROCESSOR["Message Processor Service"]
DB_WRITE["PostgreSQL<br/>INSERT conversations<br/>INSERT conversation_messages"]
PUSHER_S["Pusher Server<br/>Trigger Events"]
SOCKET_S["Socket.IO Server<br/>Emit Events"]
end
subgraph FE_RECEIVE["FRONTEND - Real-time Clients"]
PUSHER_C["Pusher Client<br/>use-pusher-chat.ts<br/>Subscribe: private-room:*<br/>private-channel:*"]
SOCKET_C["Socket.IO Client<br/>use-socket-io.ts<br/>Listen: new-message<br/>new-conversation"]
SWR_CACHE["SWR Cache<br/>Automatic Revalidation<br/>mutate() on events"]
UI["UI Components<br/>ChatNav, ChatMessageList<br/>Auto Update"]
end
WA_MSG -->|"HTTP POST"| WH_WA
IG_MSG -->|"HTTP POST"| WH_IG
TG_MSG -->|"HTTP POST"| WH_TG
WH_WA -->|"Publish to Queue"| RABBIT
WH_IG -->|"Direct Process"| PROCESSOR
WH_TG -->|"Direct Process"| PROCESSOR
RABBIT -->|"Consume"| PROCESSOR
PROCESSOR --> DB_WRITE
PROCESSOR --> PUSHER_S
PROCESSOR --> SOCKET_S
PUSHER_S -->|"private-room:{convId}<br/>Event: new-message"| PUSHER_C
PUSHER_S -->|"private-channel:{chId}<br/>Event: new-conversation"| PUSHER_C
SOCKET_S -->|"Event: new-message"| SOCKET_C
SOCKET_S -->|"Event: conversation-updated"| SOCKET_C
PUSHER_C --> SWR_CACHE
SOCKET_C --> SWR_CACHE
SWR_CACHE --> UI
style EXTERNAL fill:#E8F5E9,stroke:#4CAF50
style BE_RECEIVE fill:#FFF3E0,stroke:#FF9800
style BE_PROCESS fill:#FCE4EC,stroke:#E91E63
style FE_RECEIVE fill:#E3F2FD,stroke:#2196F3
```
---
## 2.3 Real-Time Event Types
Tabel event real-time yang digunakan dalam sistem.
```mermaid
flowchart TD
subgraph EVENTS["Real-Time Events"]
direction TB
E1["new-message<br/>Pesan baru masuk/terkirim"]
E2["new-conversation<br/>Conversation baru dibuat"]
E3["message-updated<br/>Status pesan berubah<br/>(SENT → DELIVERED → READ)"]
E4["conversation-updated<br/>Status/assignee conversation berubah"]
E5["new-post-comment<br/>Komentar baru di Instagram post"]
E6["post-updated<br/>Instagram post diupdate"]
end
subgraph CHANNELS["Pusher Channels"]
C1["private-room:{conversationId}<br/>Event: new-message, message-updated"]
C2["private-channel:{channelId}<br/>Event: new-conversation, conversation-updated"]
C3["private-post:{postId}<br/>Event: new-post-comment, post-updated"]
end
E1 --> C1
E2 --> C2
E3 --> C1
E4 --> C2
E5 --> C3
E6 --> C3
style EVENTS fill:#F3E5F5,stroke:#9C27B0
style CHANNELS fill:#E8EAF6,stroke:#3F51B5
```
---
## 2.4 Authentication Integration Flow
Alur integrasi autentikasi antara FE dan BE.
```mermaid
flowchart TD
subgraph FE_AUTH["FRONTEND - Auth Context"]
BA_CLIENT["better-auth client<br/>/src/lib/better-auth.ts<br/>Base URL: /api/v1/auth"]
AUTH_PROVIDER["AuthProvider<br/>/src/auth/context/better-auth/"]
AUTH_GUARD["AuthGuard<br/>GuestGuard<br/>WorkspaceGuard<br/>RouteRoleGuard"]
end
subgraph API_AUTH["API ENDPOINTS"]
SIGN_IN["POST /api/v1/auth/sign-in/email-password"]
SIGN_UP["POST /api/v1/auth/sign-up/email-password"]
FORGOT["POST /api/v1/auth/forget-password"]
RESET["POST /api/v1/auth/reset-password"]
CHANGE["POST /api/v1/auth/change-password"]
SESSION["GET /api/v1/auth/get-session"]
ADMIN_API["POST /api/v1/auth/admin/*"]
ORG_API["POST /api/v1/auth/organization/*"]
end
subgraph BE_AUTH["BACKEND - better-auth Engine"]
BA_HANDLER["better-auth handler<br/>/src/services/better-auth/"]
SESSION_MW["requireAuth Middleware<br/>Session verification"]
PERM_MW["requirePermission Middleware<br/>RBAC check"]
APIKEY_MW["requireWorkspaceApiKey Middleware<br/>x-api-key verification"]
end
BA_CLIENT -->|"HTTP Request"| API_AUTH
AUTH_PROVIDER --> BA_CLIENT
AUTH_GUARD --> AUTH_PROVIDER
API_AUTH --> BA_HANDLER
BA_HANDLER -->|"Set Session Cookie"| BA_CLIENT
SESSION_MW -->|"Verify Session"| BA_HANDLER
PERM_MW -->|"Check Role + Permission"| BA_HANDLER
APIKEY_MW -->|"Verify API Key"| BA_HANDLER
style FE_AUTH fill:#E3F2FD,stroke:#1976D2
style API_AUTH fill:#FFF8E1,stroke:#F9A825
style BE_AUTH fill:#FCE4EC,stroke:#C2185B
```
---
## 2.5 File Upload Integration Flow
```mermaid
flowchart TD
FE_UPLOAD["Frontend<br/>File Upload Component<br/>(multipart/form-data)"] --> ROUTE_CHECK{Upload Type?}
ROUTE_CHECK -->|"Chat Media"| CHAT_API["POST /api/v1/u/conversation-messages/upload-media<br/>body: {file, mediaType, conversationId}"]
ROUTE_CHECK -->|"Ticket Media"| TICKET_API["POST /api/v1/u/tickets/upload-media<br/>body: {file, ticketId?}"]
ROUTE_CHECK -->|"Template Media"| TEMPL_API["POST /api/v1/u/templates/upload-media<br/>body: {file, mediaType, channelId}<br/>(Meta Resumable API)"]
ROUTE_CHECK -->|"Channel Icon"| ICON_API["POST /api/v1/u/channels/:id/upload-icon<br/>body: {file}"]
ROUTE_CHECK -->|"Profile Photo"| PROF_API["PUT /api/v1/u/user/update-user<br/>body: {photo}"]
CHAT_API --> FILE_SERVICE["File Storage Service"]
TICKET_API --> FILE_SERVICE
TEMPL_API --> META_API["Meta Graph API<br/>Resumable Upload"]
ICON_API --> FILE_SERVICE
PROF_API --> FILE_SERVICE
FILE_SERVICE --> STORAGE_CHECK{Storage Type?}
STORAGE_CHECK -->|"LOCAL"| LOCAL["Local Filesystem<br/>/uploads/*"]
STORAGE_CHECK -->|"S3"| S3["AWS S3"]
STORAGE_CHECK -->|"GCS"| GCS["Google Cloud Storage"]
STORAGE_CHECK -->|"AZURE"| AZURE["Azure Blob Storage"]
META_API --> META_STORE["Meta Servers"]
style FE_UPLOAD fill:#E3F2FD,stroke:#1976D2
style STORAGE_CHECK fill:#FFF8E1,stroke:#F9A825
```
---
## 2.6 Middleware Stack (Request Lifecycle)
Diagram ini menunjukkan urutan middleware yang dilalui setiap request dari FE ke BE.
```mermaid
flowchart TD
REQUEST["Incoming HTTP Request"] --> CORS["① CORS Middleware<br/>cors.config.ts<br/>Allowed origins validation"]
CORS --> TIMING["② Timing Middleware<br/>Response time header"]
TIMING --> REQ_ID["③ Request ID<br/>Unique request identifier"]
REQ_ID --> LOGGER["④ Logger Middleware<br/>Request logging"]
LOGGER --> BODY_LIMIT["⑤ Body Limit Middleware<br/>5MB default<br/>100MB WhatsApp uploads"]
BODY_LIMIT --> TIMEOUT["⑥ Timeout Middleware<br/>5min default<br/>15min uploads"]
TIMEOUT --> ROUTE_MATCH{Route Match?}
ROUTE_MATCH -->|"/api/v1/auth/*"| AUTH_ROUTE["better-auth handler<br/>(self-handled)"]
ROUTE_MATCH -->|"/api/webhook/*"| WEBHOOK_ROUTE["Webhook Routes<br/>(WhatsApp CORS only)"]
ROUTE_MATCH -->|"/api/v1/u/*"| PROTECTED["Protected Route Chain"]
ROUTE_MATCH -->|"/uploads/*"| UPLOAD_ROUTE["File Storage Route<br/>(+ upload timeout)"]
ROUTE_MATCH -->|"/api/v1/swagger"| SWAGGER["Swagger UI"]
PROTECTED --> AUTH_MW["⑦ requireAuth<br/>Session verification"]
AUTH_MW --> PERM_MW["⑧ requirePermission<br/>RBAC: resource:action"]
PERM_MW --> HANDLER["Route Handler<br/>→ Service → Repository → DB"]
AUTH_ROUTE --> HANDLER2["better-auth internal handler"]
WEBHOOK_ROUTE --> HANDLER3["Webhook processor"]
style REQUEST fill:#C8E6C9,stroke:#4CAF50
style HANDLER fill:#BBDEFB,stroke:#2196F3
style HANDLER2 fill:#BBDEFB,stroke:#2196F3
style HANDLER3 fill:#BBDEFB,stroke:#2196F3
```
# BAGIAN 3: FLOWCHART FRONTEND (FE)
## Detailed Frontend Application Flowcharts
---
## 3.1 FE General Routing Architecture
Diagram lengkap struktur routing Frontend dari root hingga setiap halaman.
```mermaid
flowchart TD
ROOT["/ (Root Route)<br/>src/app.tsx"] --> MAIN_LAYOUT["MainLayout<br/>(Public Pages)"]
ROOT --> AUTH_LAYOUT["AuthSplitLayout<br/>(Auth Pages)"]
ROOT --> SIMPLE_LAYOUT["SimpleLayout<br/>(Minimal Pages)"]
ROOT --> DASH_LAYOUT["DashboardLayout<br/>(Protected Pages)"]
subgraph PUBLIC["Public Routes"]
MAIN_LAYOUT --> HOME["/ — HomePage"]
MAIN_LAYOUT --> ABOUT["/about-us"]
MAIN_LAYOUT --> PRIVACY["/privacy-policy"]
MAIN_LAYOUT --> TERMS["/terms-of-service"]
SIMPLE_LAYOUT --> MAINT["/maintenance"]
end
subgraph AUTH_ROUTES["Auth Routes (GuestGuard)"]
AUTH_LAYOUT --> GUEST["GuestGuard<br/>(Block if logged in)"]
GUEST --> SIGNIN["/auth/sign-in<br/>BetterAuth.SignInPage"]
GUEST --> FORGOT["/auth/forgot-password<br/>BetterAuth.ForgotPasswordPage"]
GUEST --> RESET["/auth/reset-password<br/>BetterAuth.ResetPasswordPage"]
GUEST --> SIGNUP["/auth/sign-up<br/>(Hidden - Invite Only)"]
GUEST --> ACCEPT_INV["/accept-invitation/:id<br/>(+ ReCAPTCHA)"]
end
subgraph ERROR_ROUTES["Error Routes"]
ROOT --> E403["/error/403 — Forbidden"]
ROOT --> E404["/error/404 — Not Found"]
ROOT --> E500["/error/500 — Server Error"]
end
subgraph DASH["Dashboard Routes (AuthGuard + WorkspaceGuard)"]
DASH_LAYOUT --> AUTH_G["AuthGuard<br/>Check Session"]
AUTH_G --> WS_G["WorkspaceGuard<br/>Check Active Workspace"]
WS_G --> DASH_NAV{{"Dashboard Navigation"}}
DASH_NAV --> CHAT_R["/dashboard/chat"]
DASH_NAV --> SOCIAL["/dashboard/social-media"]
DASH_NAV --> WS_CREATE["/dashboard/workspace/create"]
DASH_NAV --> TAG_NAV["/dashboard/tag<br/>/new /:id/edit"]
DASH_NAV --> CONTACT["/dashboard/contact"]
DASH_NAV --> CAMP_NAV["/dashboard/campaign/*"]
DASH_NAV --> TICK_NAV["/dashboard/ticket/*"]
DASH_NAV --> SET_NAV["/dashboard/settings/*"]
end
style ROOT fill:#C8E6C9,stroke:#4CAF50
style DASH fill:#E3F2FD,stroke:#1976D2
style AUTH_ROUTES fill:#FFF3E0,stroke:#FF9800
style PUBLIC fill:#F3E5F5,stroke:#9C27B0
```
---
## 3.2 FE Auth Flow (Detailed)
Alur autentikasi lengkap dari halaman login hingga masuk dashboard.
```mermaid
flowchart TD
START([User Opens App]) --> VISIT{"Current URL?"}
VISIT -->|"/auth/*"| GUEST_CHECK{GuestGuard<br/>Is Logged In?}
VISIT -->|"/dashboard/*"| AUTH_CHECK{AuthGuard<br/>Has Session?}
VISIT -->|Other| PUBLIC_PAGE["Show Public Page"]
GUEST_CHECK -->|Yes| REDIRECT_DASH["Redirect to<br/>/dashboard/chat"]
GUEST_CHECK -->|No| AUTH_PAGE["Show Auth Page"]
AUTH_CHECK -->|No| REDIRECT_LOGIN["Redirect to<br/>/auth/sign-in"]
AUTH_CHECK -->|Yes| WS_CHECK{WorkspaceGuard<br/>Has Workspace?}
WS_CHECK -->|No| REDIRECT_WS["Redirect to<br/>/dashboard/workspace/create"]
WS_CHECK -->|Yes| ROLE_CHECK{RouteRoleGuard<br/>Has Permission?}
ROLE_CHECK -->|No| REDIRECT_403["Redirect to<br/>/error/403"]
ROLE_CHECK -->|Yes| SHOW_PAGE["Show Dashboard Page"]
subgraph SIGNIN_FLOW["Sign-In Process"]
AUTH_PAGE --> SIGNIN["BetterAuth.SignInPage"]
SIGNIN --> INPUT["Input Email + Password"]
INPUT --> CLICK["Click Sign In"]
CLICK --> API_CALL["authClient.signIn.email()<br/>POST /api/v1/auth/sign-in/email-password"]
API_CALL --> SUCCESS{Success?}
SUCCESS -->|No| ERR_MSG["Show Error<br/>'Invalid credentials'"]
SUCCESS -->|Yes| SESSION_SET["Session Cookie Set<br/>AuthProvider Context Updated"]
SESSION_SET --> REDIRECT_DASH
ERR_MSG --> INPUT
end
subgraph INVITE_FLOW["Invitation Flow"]
ACCEPT_INV["/accept-invitation/:id"] --> CAPTCHA["ReCAPTCHA Verification"]
CAPTCHA --> VERIFY{Captcha Valid?}
VERIFY -->|No| CAPTCHA
VERIFY -->|Yes| ACCEPT_API["authClient.organization<br/>.acceptInvitation(id)"]
ACCEPT_API --> SUCCESS_INV{Success?}
SUCCESS_INV -->|No| INV_ERR["Show Error"]
SUCCESS_INV -->|Yes| SET_ACTIVE["SetActive Organization"]
SET_ACTIVE --> REDIRECT_DASH
INV_ERR --> CAPTCHA
end
style START fill:#C8E6C9,stroke:#4CAF50
style REDIRECT_LOGIN fill:#FFCDD2,stroke:#F44336
style REDIRECT_403 fill:#FFCDD2,stroke:#F44336
style SHOW_PAGE fill:#BBDEFB,stroke:#2196F3
style SIGNIN fill:#FFF8E1,stroke:#F9A825
```
---
## 3.3 FE Chat Module Flow (Detailed)
Modul Chat adalah modul terbesar (82 files). Diagram ini menunjukkan alur lengkap.
> **Catatan Penting (Kondisi Aktual):**
> - **Conversations**: Di-fetch **Full GET**. BE route menerima parameter `page`/`limit` dan memanggil `calculatePagination()`, namun repository **tidak menggunakan** `.limit()`/`.offset()` di query DB. Semua conversations di-return sekaligus.
> - **Messages**: Di-fetch **Full GET**. Parameter `page`/`limit` di route, service, dan repository BE **sudah di-comment out**. Response mengembalikan `page:0, limit:0, totalPages:0` (dummy). Semua messages dalam conversation chain di-return sekaligus.
> - **Infinite Scroll**: Hook `use-message-list-infinite-scroll.ts` dan `use-message-list-scroll-persistence.ts` masih di-import di `chat-message-list.tsx`, tapi **tidak pernah ter-trigger** karena `hasMore` selalu `false` (karena response pagination dummy). Hook `use-infinite-scroll.ts` dan `use-messages-scroll.ts` adalah **dead code** (tidak di-import di mana pun).
> - **Auto-Scroll**: Hook `use-message-list-initial-load.ts` tetap aktif untuk auto-scroll to bottom saat initial load.
```mermaid
flowchart TD
CHAT_PAGE["Chat Page<br/>/dashboard/chat"] --> INIT["Initialize ChatContext"]
INIT --> LOAD_CHANNELS["Load Channels<br/>GET /channels/with-unread-count"]
INIT --> LOAD_COUNTS["Load Chat Counts<br/>GET /conversations/total-counts-chat"]
INIT --> CONNECT_RT["Connect Real-time<br/>Pusher + Socket.IO"]
LOAD_CHANNELS --> RENDER_LAYOUT["Render ChatLayout<br/>(3-Column Layout)"]
LOAD_COUNTS --> RENDER_LAYOUT
CONNECT_RT --> RENDER_LAYOUT
subgraph LEFT_PANEL["LEFT PANEL — ChatNav"]
RENDER_LAYOUT --> CH_NAV["chat-nav.tsx"]
CH_NAV --> CH_FOOTER["chat-channel-list-footer.tsx<br/>Channel filter tabs"]
CH_FOOTER --> CH_SEARCH["chat-nav-search-results.tsx<br/>Search conversations"]
CH_SEARCH --> CH_ITEM["chat-nav-item.tsx<br/>Conversation list item"]
CH_ITEM --> CH_LIST["Conversation List (Full GET)<br/>use-conversation-pagination.ts<br/>⚠️ BE route terima page/limit<br/>tapi repository tidak pakai"]
end
subgraph FILTERS["Conversation Filters"]
FILTER_GROUP["Filter Options:"] --> F1["Channel Filter<br/>(WhatsApp, Instagram, etc.)"]
FILTER_GROUP --> F2["Status Filter<br/>All / My / Unassigned / Assigned / Resolved"]
FILTER_GROUP --> F3["Tag Filter"]
FILTER_GROUP --> F4["Date Range Filter"]
FILTER_GROUP --> F5["24h Window Filter<br/>(WhatsApp only)"]
FILTER_GROUP --> F6["First Response Filter"]
end
CH_LIST --> FILTERS
subgraph CENTER_PANEL["CENTER PANEL — ChatRoom"]
CH_LIST -->|"Select Conversation"| LOAD_MSG["Load All Messages (Full GET)<br/>GET /conversation-messages<br/>use-message-list-initial-load.ts<br/>⚠️ BE limit/offset di-comment out<br/>→ returns all messages"]
LOAD_MSG --> MSG_LIST["chat-message-list.tsx<br/>Message list<br/>(auto-scroll to bottom on load)"]
MSG_LIST --> MSG_ITEM["chat-message-item.tsx<br/>Individual message bubble"]
MSG_ITEM --> MSG_TYPES["Message Types:<br/>TEXT, IMAGE, VIDEO, AUDIO,<br/>DOCUMENT, TEMPLATE, SYSTEM,<br/>INTERNAL_NOTE"]
MSG_TYPES --> DATE_SEP["chat-date-separator.tsx<br/>Date separators between messages"]
end
subgraph INPUT_AREA["MESSAGE INPUT"]
MSG_LIST --> MSG_INPUT["chat-message-input.tsx"]
MSG_INPUT --> TEXT_INPUT["bubble-editor.tsx<br/>(TipTap Rich Editor)"]
MSG_INPUT --> FILE_BTN["File Upload<br/>use-file-upload.ts"]
MSG_INPUT --> EMOJI_BTN["Emoji Picker<br/>use-emoji-picker.ts"]
MSG_INPUT --> QUICK_BTN["Quick Message<br/>quick-message-overlay.tsx<br/>use-quick-message.ts"]
MSG_INPUT --> TEMPL_BTN["WhatsApp Template<br/>chat-template-dialog.tsx<br/>use-template-dialog.ts"]
MSG_INPUT --> REPLY_BTN["Reply to Message"]
MSG_INPUT --> SEND_BTN["Send Button"]
end
subgraph SEND_FLOW["SEND MESSAGE FLOW"]
TEXT_INPUT --> SEND_BTN
FILE_BTN --> UPLOAD["use-file-upload.ts<br/>POST /upload-media"]
UPLOAD --> SEND_BTN
QUICK_BTN --> INSERT_QM["Insert Quick Message Text"]
TEMPL_BTN --> SEND_TEMPL["POST /conversation-messages/template"]
SEND_BTN -->|"POST /conversation-messages<br/>(JSON or Multipart)"| API_SEND
INSERT_QM --> TEXT_INPUT
end
subgraph RT_UPDATE["REAL-TIME UPDATE"]
API_SEND -->|"Message Sent"| PUSHER_RECV["use-pusher-chat.ts<br/>Listen: new-message"]
PUSHER_RECV --> SWR_MUTATE["SWR mutate()<br/>Revalidate messages cache"]
SWR_MUTATE --> UI_UPDATE["Update Message List<br/>(Optimistic UI)"]
end
subgraph RIGHT_PANEL["RIGHT PANEL — Details Sidebar"]
RENDER_LAYOUT --> INFO_TAB["Information Tab<br/>chat-room-information.tsx<br/>Contact details, tags, channels"]
RENDER_LAYOUT --> NOTE_TAB["Notes Tab<br/>note-list, note-item,<br/>note-detail, note-form<br/>CRUD: POST /notes"]
RENDER_LAYOUT --> TICKET_TAB["Ticket Tab<br/>ticket-list, ticket-item,<br/>ticket-detail, ticket-form<br/>Linked tickets to conversation"]
end
subgraph HEADER_ACTIONS["HEADER ACTIONS"]
RENDER_LAYOUT --> CH_HEADER["chat-header-details.tsx"]
CH_HEADER --> ASSIGN["Assign Conversation<br/>chat-assign-dialog.tsx<br/>PATCH /conversations/:id/status"]
CH_HEADER --> RESOLVE["Resolve Conversation<br/>chat-resolve-dialog.tsx<br/>PATCH /conversations/:id/status"]
CH_HEADER --> HISTORY["View History<br/>chat-room-history.tsx<br/>GET /conversations/:id/history"]
CH_HEADER --> GET_NEW["Get New Chat<br/>POST /conversations/get-new-chat<br/>(Claim next unassigned)"]
CH_HEADER --> SEARCH_MSG["Search Messages<br/>chat-search-dialog.tsx<br/>GET /conversation-messages/search"]
CH_HEADER --> BULK_RESOLVE["Bulk Resolve<br/>bulk-resolve-modal.tsx<br/>POST /conversations/bulk-resolve"]
end
style CHAT_PAGE fill:#E3F2FD,stroke:#1976D2
style LEFT_PANEL fill:#E8F5E9,stroke:#4CAF50
style CENTER_PANEL fill:#FFF3E0,stroke:#FF9800
style RIGHT_PANEL fill:#F3E5F5,stroke:#9C27B0
style SEND_FLOW fill:#FFEBEE,stroke:#F44336
style RT_UPDATE fill:#E0F7FA,stroke:#00BCD4
```
---
## 3.4 FE Campaign Module Flow (Detailed)
Alur lengkap modul Campaign dari pembuatan template hingga eksekusi.
```mermaid
flowchart TD
CAMP_PAGE["Campaign Page<br/>/dashboard/campaign"] --> CAMP_LAYOUT["campaign-layout.tsx<br/>(Tab Navigation)"]
CAMP_LAYOUT --> TAB_CAMPAIGNS["Tab: Campaigns"]
CAMP_LAYOUT --> TAB_TEMPLATES["Tab: Templates"]
CAMP_LAYOUT --> TAB_RECIPIENTS["Tab: Recipient Lists"]
subgraph CAMPAIGNS_TAB["CAMPAIGNS MANAGEMENT"]
TAB_CAMPAIGNS --> CAMP_LIST["campaign-view.tsx<br/>Campaign List DataTable"]
CAMP_LIST --> CAMP_TOOLBAR["campaign-table-toolbar.tsx<br/>Filters + Search"]
CAMP_LIST --> CAMP_ROW["campaign-table-row.tsx<br/>Campaign Row"]
CAMP_ROW --> CAMP_ACTIONS["Campaign Actions"]
CAMP_ACTIONS --> VIEW_DETAIL["View Detail<br/>/dashboard/campaign/:id<br/>campaign-detail-view.tsx"]
CAMP_ACTIONS --> EDIT["Edit Campaign<br/>/dashboard/campaign/:id/edit<br/>campaign-create-view.tsx"]
CAMP_ACTIONS --> EXECUTE["Execute Campaign<br/>POST /campaigns/:id/execute"]
CAMP_ACTIONS --> DELETE["Delete Campaign<br/>DELETE /campaigns/:id"]
CAMP_LIST --> CREATE_BTN["+ New Campaign"]
CREATE_BTN --> CREATE_FORM["campaign-create-view.tsx<br/>campaign-create-form.tsx"]
CREATE_FORM --> FORM_STEP1["Step 1: Campaign Name<br/>& Description"]
FORM_STEP1 --> FORM_STEP2["Step 2: Select Channel<br/>(WhatsApp channel only)"]
FORM_STEP2 --> FORM_STEP3["Step 3: Select Template<br/>(from approved templates)"]
FORM_STEP3 --> FORM_STEP4["Step 4: Select Recipient List<br/>(from recipient lists)"]
FORM_STEP4 --> SAVE_DRAFT["Save as DRAFT<br/>POST /campaigns"]
end
subgraph TEMPLATES_TAB["TEMPLATE MANAGEMENT"]
TAB_TEMPLATES --> TEMPL_LIST["campaign-templates-view.tsx<br/>Template List"]
TEMPL_LIST --> TEMPL_TOOLBAR["template-table-toolbar.tsx"]
TEMPL_LIST --> TEMPL_ROW["template-table-row.tsx"]
TEMPL_ROW --> TEMPL_ACTIONS["Template Actions"]
TEMPL_ACTIONS --> VIEW_TEMPL["View Detail<br/>campaign-templates-detail.tsx"]
TEMPL_ACTIONS --> EDIT_TEMPL["Edit Template<br/>campaign-template-edit.tsx"]
TEMPL_ACTIONS --> SYNC_TEMPL["Sync to WhatsApp<br/>POST /templates/:id/sync"]
TEMPL_ACTIONS --> DEL_TEMPL["Delete Template<br/>DELETE /templates/:id"]
TEMPL_LIST --> CREATE_TEMPL["+ New Template"]
CREATE_TEMPL --> TEMPL_CREATE["campaign-template-create.tsx"]
TEMPL_CREATE --> TEMPL_FORM["template-form-fields.tsx<br/>template-form-schema.ts"]
TEMPL_FORM --> TEMPL_HEADER["Header Component<br/>(Text / Image / Video)"]
TEMPL_FORM --> TEMPL_BODY["Body Component<br/>(Text + Variables {{1}}, {{2}}...)"]
TEMPL_FORM --> TEMPL_FOOTER["Footer Component<br/>(Text)"]
TEMPL_FORM --> TEMPL_BUTTONS["Buttons Component<br/>(Quick Reply / URL / Phone)"]
TEMPL_BODY --> UPLOAD_MEDIA["Upload Header Media<br/>POST /templates/upload-media<br/>(Meta Resumable API)"]
TEMPL_FORM --> SAVE_TEMPL["Create Template<br/>POST /templates"]
SAVE_TEMPL --> SYNC_WA["Sync to WhatsApp<br/>POST /templates/:id/sync"]
end
subgraph RECIPIENTS_TAB["RECIPIENT LIST MANAGEMENT"]
TAB_RECIPIENTS --> RL_LIST["campaign-recipient-lists-view.tsx"]
RL_LIST --> RL_TOOLBAR["recipient-list-table-toolbar.tsx"]
RL_LIST --> RL_ROW["recipient-list-table-row.tsx"]
RL_ROW --> RL_ACTIONS["List Actions"]
RL_ACTIONS --> VIEW_RL["View List Detail<br/>campaign-recipient-list-detail.tsx"]
RL_ACTIONS --> EDIT_RL["Edit List"]
RL_ACTIONS --> DEL_RL["Delete List<br/>DELETE /campaign-recipient-lists/:id"]
RL_LIST --> CREATE_RL["+ New Recipient List"]
CREATE_RL --> WIZARD["recipient-list-create-wizard.tsx<br/>(Step-by-step Wizard)"]
WIZARD --> W_STEP1["Step 1: Name & Description<br/>+ Variable Definitions<br/>(e.g. {{nama}}, {{no_pesanan}})"]
W_STEP1 --> W_STEP2["Step 2: Select Contacts<br/>recipient-list-select-contacts-dialog.tsx<br/>(From contact database)"]
W_STEP2 --> W_STEP3["Step 3: Input Variable Values<br/>recipient-list-input-variables-dialog.tsx<br/>(Per contact: nama=John, no_pesanan=123)"]
W_STEP3 --> SAVE_RL["Save Recipient List<br/>POST /campaign-recipient-lists"]
VIEW_RL --> ITEMS_LIST["List Items<br/>campaign-recipient-list-item-table-row.tsx"]
ITEMS_LIST --> ADD_CONTACTS["Add More Contacts<br/>recipient-list-add-contacts-dialog.tsx<br/>POST /campaign-recipient-lists/:id/contacts"]
ITEMS_LIST --> RM_CONTACTS["Remove Contacts<br/>DELETE /campaign-recipient-lists/:id/contacts"]
end
subgraph EXECUTION_FLOW["CAMPAIGN EXECUTION"]
EXECUTE --> EXEC_API["POST /campaigns/:id/execute"]
EXEC_API --> BE_PROCESS["Backend Processing<br/>(See BE Campaign Flow)"]
BE_PROCESS --> DELIVERY_WEBHOOK["WhatsApp Delivery Webhooks<br/>→ Update per-recipient status"]
DELIVERY_WEBHOOK --> VIEW_STATS["View Campaign Stats<br/>sent / delivered / read / failed"]
end
style CAMPAIGNS_TAB fill:#E8F5E9,stroke:#4CAF50
style TEMPLATES_TAB fill:#FFF3E0,stroke:#FF9800
style RECIPIENTS_TAB fill:#F3E5F5,stroke:#9C27B0
style EXECUTION_FLOW fill:#FFEBEE,stroke:#F44336
```
---
## 3.5 FE Ticket Module Flow (Detailed)
Alur lengkap modul Ticket dari pembuatan hingga lifecycle management.
```mermaid
flowchart TD
TICKET_PAGE["Ticket Page<br/>/dashboard/ticket"] --> TICKET_LIST["ticket-list-view.tsx<br/>Ticket DataTable"]
TICKET_LIST --> TOOLBAR["ticket-table-toolbar.tsx<br/>Status Filter | Assignee Filter<br/>Contact Search"]
TICKET_LIST --> TABLE["Ticket Table Columns:<br/>Number, Title, Status,<br/>Assignee, Created, Updated"]
TABLE --> CREATE_BTN["+ New Ticket"]
TABLE --> SELECT_ROW["Select Ticket → Detail View"]
TABLE --> BATCH_ACTIONS["Batch Actions<br/>(Checkbox Selection)"]
subgraph CREATE_FLOW["CREATE TICKET"]
CREATE_BTN --> CREATE_VIEW["ticket-create-view.tsx<br/>(Standalone)"]
CREATE_VIEW --> CREATE_FORM["ticket-create-form.tsx"]
CREATE_FORM --> FORM_TITLE["Ticket Title *"]
CREATE_FORM --> FORM_DESC["Description *"]
CREATE_FORM --> FORM_CONV["Link Conversation<br/>(optional - select from list)"]
CREATE_FORM --> FORM_CONTACT["Link Contact<br/>(optional - select from list)"]
CREATE_FORM --> FORM_ATTACH["Attachments<br/>POST /tickets/upload-media"]
CREATE_FORM --> FORM_TEMPL["Use Template<br/>/dashboard/ticket/templates"]
FORM_ATTACH --> SUBMIT["POST /tickets<br/>→ Status: TODO"]
FORM_TITLE --> SUBMIT
FORM_DESC --> SUBMIT
end
subgraph DETAIL_FLOW["TICKET DETAIL VIEW"]
SELECT_ROW --> DETAIL["ticket-detail-view.tsx<br/>/dashboard/ticket/:id"]
DETAIL --> INFO["Ticket Info<br/>Number, Title, Description,<br/>Status, Assignee, Dates"]
DETAIL --> ATTACHMENTS["ticket-attachment-preview.tsx<br/>File attachments"]
DETAIL --> HISTORY["ticket-history-timeline.tsx<br/>Status change history"]
DETAIL --> ACTION_BUTTONS["Status Action Buttons"]
end
subgraph ACTIONS["TICKET LIFECYCLE ACTIONS"]
ACTION_BUTTONS --> ASSIGN["Assign to Agent<br/>ticket-assign-dialog.tsx<br/>PUT /tickets/:id<br/>assignedTo, assignedBy"]
ACTION_BUTTONS --> START["Start Progress<br/>PUT /tickets/:id<br/>status: IN_PROGRESS"]
ACTION_BUTTONS --> RESOLVE["Resolve<br/>ticket-resolve-dialog.tsx<br/>PUT /tickets/:id<br/>status: RESOLVED + resolvedNote"]
ACTION_BUTTONS --> DONE["Mark Done<br/>ticket-done-dialog.tsx<br/>PUT /tickets/:id<br/>status: DONE"]
ACTION_BUTTONS --> REJECT["Reject<br/>ticket-reject-dialog.tsx<br/>PUT /tickets/:id<br/>status: REJECTED + rejectReason"]
ACTION_BUTTONS --> EDIT["Edit Ticket<br/>ticket-edit-view.tsx<br/>PUT /tickets/:id"]
end
subgraph BATCH_OP["BATCH OPERATIONS"]
BATCH_ACTIONS --> BATCH_ASSIGN["Batch Assign<br/>ticket-batch-assign-dialog.tsx<br/>Loop PUT /tickets/:id"]
BATCH_ACTIONS --> BATCH_STATUS["Batch Update Status<br/>→ IN_PROGRESS<br/>Loop PUT /tickets/:id"]
end
subgraph TICKET_TEMPLATES["TICKET TEMPLATES"]
TICKET_PAGE --> TEMPL_NAV["/dashboard/ticket/templates"]
TEMPL_NAV --> TEMPL_LIST_T["ticket-template-list-view.tsx"]
TEMPL_LIST_T --> TEMPL_CREATE_T["+ New Ticket Template<br/>ticket-template-create-view.tsx"]
TEMPL_CREATE_T --> TEMPL_FORM_T["ticket-template-form-fields.tsx"]
TEMPL_FORM_T --> TEMPL_TYPE["Template Type:<br/>TICKET_START / TICKET_END"]
TEMPL_FORM_T --> SAVE_TEMPL_T["POST /templates/ticket"]
end
subgraph EMBEDDED["TICKET FROM CHAT"]
CHAT_PANEL["Chat Room<br/>Right Panel → Ticket Tab"] --> TICKET_TAB["chat-room-ticket.tsx"]
TICKET_TAB --> TICKET_FORM_CHAT["ticket-form.tsx<br/>(Inline Create)"]
TICKET_FORM_CHAT --> CREATE_FROM_CHAT["Create Ticket<br/>(auto-links current conversation)"]
TICKET_TAB --> TICKET_LIST_CHAT["ticket-list.tsx<br/>(Linked tickets list)"]
end
style CREATE_FLOW fill:#E8F5E9,stroke:#4CAF50
style DETAIL_FLOW fill:#FFF3E0,stroke:#FF9800
style ACTIONS fill:#FFEBEE,stroke:#F44336
style TICKET_TEMPLATES fill:#E3F2FD,stroke:#1976D2
style EMBEDDED fill:#F3E5F5,stroke:#9C27B0
```
---
## 3.6 FE Settings Module Flow (Detailed)
Alur lengkap halaman Settings beserta role guard pada setiap sub-menu.
```mermaid
flowchart TD
SETTINGS_ENTRY["/dashboard/settings"] --> REDIRECT["Redirect to<br/>/dashboard/settings/user/list"]
SETTINGS_ENTRY --> SETTINGS_NAV{{"Settings Navigation"}}
subgraph USER_MGMT["USER MANAGEMENT [admin, owner]"]
SETTINGS_NAV --> SET_USER["/settings/user/list<br/>SettingsUserListPage"]
SETTINGS_NAV --> SET_USER_NEW["/settings/user/new<br/>UserCreatePage"]
SETTINGS_NAV --> SET_USER_EDIT["/settings/user/:id/edit<br/>UserEditPage"]
SETTINGS_NAV --> SET_ROLE_PERM["/settings/user/role-permission<br/>SettingsUserRolePermissionPage"]
SET_USER --> USER_TABLE["User List Table<br/>GET /auth/admin/list-users"]
USER_TABLE --> USER_ACTIONS["User Actions"]
USER_ACTIONS --> CREATE_USER["Create User<br/>POST /auth/admin/create-user"]
USER_ACTIONS --> EDIT_USER["Edit User<br/>POST /auth/admin/update-user"]
USER_ACTIONS --> SET_ROLE["Set Role<br/>POST /auth/admin/set-role"]
USER_ACTIONS --> BAN_USER["Ban User"]
USER_ACTIONS --> UNBAN["Unban User<br/>POST /auth/admin/unban-user"]
USER_ACTIONS --> INVITE["Invite Member<br/>POST /auth/organization/invite-member"]
end
subgraph CHANNEL_MGMT["CHANNEL MANAGEMENT [admin, owner]"]
SETTINGS_NAV --> SET_CH["/settings/channel<br/>ChannelPage"]
SETTINGS_NAV --> SET_CH_NEW["/settings/channel/new<br/>ChannelCreatePage"]
SETTINGS_NAV --> SET_CH_EDIT["/settings/channel/:id/edit<br/>ChannelEditPage"]
SET_CH --> CH_LIST["Channel List<br/>GET /channels"]
CH_LIST --> CH_ACTIONS["Channel Actions"]
CH_ACTIONS --> CONNECT_WA["Connect WhatsApp<br/>POST /channels/whatsapp<br/>(Embedded Signup Flow)"]
CH_ACTIONS --> CONNECT_IG["Connect Instagram<br/>POST /channels/instagram<br/>(Business Login Flow)"]
CH_ACTIONS --> EDIT_CH["Edit Channel<br/>PUT /channels/:id"]
CH_ACTIONS --> GEN_WEBHOOK["Generate Webhook Token<br/>POST /channels/:id/generate-webhook"]
CH_ACTIONS --> DEL_CH["Delete Channel<br/>DELETE /channels/:id"]
end
subgraph WS_MGMT["WORKSPACE MANAGEMENT [owner only]"]
SETTINGS_NAV --> SET_WS["/settings/workspace<br/>WorkspacePage"]
SETTINGS_NAV --> SET_WS_NEW["/settings/workspace/new<br/>WorkspaceCreatePage"]
SETTINGS_NAV --> SET_WS_EDIT["/settings/workspace/:id/edit<br/>WorkspaceEditPage"]
SET_WS --> WS_LIST["Workspace List<br/>authClient.organization.list()"]
WS_LIST --> WS_ACTIONS["Workspace Actions"]
WS_ACTIONS --> CREATE_WS["Create Workspace<br/>authClient.organization.create()"]
WS_ACTIONS --> EDIT_WS["Edit Workspace<br/>authClient.organization.update()"]
WS_ACTIONS --> DEL_WS["Delete Workspace<br/>authClient.organization.delete()"]
WS_ACTIONS --> SESSION_CFG["Session Time Config<br/>PUT /workspaces/:id/session-time<br/>Options: 5min, 30min, 1hour, 24hour, never"]
end
subgraph ACCOUNT["MY ACCOUNT [all roles]"]
SETTINGS_NAV --> SET_ACCOUNT["/settings/account<br/>AccountGeneralPage"]
SETTINGS_NAV --> SET_PWD["/settings/account/change-password<br/>AccountChangePasswordPage"]
SET_ACCOUNT --> PROFILE["Profile Settings<br/>PUT /user/update-user<br/>(username, fullname, phone,<br/>photo, image_url, status)"]
SET_PWD --> CHANGE_PWD["Change Password<br/>POST /auth/change-password<br/>(currentPassword, newPassword,<br/>revokeOtherSessions)"]
end
subgraph OTHER_SETTINGS["OTHER SETTINGS [admin, owner]"]
SETTINGS_NAV --> SET_TAG["/settings/tag<br/>Tag Management<br/>CRUD /tags"]
SETTINGS_NAV --> SET_OH["/settings/office-hours<br/>Office Hours & Auto Response<br/>CRUD /office-hour-*"]
SETTINGS_NAV --> SET_QM["/settings/quick-message<br/>Quick Reply Templates<br/>CRUD /message-templates"]
SETTINGS_NAV --> SET_API["/settings/api-keys<br/>API Key Management<br/>CRUD /workspace-api-keys"]
SETTINGS_NAV --> SET_RC["/settings/resolve-category<br/>Resolve Categories<br/>CRUD /conversation-resolve-category"]
end
subgraph OFFICE_HOURS_DETAIL["OFFICE HOURS DETAIL"]
SET_OH --> OH_SCHEDULE["Schedule Configuration<br/>Per Channel"]
OH_SCHEDULE --> OH_DAYS["Days: Mon-Sun<br/>Start Time, End Time<br/>Timezone"]
OH_SCHEDULE --> OH_STATUS["useOfficeHoursStatus hook<br/>Auto-check every minute<br/>→ Online / Offline"]
SET_OH --> OH_RESPONSE["Auto Response Configuration<br/>Per Channel"]
OH_RESPONSE --> OH_DURING["During Office Hours<br/>Auto-response message"]
OH_RESPONSE --> OH_OUTSIDE["Outside Office Hours<br/>Auto-response message"]
end
style USER_MGMT fill:#E8F5E9,stroke:#4CAF50
style CHANNEL_MGMT fill:#FFF3E0,stroke:#FF9800
style WS_MGMT fill:#FFEBEE,stroke:#F44336
style ACCOUNT fill:#E3F2FD,stroke:#1976D2
style OTHER_SETTINGS fill:#F3E5F5,stroke:#9C27B0
```
---
## 3.7 FE Data Fetching Architecture (SWR Pattern)
Diagram yang menunjukkan pola data fetching menggunakan SWR di seluruh aplikasi.
```mermaid
flowchart TD
COMPONENT["React Component<br/>(Page / Section)"] --> HOOK["Custom Hook<br/>(use-conversations,<br/>use-messages, etc.)"]
HOOK --> SWR_HOOK["useSWR()<br/>SWR Hook"]
SWR_HOOK --> FETCHER["Axios Fetcher<br/>/src/lib/fetcher.ts"]
FETCHER --> API["API Request<br/>GET /api/v1/u/*"]
API --> RESPONSE{"Response?"}
RESPONSE -->|200 OK| CACHE["SWR Cache<br/>(Key: URL + Params)"]
RESPONSE -->|Error| ERROR_HANDLER["Error Handling"]
ERROR_HANDLER --> SHOW_TOAST["Snackbar Toast<br/>Error Message"]
CACHE --> RENDER["Render Component<br/>with Cached Data"]
RENDER --> IDLE{"Data Stale?"}
IDLE -->|Yes| REVALIDATE["Auto Revalidation<br/>(revalidateOnFocus,<br/>revalidateOnReconnect)"]
IDLE -->|No| WAIT["Wait for Event"]
REVALIDATE --> FETCHER
subgraph RT_EVENTS["Real-Time Triggered Revalidation"]
PUSHER_EV["Pusher Event Received"] --> MUTATE["SWR mutate()<br/>Invalidate specific key"]
SOCKET_EV["Socket.IO Event"] --> MUTATE
MUTATE --> FETCHER
end
subgraph MANUAL["Manual Mutations"]
OPTIMISTIC["Optimistic Update"] --> LOCAL_UPDATE["Update local cache first"]
MANUAL_CALL["Manual API Call<br/>(POST/PUT/DELETE)"] --> MANUAL_MUTATE["mutate() after success"]
MANUAL_CALL -->|"Error"| ROLLBACK["Rollback optimistic update"]
LOCAL_UPDATE --> MANUAL_MUTATE
end
```
# BAGIAN 4: FLOWCHART BACKEND (BE)
## Detailed Backend Application Flowcharts
---
## 4.1 BE General Service Architecture
Arsitektur umum Backend menggunakan pattern **Repository → Service → Route** (Layered Architecture).
```mermaid
flowchart TD
ENTRY["index.ts<br/>Bun HTTP Server<br/>Port: 8000"] --> APP_SVC["app.service.ts<br/>createApp()"]
APP_SVC --> INIT_PUSH["Initialize Pusher"]
APP_SVC --> INIT_RMQ["Initialize RabbitMQ"]
APP_SVC --> DEV_INIT{"Development?"}
DEV_INIT -->|Yes| DB_INIT["Initialize DB<br/>Test Connection"]
subgraph GLOBAL_MW["Global Middleware Stack"]
GM1["CORS (cors.config.ts)<br/>Allowed origins validation"]
GM2["Timing (response time header)"]
GM3["Request ID (unique per request)"]
GM4["Logger (request logging)"]
GM5["Body Limit (5MB default)"]
GM6["Timeout (5min default)"]
GM1 --> GM2 --> GM3 --> GM4 --> GM5 --> GM6
end
APP_SVC --> GLOBAL_MW
subgraph ROUTES["Route Registration (v1.ts)"]
R_AUTH["/api/v1/auth/*<br/>better-auth handler"]
R_WEBHOOK["/api/webhook/*<br/>Webhook receivers"]
R_PROTECTED["/api/v1/u/*<br/>requireAuth middleware"]
R_UPLOADS["/api/uploads<br/>File storage"]
R_SWAGGER["/api/v1/swagger<br/>OpenAPI docs"]
end
GLOBAL_MW --> ROUTES
subgraph AUTH_MW_LAYER["Auth Middleware Chain"]
AM1["requireAuth<br/>(Session verification)"]
AM2["optionalAuth<br/>(No block if missing)"]
AM3["requirePermission<br/>(RBAC check)"]
AM4["requireWorkspaceApiKey<br/>(x-api-key header)"]
AM5["requireAuthOrWorkspaceApiKey<br/>(Either session or API key)"]
end
R_PROTECTED --> AUTH_MW_LAYER
AUTH_MW_LAYER --> SERVICE["Service Layer<br/>(47 Modules)"]
SERVICE --> REPO["Repository Layer<br/>(Drizzle ORM queries)"]
REPO --> DB["PostgreSQL<br/>schema: omnichannel<br/>30+ tables"]
SERVICE --> NOTIFY["Real-time Notification<br/>notifyHybrid()<br/>Socket.IO + Pusher"]
SERVICE --> EXT_API["External APIs<br/>WhatsApp / Instagram / Telegram"]
R_WEBHOOK --> RABBIT["RabbitMQ Publisher"]
RABBIT --> WORKER["Worker Consumer"]
WORKER --> SERVICE
style ENTRY fill:#C8E6C9,stroke:#4CAF50
style GLOBAL_MW fill:#FFF3E0,stroke:#FF9800
style AUTH_MW_LAYER fill:#FFEBEE,stroke:#F44336
style SERVICE fill:#E3F2FD,stroke:#1976D2
style REPO fill:#F3E5F5,stroke:#9C27B0
```
---
## 4.2 BE Auth & RBAC Flow (Detailed)
Alur lengkap autentikasi dan authorization di Backend.
```mermaid
flowchart TD
REQUEST["Incoming Request"] --> ROUTE_TYPE{Route Type?}
subgraph PUBLIC_ROUTES["Public Routes (No Auth)"]
ROUTE_TYPE -->|"/api/v1/swagger"| SWAGGER["Swagger UI"]
ROUTE_TYPE -->|"/api/webhook/whatsapp<br/>(GET verify)"| WH_VERIFY["Webhook Verification"]
end
subgraph BETTER_AUTH["Better-Auth Self-Handled"]
ROUTE_TYPE -->|"/api/v1/auth/*"| BA_HANDLER["better-auth handler"]
BA_HANDLER --> BA_SIGNIN["sign-in/email-password"]
BA_HANDLER --> BA_SIGNUP["sign-up/email-password"]
BA_HANDLER --> BA_FORGET["forget-password"]
BA_HANDLER --> BA_RESET["reset-password"]
BA_HANDLER --> BA_CHANGE["change-password"]
BA_HANDLER --> BA_SESSION["get-session"]
BA_HANDLER --> BA_ADMIN["admin/list-users"]
BA_HANDLER --> BA_CREATE["admin/create-user"]
BA_HANDLER --> BA_UPDATE["admin/update-user"]
BA_HANDLER --> BA_SET_ROLE["admin/set-role"]
BA_HANDLER --> BA_UNBAN["admin/unban-user"]
BA_HANDLER --> BA_ORG_LIST["organization/list-members"]
BA_HANDLER --> BA_ORG_INVITE["organization/invite-member"]
BA_HANDLER --> BA_ORG_UPDATE["organization/update-member-role"]
BA_HANDLER --> BA_ORG_REMOVE["organization/remove-member"]
BA_HANDLER --> BA_ORG_ACCEPT["organization/accept-invitation"]
BA_HANDLER --> BA_APIKEY["api-key/create"]
end
subgraph PROTECTED["Protected Routes (Auth Required)"]
ROUTE_TYPE -->|"/api/v1/u/*"| AUTH_CHECK
AUTH_CHECK["Which Auth Method?"]
AUTH_CHECK -->|"Default"| REQUIRE_AUTH["requireAuth middleware"]
AUTH_CHECK -->|"Tickets"| EITHER["requireAuthOrWorkspaceApiKey"]
AUTH_CHECK -->|"Optional"| OPTIONAL["optionalAuth"]
REQUIRE_AUTH --> SESSION_CHECK{Valid Session<br/>Cookie?}
SESSION_CHECK -->|No| ERR_401["401 Unauthorized<br/>response.util.ts"]
SESSION_CHECK -->|Yes| SET_CTX["Set c.set('user', user)<br/>c.set('session', session)"]
EITHER --> CHECK_EITHER{Session OR<br/>API Key?}
CHECK_EITHER -->|Neither| ERR_401
CHECK_EITHER -->|Session| SET_CTX
CHECK_EITHER -->|API Key| CHECK_API{Valid API Key?<br/>x-api-key header}
CHECK_API -->|No| ERR_401
CHECK_API -->|Yes| SET_API_CTX["Set user + workspace<br/>from key metadata"]
SET_CTX --> RBAC_CHECK
SET_API_CTX --> RBAC_CHECK
RBAC_CHECK["requirePermission({resource: action})"]
RBAC_CHECK --> CHECK_ROLE{User has<br/>permission?}
CHECK_ROLE -->|No| ERR_403["403 Forbidden"]
CHECK_ROLE -->|Yes| HANDLER["Route Handler"]
end
HANDLER --> SVC["Service → Repository → DB"]
SVC --> RESP["Response<br/>response.util.ts<br/>success / error / paginated"]
SVC --> NOTIFY["notifyHybrid()<br/>Pusher + Socket.IO"]
style PUBLIC_ROUTES fill:#E8F5E9,stroke:#4CAF50
style BETTER_AUTH fill:#FFF3E0,stroke:#FF9800
style PROTECTED fill:#FFEBEE,stroke:#F44336
```
---
## 4.3 BE Conversation & Message Flow (Chat Engine)
Alur lengkap mesin chat Backend, baik inbound maupun outbound.
```mermaid
flowchart TD
subgraph INBOUND["INBOUND MESSAGE FLOW"]
WEBHOOK["Webhook Received"] --> PARSE["Parse Platform Data"]
PARSE --> EXTRACT["Extract:<br/>sender_phone, content, type,<br/>channel_code, raw_id"]
EXTRACT --> PROFANITY{"Profanity<br/>Filter?"}
PROFANITY -->|Flagged| MARK_FLAGGED["Mark as flagged"]
PROFANITY -->|Clean| FIND_CONTACT
MARK_FLAGGED --> FIND_CONTACT{Contact<br/>Exists?<br/>MATCH by phone + channel_id}
FIND_CONTACT -->|No| CREATE_CONTACT["Create Contact<br/>INSERT contacts<br/>contact_name=phone<br/>contact_phone=phone"]
FIND_CONTACT -->|Yes| GET_CONTACT["SELECT contact<br/>WHERE phone + channel_id"]
CREATE_CONTACT --> FIND_CONV
GET_CONTACT --> FIND_CONV
FIND_CONV{Active Conversation?<br/>MATCH by contact_id<br/>+ channel_id<br/>+ status NOT CLOSED/RESOLVED}
FIND_CONV -->|No| CREATE_CONV["INSERT conversations<br/>status: UNASSIGNED<br/>unread_counts: 1"]
FIND_CONV -->|Yes Chain| UPDATE_CHAIN["Link previous_conversation_id"]
FIND_CONV -->|Yes Active| UPDATE_CONV
UPDATE_CHAIN --> UPDATE_CONV["UPDATE conversations<br/>last_message, last_message_at<br/>unread_counts++<br/>conversation_24h_window<br/>(WhatsApp only)"]
UPDATE_CONV --> SAVE_MSG["INSERT conversation_messages<br/>from=customer, to=system<br/>type based on content"]
SAVE_MSG --> SAVE_ATTACH{"Has<br/>Attachments?"}
SAVE_ATTACH -->|Yes| SAVE_ATT["INSERT message_attachments<br/>Download media from<br/>platform API"]
SAVE_ATTACH -->|No| CHECK_OH
SAVE_ATT --> CHECK_OH
CHECK_OH{"Within<br/>Office Hours?<br/>office-hours-schedule"}
CHECK_OH -->|No| AUTO_RESP["Send Auto Response<br/>office-hours-auto-responses<br/>→ Platform Send API"]
CHECK_OH -->|Yes| NOTIFY
AUTO_RESP --> NOTIFY["notifyHybrid()"]
NOTIFY --> P1["Pusher: private-room:{convId}<br/>Event: new-message"]
NOTIFY --> P2["Pusher: private-channel:{chId}<br/>Event: new-conversation<br/>(if new)"]
NOTIFY --> S1["Socket.IO: emit new-message"]
end
subgraph OUTBOUND["OUTBOUND MESSAGE FLOW"]
SEND_API["POST /conversation-messages<br/>(Agent sends message)"] --> VALIDATE["Validate with Zod<br/>conversation-message.validation.ts"]
VALIDATE --> CHECK_TYPE{Message Type?}
CHECK_TYPE -->|"TEXT"| PREPARE_TEXT["Prepare text content"]
CHECK_TYPE -->|"IMAGE/VIDEO/AUDIO/DOC"| UPLOAD_MEDIA["Check media<br/>Already uploaded or new"]
CHECK_TYPE -->|"TEMPLATE"| PREPARE_TEMPL["Prepare template<br/>components + variables"]
PREPARE_TEXT --> PROFANITY_OUT{"Profanity?"}
UPLOAD_MEDIA --> PROFANITY_OUT
PREPARE_TEMPL --> SKIP_PROFANE
PROFANITY_OUT -->|Flagged| REJECT["400 Bad Request<br/>'Message contains inappropriate content'"]
PROFANITY_OUT -->|Clean| CHECK_WINDOW
SKIP_PROFANE --> CHECK_WINDOW{"WhatsApp 24h<br/>Window Active?<br/>(if WhatsApp channel)"}
CHECK_WINDOW -->|Expired| ERR_WINDOW["Error:<br/>'24-hour messaging window expired'"]
CHECK_WINDOW -->|Active or Non-WA| SEND_PLATFORM
SEND_PLATFORM["Send to Platform API"]
SEND_PLATFORM --> WA_API["WhatsApp API<br/>POST /messages"]
SEND_PLATFORM --> IG_API["Instagram API<br/>POST /messages"]
SEND_PLATFORM --> TG_API["Telegram API<br/>sendMessage"]
WA_API --> SAVE_OUT["INSERT conversation_messages<br/>from=agent, to=customer<br/>status: SENT"]
IG_API --> SAVE_OUT
TG_API --> SAVE_OUT
SAVE_OUT --> UPDATE_CONV_OUT["UPDATE conversations<br/>last_message, last_message_at"]
UPDATE_CONV_OUT --> NOTIFY_OUT["notifyHybrid()<br/>private-room:{convId}<br/>Event: new-message"]
end
subgraph DELIVERY["DELIVERY STATUS WEBHOOK"]
DELIVERY_WB["Platform Delivery Webhook"] --> FIND_MSG{"Find Message<br/>by platform_msg_id"}
FIND_MSG --> UPDATE_STATUS["UPDATE conversation_messages<br/>status: DELIVERED / READ / FAILED"]
UPDATE_STATUS --> NOTIFY_DELIVERY["notifyHybrid()<br/>Event: message-updated"]
end
style INBOUND fill:#E8F5E9,stroke:#4CAF50
style OUTBOUND fill:#FFF3E0,stroke:#FF9800
style DELIVERY fill:#E3F2FD,stroke:#1976D2
```
---
## 4.4 BE Conversation Status Flow
Alur perubahan status conversation.
```mermaid
stateDiagram-v2
[*] --> UNASSIGNED: Customer sends first message
UNASSIGNED --> ASSIGNED: Agent claims chat<br/>(POST /get-new-chat)
UNASSIGNED --> ASSIGNED: Admin assigns agent<br/>(PATCH /status: assignedTo)
ASSIGNED --> UNASSIGNED: Agent unassigns<br/>(PATCH /status: assignedTo=null)
ASSIGNED --> RESOLVED: Agent resolves<br/>(PATCH /status + resolveCategoryIds)
ASSIGNED --> ASSIGNED: Agent sends message<br/>(message flow continues)
RESOLVED --> ASSIGNED: Customer replies<br/>(new message in chain)
RESOLVED --> CLOSED: Admin closes<br/>(PATCH /status: CLOSED)
CLOSED --> ASSIGNED: Customer replies<br/>(new conversation created,<br/>previous_conversation_id linked)
UNASSIGNED: Unread count tracked
ASSIGNED: first_response_at set
ASSIGNED: first_response_by set
RESOLVED: resolved_at set
RESOLVED: resolved_by set
RESOLVED: resolve categories linked
CLOSED: conversation ended
```
---
## 4.5 BE Campaign Execution Flow (Detailed)
Alur lengkap eksekusi campaign di Backend.
```mermaid
flowchart TD
TRIGGER["POST /campaigns/:id/execute"] --> LOAD["Load Campaign<br/>+ Template + Channel + Recipient List"]
LOAD --> STATUS_CHECK{Campaign Status?}
STATUS_CHECK -->|"DRAFT"| SET_RUNNING["UPDATE campaigns<br/>status: RUNNING<br/>started_at: NOW()"]
STATUS_CHECK -->|"SCHEDULED"| CHECK_SCHEDULE{Scheduled time<br/>reached?}
STATUS_CHECK -->|"RUNNING"| ALREADY["Return: Already running"]
STATUS_CHECK -->|"COMPLETED/CANCELLED"| ERR["Return Error"]
CHECK_SCHEDULE -->|"Not yet"| SCHEDULE_WAIT["Wait for cron/scheduler"]
CHECK_SCHEDULE -->|"Yes"| SET_RUNNING
SET_RUNNING --> LOAD_TEMPLATE["Load Template<br/>templates table<br/>template_components (JSON)"]
LOAD_TEMPLATE --> LOAD_LIST["Load Recipient List Items<br/>campaign_recipient_list_items<br/>+ contacts"]
LOAD_LIST --> INIT_COUNTS["Reset campaign counts:<br/>total_recipients, sent, delivered,<br/>read, failed = 0"]
INIT_COUNTS --> LOOP_START["Start Loop"]
subgraph LOOP["PER RECIPIENT LOOP"]
LOOP_START --> GET_NEXT{Next<br/>Recipient?}
GET_NEXT -->|No| COMPLETE
GET_NEXT -->|Yes| RENDER["Render Template<br/>Replace {{1}}, {{2}}...<br/>with recipient variables"]
RENDER --> FIND_CONV{Has Active<br/>Conversation?<br/>contact_id + channel_id}
FIND_CONV -->|No| CREATE_CONV["INSERT conversations<br/>status: UNASSIGNED"]
FIND_CONV -->|Yes| USE_CONV["Use existing conversation"]
CREATE_CONV --> SEND_WA
USE_CONV --> SEND_WA
SEND_WA["Send via WhatsApp API<br/>POST /{phone_id}/messages<br/>template type message"]
SEND_WA --> WA_RESPONSE{WhatsApp<br/>API Response?}
WA_RESPONSE -->|Success| RECORD_SENT["INSERT campaign_recipients<br/>status: SENT<br/>conversation_id, message_id"]
WA_RESPONSE -->|Rate Limit| WAIT_RETRY["Wait + Retry<br/>(Exponential backoff)"]
WA_RESPONSE -->|Error| RECORD_FAIL["INSERT campaign_recipients<br/>status: FAILED"]
RECORD_SENT --> INC_SENT["UPDATE campaigns<br/>sent++"]
RECORD_FAIL --> INC_FAIL["UPDATE campaigns<br/>failed++"]
INC_SENT --> NEXT["Continue Loop"]
INC_FAIL --> NEXT
WAIT_RETRY --> SEND_WA
NEXT --> GET_NEXT
end
COMPLETE["All Recipients Processed"] --> SET_COMPLETE["UPDATE campaigns<br/>status: COMPLETED<br/>completed_at: NOW()"]
subgraph DELIVERY["DELIVERY WEBHOOKS"]
WA_DELIVERY["WhatsApp Delivery Callback"] --> FIND_RECIP{Find campaign_recipient<br/>by conversation_message_id}
FIND_RECIP --> UPDATE_RECIP["UPDATE campaign_recipients<br/>status: DELIVERED / READ / FAILED"]
UPDATE_RECIP --> UPDATE_CAMP_COUNTS["UPDATE campaigns<br/>delivered++ / read++ / failed++"]
UPDATE_CAMP_COUNTS --> NOTIFY_STATS["Optional: Push notification<br/>to campaign dashboard"]
end
style LOOP fill:#E8F5E9,stroke:#4CAF50
style DELIVERY fill:#FFF3E0,stroke:#FF9800
```
---
## 4.6 BE Ticket Lifecycle Flow (Detailed)
Alur lengkap lifecycle ticket di Backend.
```mermaid
flowchart TD
CREATE["POST /tickets<br/>Create Ticket"] --> VALIDATE_T["Validate with Zod<br/>ticket.validation.ts"]
VALIDATE_T --> INSERT_TICKET["INSERT tickets<br/>ticket_number: auto-generated<br/>status: TODO"]
INSERT_TICKET --> INSERT_ATTACH{"Has<br/>Attachments?"}
INSERT_ATTACH -->|Yes| INSERT_ATT["INSERT ticket_attachments<br/>(link to ticket_id)"]
INSERT_ATTACH -->|No| DONE_CREATE
INSERT_ATT --> DONE_CREATE["Ticket Created<br/>201 Created Response"]
subgraph LIFECYCLE["TICKET LIFECYCLE"]
DONE_CREATE --> TODO_ST["Status: TODO"]
TODO_ST -->|"Assign to Agent<br/>PUT /tickets/:id<br/>assignedTo, assignedBy"| IN_PROGRESS["Status: IN_PROGRESS"]
TODO_ST -->|"Soft Delete<br/>DELETE /tickets/:id"| DELETED["Status: DELETED<br/>(deleted_at IS NOT NULL)"]
IN_PROGRESS -->|"Reassign<br/>assignedTo changed"| IN_PROGRESS
IN_PROGRESS -->|"Resolve<br/>status: RESOLVED<br/>resolvedBy, resolvedAt<br/>resolvedNote"| RESOLVED["Status: RESOLVED"]
IN_PROGRESS -->|"Reject<br/>status: REJECTED<br/>rejectedBy, rejectedAt<br/>rejectReason"| REJECTED["Status: REJECTED"]
RESOLVED -->|"Reopen<br/>status back to<br/>IN_PROGRESS"| IN_PROGRESS
RESOLVED -->|"Mark Done<br/>status: DONE<br/>closedBy, closedAt"| DONE["Status: DONE"]
REJECTED --> END_STATE["Terminal State"]
DONE --> END_STATE
DELETED --> END_STATE
end
subgraph HISTORY["STATUS HISTORY TRACKING"]
STATUS_CHANGE["Any Status Change"] --> RECORD_HIST["INSERT ticket_status_history<br/>ticket_id, old_status, new_status<br/>updated_by, assigned_to<br/>resolved_by, rejected_by"]
RECORD_HIST --> NOTIFY_TICKET["notifyHybrid()<br/>(if real-time needed)"]
end
LIFECYCLE --> HISTORY
subgraph BATCH["BATCH OPERATIONS"]
BATCH_ASSIGN["Batch Assign<br/>(FE loops PUT calls)"] --> LOOP_PUT1["PUT /tickets/:id<br/>assignedTo: userId<br/>× N tickets"]
BATCH_STATUS["Batch Update Status<br/>(FE loops PUT calls)"] --> LOOP_PUT2["PUT /tickets/:id<br/>status: IN_PROGRESS<br/>× N tickets"]
end
subgraph FROM_CHAT["TICKET FROM CONVERSATION"]
CHAT_LINK["conversation_id provided"] --> VERIFY_CONV{"Conversation<br/>Exists?"}
VERIFY_CONV -->|Yes| LINK_CONV["Set ticket.conversation_id"]
VERIFY_CONV -->|No| ERR_CONV["Error: Conversation not found"]
CONTACT_LINK["contact_id provided"] --> VERIFY_CONTACT{"Contact<br/>Exists?"}
VERIFY_CONTACT -->|Yes| LINK_CONTACT["Set ticket.contact_id"]
VERIFY_CONTACT -->|No| ERR_CONTACT["Error: Contact not found"]
end
style LIFECYCLE fill:#E8F5E9,stroke:#4CAF50
style HISTORY fill:#FFF3E0,stroke:#FF9800
style BATCH fill:#E3F2FD,stroke:#1976D2
style FROM_CHAT fill:#F3E5F5,stroke:#9C27B0
```
---
## 4.7 BE Webhook Processing Architecture
Arsitektur pemrosesan webhook dari external channels.
```mermaid
flowchart TD
subgraph INCOMING["WEBHOOK RECEIVERS"]
WH_WA_GET["GET /api/webhook/whatsapp<br/>Verification<br/>(hub.mode, hub.verify_token,<br/>hub.challenge)"]
WH_WA_POST["POST /api/webhook/whatsapp<br/>Receive messages<br/>(WhatsApp payload)"]
WH_IG["POST /api/webhook/instagram<br/>Receive DMs + Comments<br/>(Instagram payload)"]
WH_TG["POST /api/webhook/telegram<br/>Receive messages<br/>(Telegram payload)"]
end
subgraph WHATSAPP_FLOW["WHATSAPP FLOW"]
WH_WA_GET --> VERIFY{Verify Token<br/>Matches?}
VERIFY -->|Yes| RETURN_CHALLENGE["Return hub.challenge<br/>200 OK"]
VERIFY -->|No| ERR_403["403 Forbidden"]
WH_WA_POST --> VERIFY_WA_HUB{"Hub Mode = 'subscribe'?"}
%% Perbaikan Baris 15 & 16: Dipisah ke baris baru
VERIFY_WA_HUB -->|Yes| RETURN_200["200 OK No processing"]
VERIFY_WA_HUB -->|No| PARSE_WA["Parse WhatsApp Payload<br/>Extract: entry.changes<br/>messages, statuses"]
PARSE_WA --> MSG_TYPE{Message Type?}
MSG_TYPE -->|"messages"| TO_QUEUE["Publish to RabbitMQ<br/>Queue: omnichannel_work_queue<br/>Route: whatsapp"]
MSG_TYPE -->|"statuses"| PROCESS_STATUS["Process Delivery Status<br/>UPDATE conversation_messages<br/>status: SENT → DELIVERED → READ"]
TO_QUEUE --> RABBIT_MQ["RabbitMQ<br/>amqplib connection"]
end
subgraph RABBIT_WORKER["RABBITMQ WORKER"]
RABBIT_MQ --> CONSUME["Consume from Queue"]
CONSUME --> ACK{"Process<br/>Success?"}
ACK -->|Yes| ACK_MSG["ack message"]
ACK -->|No| NACK["nack + requeue<br/>(or dead-letter)"]
NACK --> CONSUME
end
subgraph DIRECT_PROCESSING["DIRECT PROCESSING<br/>(Instagram, Telegram)"]
WH_IG --> PARSE_IG["Parse Instagram Payload"]
PARSE_IG --> IG_TYPE{Event Type?}
%% Perbaikan: Menghapus karakter asteris (*) yang bisa mengganggu parser
IG_TYPE -->|messages| INSTA_DM["Process Instagram DM<br/>→ conversation engine"]
IG_TYPE -->|comments| INSTA_COMMENT["Process Comment<br/>→ conversation engine<br/>context: POST_COMMENT"]
WH_TG --> PARSE_TG["Parse Telegram Payload"]
PARSE_TG --> TG_PROCESS["Process Telegram Message<br/>→ conversation engine"]
end
subgraph SHARED_PROCESSOR["SHARED MESSAGE PROCESSOR"]
TO_QUEUE --> PROCESSOR["Message Processor Service"]
INSTA_DM --> PROCESSOR
INSTA_COMMENT --> PROCESSOR
TG_PROCESS --> PROCESSOR
PROCESSOR --> CONTACT_RESOLVE["Resolve/Create Contact"]
PROCESSOR --> CONV_RESOLVE["Resolve/Create Conversation"]
PROCESSOR --> SAVE_MESSAGE["Save Message + Attachments"]
PROCESSOR --> CHECK_AUTO_RESP["Check Auto Response"]
PROCESSOR --> NOTIFY_RT["Real-time Notification"]
end
PROCESS_STATUS --> DB_UPDATE["Direct DB Update"]
DB_UPDATE --> DB[(PostgreSQL)]
SHARED_PROCESSOR --> DB
style INCOMING fill:#E8F5E9,stroke:#4CAF50
style WHATSAPP_FLOW fill:#FFF3E0,stroke:#FF9800
style RABBIT_WORKER fill:#FFEBEE,stroke:#F44336
style DIRECT_PROCESSING fill:#E3F2FD,stroke:#1976D2
style SHARED_PROCESSOR fill:#F3E5F5,stroke:#9C27B0
```
---
## 4.8 BE Service Module Pattern
Pattern yang digunakan di setiap service module Backend.
```mermaid
flowchart TD
subgraph MODULE["Service Module Structure"]
direction TB
ROUTE["*.route.ts<br/>───────────────<br/>Define Hono routes<br/>Apply middleware<br/>Call service functions<br/>Return responses"]
SVC["*.service.ts<br/>───────────────<br/>Business logic<br/>Orchestrate repos<br/>Handle errors<br/>Trigger notifications"]
REPO["*.repository.ts<br/>───────────────<br/>Drizzle ORM queries<br/>INSERT / SELECT<br/>UPDATE / DELETE<br/>Transactions"]
VALID["*.validation.ts<br/>───────────────<br/>Zod schemas<br/>Input validation<br/>Output parsing"]
ERR["*.error.ts<br/>───────────────<br/>Custom error classes<br/>Error codes<br/>Error messages"]
TYPE["*.type.ts<br/>───────────────<br/>TypeScript types<br/>Interfaces<br/>Enums"]
end
ROUTE -->|"Validate input"| VALID
ROUTE -->|"Call business logic"| SVC
SVC -->|"Query database"| REPO
SVC -->|"Throw errors"| ERR
REPO -->|"Read types"| TYPE
ROUTE -->|"Read types"| TYPE
SVC -->|"Read types"| TYPE
subgraph EXAMPLE["Example: conversation-message"]
EX_ROUTE["conversation-message.route.ts<br/>GET / → listMessages()<br/>POST / → sendMessage()<br/>POST /template → sendTemplate()"]
EX_SVC["conversation-message.service.ts<br/>sendMessage():<br/> 1. Validate profanity<br/> 2. Check 24h window<br/> 3. Send to platform API<br/> 4. Save to DB<br/> 5. Notify real-time"]
EX_REPO["conversation-message.repository.ts<br/>findById()<br/>findByConversationId()<br/>create()<br/>updateStatus()"]
EX_VALID["conversation-message.validation.ts<br/>sendMessageSchema<br/>sendTemplateSchema<br/>listMessagesSchema"]
end
EX_ROUTE --> EX_SVC
EX_SVC --> EX_REPO
EX_ROUTE --> EX_VALID
style MODULE fill:#F5F5F5,stroke:#9E9E9E
style EXAMPLE fill:#E3F2FD,stroke:#1976D2
```
---
## 4.9 BE Error Handling Flow
```mermaid
flowchart TD
REQUEST["Request Handler"] --> TRY["try { ... }"]
TRY --> HANDLER["Business Logic"]
HANDLER --> SUCCESS{Success?}
SUCCESS -->|Yes| RETURN_OK["response.util.success(data)<br/>200 OK"]
SUCCESS -->|No| ERROR_CATCH["catch (error)"]
ERROR_CATCH --> ERROR_TYPE{Error Type?}
ERROR_TYPE -->|"AppError"| APP_ERR["Custom App Error<br/>status + message + code"]
ERROR_TYPE -->|"HTTPException"| HTTP_ERR["Hono HTTP Exception"]
ERROR_TYPE -->|"DrizzleQueryError"| DB_ERR["Database Query Error"]
ERROR_TYPE -->|"PostgresError"| PG_ERR["PostgreSQL Error<br/>(unique violation, fk, etc.)"]
ERROR_TYPE -->|"ZodError"| ZOD_ERR["Validation Error<br/>Input format invalid"]
ERROR_TYPE -->|"Unknown"| UNKNOWN_ERR["Unknown Error"]
APP_ERR --> MAP_STATUS["Map to HTTP Status"]
HTTP_ERR --> MAP_STATUS
DB_ERR --> LOG_ERR["Log Error<br/>console.error"]
PG_ERR --> LOG_ERR
ZOD_ERR --> MAP_400["400 Bad Request<br/>Validation error details"]
UNKNOWN_ERR --> LOG_ERR
LOG_ERR --> MAP_500["500 Internal Server Error"]
MAP_STATUS --> RESPONSE["Return Error Response<br/>response.util.error()"]
MAP_400 --> RESPONSE
MAP_500 --> RESPONSE
style REQUEST fill:#C8E6C9,stroke:#4CAF50
style ERROR_CATCH fill:#FFCDD2,stroke:#F44336
style RESPONSE fill:#BBDEFB,stroke:#2196F3
```
# BAGIAN 5: RINGKASAN & TABEL REFERENSI
## Summary Tables & Database Schema
---
## 5.1 Ringkasan 5 Fitur Utama
| # | Fitur | Deskripsi Singkat | Kompleksitas |
|---|-------|-------------------|-------------|
| 1 | **Auth** | Autentikasi user dengan Better-Auth, session management, RBAC 3 role (owner/admin/member), forgot/reset password, API key authentication | Tinggi |
| 2 | **Chat** | Real-time omnichannel messaging (WhatsApp/Instagram/Telegram), conversation management (assign/resolve), message templates, file attachments, internal notes, office hours auto-response, profanity filter | Sangat Tinggi |
| 3 | **Campaign** | WhatsApp marketing campaign builder, template management (Meta Resumable API), recipient list dengan variable personalization, batch execution, delivery tracking | Tinggi |
| 4 | **Ticket** | Issue tracking terhubung dengan conversation/contact, full lifecycle (TODO → IN_PROGRESS → RESOLVED → DONE/REJECTED), attachment, status history, batch operations | Sedang |
| 5 | **Settings** | Konfigurasi workspace: user management, channel connection (WhatsApp Embedded Signup, Instagram Business Login), office hours, quick reply, API keys, tags, resolve categories, role permissions | Tinggi |
---
## 5.2 Technology Stack Comparison
| Layer | Frontend (FE) | Backend (BE) |
|-------|--------------|--------------|
| **Runtime** | Browser + Vite Dev Server | Bun (HTTP Server) |
| **Framework** | React 19.2 | Hono 4.10 |
| **Language** | TypeScript 5.9 | TypeScript |
| **UI Library** | MUI v7.3, Emotion | — |
| **State/Data** | SWR 2.3, React Context | — |
| **HTTP Client** | Axios 1.11 | Hono built-in |
| **Validation** | Zod 4.0 + @hookform/resolvers | Zod 4.1 + drizzle-zod |
| **Forms** | React Hook Form 7.62 | — |
| **ORM** | — | Drizzle ORM 0.44 |
| **Database** | — | PostgreSQL (pg driver) |
| **Auth** | better-auth client 1.3 | better-auth server 1.3 |
| **Real-time** | Pusher-js 8.4, Socket.io-client 4.8, react-use-websocket 4.13 | Pusher 5.2, Socket.IO 4.8 |
| **Message Queue** | — | RabbitMQ (amqplib 0.10) |
| **Routing** | react-router v7.8 | Hono router |
| **Rich Text** | TipTap v3, Quill 2.0 | — |
| **Charts** | ApexCharts 5.3 | — |
| **i18n** | i18next 25.3 | — |
| **Animations** | Framer Motion 12.23 | — |
| **PDF** | @react-pdf/renderer 4.3 | — |
| **Package Manager** | Yarn 4.12 | Bun |
| **Build Tool** | Vite 7.1 | Bun |
| **Process Manager** | PM2 (pm2.config.cjs) | PM2 (pm2.config.js) |
| **Container** | Docker | Docker |
| **CI/CD** | GitLab CI | GitLab CI |
---
## 5.3 Role-Based Access Control Matrix
| Menu / Fitur | owner | admin | member | Endpoint Pattern |
|---|:---:|:---:|:---:|---|
| **Login / Register** | ✓ | ✓ | ✓ | `/api/v1/auth/*` |
| **Dashboard — Chat** | ✓ | ✓ | ✓ | `/api/v1/u/conversations/*` |
| **Dashboard — Social Media** | ✓ | ✓ | ✓ | `/api/v1/u/posts/*` |
| **Dashboard — Campaign** | ✓ | ✓ | ✓ | `/api/v1/u/campaigns/*` |
| **Dashboard — Contact** | ✓ | ✓ | ✓ | `/api/v1/u/contacts/*` |
| **Dashboard — Tickets** | ✓ | ✓ | ✓ | `/api/v1/u/tickets/*` |
| **Settings — User Management** | ✓ | ✓ | ✗ | `/api/v1/auth/admin/*` |
| **Settings — Role & Permission** | ✓ | ✓ | ✗ | `/api/v1/u/role-permissions/*` |
| **Settings — Channel Management** | ✓ | ✓ | ✗ | `/api/v1/u/channels/*` |
| **Settings — Workspace Management** | ✓ | ✗ | ✗ | `/api/v1/u/workspaces/*` |
| **Settings — Workspace Members** | ✓ | ✓ | ✗ | `/api/v1/u/workspace-members/*` |
| **Settings — Tags** | ✓ | ✓ | ✗ | `/api/v1/u/tags/*` |
| **Settings — Office Hours** | ✓ | ✓ | ✗ | `/api/v1/u/office-hour-*` |
| **Settings — Quick Reply** | ✓ | ✓ | ✗ | `/api/v1/u/message-templates/*` |
| **Settings — API Keys** | ✓ | ✓ | ✗ | `/api/v1/u/workspace-api-keys/*` |
| **Settings — Resolve Category** | ✓ | ✓ | ✗ | `/api/v1/u/conversation-resolve-category/*` |
| **Settings — My Account** | ✓ | ✓ | ✓ | `/api/v1/u/user/*` |
| **Settings — Change Password** | ✓ | ✓ | ✓ | `/api/v1/auth/change-password` |
---
## 5.4 Complete API Endpoint Catalog
### Auth Endpoints (`/api/v1/auth/*`)
| Method | Endpoint | Auth | Deskripsi |
|--------|----------|------|-----------|
| POST | `/auth/sign-in/email-password` | None | Login |
| POST | `/auth/sign-up/email-password` | None | Register (disabled) |
| POST | `/auth/forget-password` | None | Lupa password |
| POST | `/auth/reset-password` | None | Reset password |
| POST | `/auth/change-password` | Session | Ganti password |
| GET | `/auth/get-session` | Session | Cek session |
| GET | `/auth/admin/list-users` | Session + Admin | List semua user |
| GET | `/auth/admin/get-user` | Session + Admin | Detail user |
| POST | `/auth/admin/create-user` | Session + Admin | Buat user |
| POST | `/auth/admin/update-user` | Session + Admin | Update user |
| POST | `/auth/admin/set-role` | Session + Admin | Set role user |
| POST | `/auth/admin/unban-user` | Session + Admin | Unban user |
| GET | `/auth/organization/list-members` | Session | List member org |
| POST | `/auth/organization/invite-member` | Session + Admin | Invite member |
| POST | `/auth/organization/update-member-role` | Session + Admin | Update role member |
| POST | `/auth/organization/remove-member` | Session + Admin | Hapus member |
| POST | `/auth/organization/accept-invitation` | Session | Terima invitation |
| POST | `/auth/api-key/create` | Session + Admin | Buat API key |
### Channel Endpoints (`/api/v1/u/channels`)
| Method | Endpoint | Permission | Deskripsi |
|--------|----------|-----------|-----------|
| GET | `/channels` | channel:read | List channels |
| GET | `/channels/with-unread-count` | channel:read | Channels + unread count |
| GET | `/channels/:id` | channel:read | Detail channel |
| POST | `/channels` | channel:create | Buat channel |
| PUT | `/channels/:id` | channel:update | Update channel |
| DELETE | `/channels/:id` | channel:delete | Hapus channel |
| POST | `/channels/:id/generate-webhook` | channel:update | Generate webhook token |
| POST | `/channels/whatsapp` | channel:create | Connect WhatsApp |
| GET | `/channels/whatsapp/get-access-token` | channel:read | Get WA access token |
| POST | `/channels/instagram` | channel:create | Connect Instagram |
| POST | `/channels/:id/upload-icon` | channel:update | Upload icon |
### Conversation Endpoints (`/api/v1/u/conversations`)
| Method | Endpoint | Permission | Deskripsi |
|--------|----------|-----------|-----------|
| GET | `/conversations` | conversation:read | List conversations |
| GET | `/conversations/total-counts-chat` | conversation:read | Total counts |
| GET | `/conversations/:id` | conversation:read | Detail conversation |
| GET | `/conversations/:id/history` | conversation:read | History chain |
| PATCH | `/conversations/:id/status` | conversation:update_status | Update status |
| PUT | `/conversations/:id/history` | — | Update resolve categories |
| POST | `/conversations/:id/read` | conversation:read | Mark as read |
| POST | `/conversations/get-new-chat` | conversation:update_status | Claim next chat |
| POST | `/conversations/bulk-resolve` | — | Bulk resolve |
### Message Endpoints (`/api/v1/u/conversation-messages`)
| Method | Endpoint | Permission | Deskripsi |
|--------|----------|-----------|-----------|
| GET | `/conversation-messages` | message:read | List messages |
| POST | `/conversation-messages` | message:create | Kirim pesan |
| POST | `/conversation-messages/template` | message:create | Kirim template |
| PUT | `/conversation-messages/:id/internal-note` | message:internal_note_update | Update note |
| DELETE | `/conversation-messages/:id/internal-note` | message:internal_note_update | Hapus note |
| POST | `/conversation-messages/upload-media` | message:create | Upload media |
| DELETE | `/conversation-messages/:id` | message:create | Hapus pesan |
### Campaign Endpoints (`/api/v1/u/campaigns`)
| Method | Endpoint | Permission | Deskripsi |
|--------|----------|-----------|-----------|
| GET | `/campaigns` | — | List campaigns |
| GET | `/campaigns/:id` | — | Detail campaign |
| POST | `/campaigns` | — | Buat campaign |
| PUT | `/campaigns/:id` | — | Update campaign |
| DELETE | `/campaigns/:id` | — | Hapus campaign |
| POST | `/campaigns/:id/execute` | — | Eksekusi campaign |
### Template Endpoints (`/api/v1/u/templates`)
| Method | Endpoint | Permission | Deskripsi |
|--------|----------|-----------|-----------|
| GET | `/templates` | template:read | List templates |
| GET | `/templates/:id` | template:read | Detail template |
| POST | `/templates` | template:create | Buat template |
| POST | `/templates/ticket` | template:create | Buat ticket template |
| PUT | `/templates/:id` | template:update | Update template |
| DELETE | `/templates/:id` | template:delete | Hapus template |
| POST | `/templates/upload-media` | template:create | Upload media (Meta API) |
| POST | `/templates/:id/sync` | template:create | Sync ke WhatsApp |
### Ticket Endpoints (`/api/v1/u/tickets`)
| Method | Endpoint | Permission | Deskripsi |
|--------|----------|-----------|-----------|
| GET | `/tickets` | ticket:read | List tickets |
| GET | `/tickets/:id` | ticket:read | Detail ticket |
| POST | `/tickets` | ticket:create | Buat ticket |
| PUT | `/tickets/:id` | ticket:update | Update ticket |
| DELETE | `/tickets/:id` | ticket:delete | Hapus ticket |
| POST | `/tickets/upload-media` | ticket:create | Upload media |
| DELETE | `/tickets/remove-media/:id` | — | Hapus media |
### Recipient List Endpoints
| Method | Endpoint | Deskripsi |
|--------|----------|-----------|
| GET | `/campaign-recipient-lists` | List recipient lists |
| GET | `/campaign-recipient-lists/:id` | Detail recipient list |
| POST | `/campaign-recipient-lists` | Buat recipient list |
| PUT | `/campaign-recipient-lists/:id` | Update recipient list |
| DELETE | `/campaign-recipient-lists/:id` | Hapus recipient list |
| GET | `/campaign-recipient-list-items` | List items |
| POST | `/campaign-recipient-lists/:id/contacts` | Tambah kontak |
| DELETE | `/campaign-recipient-lists/:id/contacts` | Hapus kontak |
---
## 5.5 Database Schema — Entity Relationship Diagram
```mermaid
erDiagram
users {
uuid user_id PK
varchar user_email "NOT NULL, INDEXED"
varchar user_username "NOT NULL, INDEXED"
varchar user_fullname
varchar user_phone
boolean user_is_email_verified "default: false"
varchar user_image_url
varchar user_role
boolean user_is_banned "default: false"
text user_ban_reason
timestamp user_ban_expires
varchar user_status "OFFLINE / ONLINE"
timestamp created_at
timestamp updated_at
timestamp deleted_at
uuid created_by "FK → users"
uuid updated_by "FK → users"
uuid deleted_by "FK → users"
}
workspaces {
uuid workspace_id PK
varchar workspace_name "NOT NULL"
varchar workspace_slug "UNIQUE"
varchar workspace_logo
integer session_time "default: 604800 (7 days)"
timestamp created_at
timestamp updated_at
timestamp deleted_at
uuid created_by "FK → users"
uuid updated_by "FK → users"
uuid deleted_by "FK → users"
}
workspace_members {
uuid workspace_member_id PK
uuid workspace_id FK "FK → workspaces, CASCADE"
uuid user_id FK "FK → users, CASCADE"
text workspace_member_role "default: member"
varchar workspace_member_status "ACTIVE / INACTIVE / SUSPENDED"
boolean is_developer "default: false"
boolean is_devops "default: false"
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
workspace_invitations {
uuid workspace_invitation_id PK
uuid workspace_id FK "FK → workspaces"
varchar workspace_invitation_email
varchar workspace_invitation_role
varchar workspace_invitation_status "default: pending"
boolean is_developer
boolean is_devops
timestamp expires_at
}
sessions {
uuid session_id PK
varchar session_token "UNIQUE"
uuid session_active_workspace_id
uuid user_id FK "FK → users, CASCADE"
timestamp expires_at
}
api_keys {
uuid id PK
text name
text start
text prefix
text key
uuid user_id FK "FK → users"
text permissions "JSON string"
jsonb metadata "workspaceId, appName"
boolean rate_limit_enabled
integer rate_limit_time_window
integer rate_limit_max
}
channels {
uuid channel_id PK
varchar channel_name "NOT NULL"
varchar channel_code "WHATSAPP / INSTAGRAM / TELEGRAM / etc."
varchar channel_icon
varchar channel_platform_id
varchar channel_waba_id
text channel_api_token
varchar channel_api_webhook_token
varchar channel_status "ACTIVE / INACTIVE / MAINTENANCE"
jsonb metadata
jsonb channel_instagram_permission
uuid workspace_id FK "FK → workspaces"
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
member_channels {
uuid member_channel_id PK
uuid workspace_member_id FK "FK → workspace_members"
uuid channel_id FK "FK → channels"
timestamp created_at
}
contacts {
uuid contact_id PK
varchar contact_name "NOT NULL"
varchar contact_phone "NOT NULL"
varchar contact_username
varchar contact_email
varchar contact_location
boolean contact_is_testing
uuid channel_id FK "FK → channels"
uuid workspace_id FK "FK → workspaces"
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
tags {
uuid tag_id PK
varchar tag_name "UNIQUE"
varchar tag_color "default: #3B82F6"
uuid workspace_id FK "FK → workspaces"
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
contact_tags {
uuid contact_tag_id PK
uuid contact_id FK "FK → contacts"
uuid tag_id FK "FK → tags"
timestamp created_at
}
conversations {
uuid conversation_id PK
uuid conversation_from
uuid conversation_to
varchar conversation_status "UNASSIGNED / ASSIGNED / RESOLVED / CLOSED"
text conversation_last_message
timestamp conversation_last_message_at
boolean conversation_is_testing
timestamp conversation_first_response_at
uuid conversation_first_response_by
timestamp conversation_24h_window_start
timestamp conversation_24h_window_end
uuid previous_conversation_id FK "self-ref (chain)"
uuid assigned_to FK "FK → users"
uuid assigned_by FK "FK → users"
timestamp assigned_at
timestamp resolved_at
uuid resolved_by FK "FK → users"
text resolved_description
uuid channel_id FK "FK → channels"
uuid contact_id FK "FK → contacts"
varchar conversation_raw_id
uuid read_by FK "FK → users"
integer unread_counts "default: 0"
varchar conversation_context "DM / POST_COMMENT / STORY_COMMENT"
uuid instagram_post_id FK "FK → instagram_posts"
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
conversation_messages {
uuid conversation_message_id PK
uuid conversation_id FK "FK → conversations"
uuid conversation_message_from
uuid conversation_message_to
text conversation_message_content
integer conversation_message_type_id FK "FK → conversation_message_types"
boolean conversation_message_is_system "default: false"
integer conversation_message_system_type_id FK "FK → conversation_message_system_types"
varchar conversation_message_status "SENT / DELIVERED / READ / FAILED"
uuid replied_to_message_id FK "self-ref (reply)"
jsonb conversation_message_template_components
boolean is_answered
uuid answered_by
timestamp answered_at
uuid created_by FK "FK → users"
timestamp created_at
timestamp updated_at
}
message_attachments {
uuid attachment_id PK
uuid conversation_message_id FK "FK → conversation_messages, CASCADE"
varchar attachment_file_name
bigint file_size
varchar mime_type
varchar file_url
varchar attachment_storage_type "LOCAL / S3 / GCS / AZURE"
jsonb attachment_metadata
timestamp expired_at
timestamp created_at
}
conversation_message_reactions {
uuid conversation_message_reaction_id PK
uuid conversation_message_id FK "FK → conversation_messages"
varchar reaction_type
varchar emoji
varchar sender_id
timestamp created_at
}
tickets {
uuid ticket_id PK
uuid conversation_id FK "FK → conversations"
varchar ticket_title "NOT NULL"
text ticket_description "NOT NULL"
text ticket_error_info
varchar ticket_status "TODO / IN_PROGRESS / RESOLVED / DONE / REJECTED / DELETED"
varchar ticket_number
uuid assigned_to FK "FK → users"
uuid assigned_by FK "FK → users"
timestamp assigned_at
uuid resolved_by FK "FK → users"
timestamp resolved_at
text resolved_note
uuid rejected_by FK "FK → users"
timestamp rejected_at
text reject_reason
uuid closed_by FK "FK → users"
timestamp closed_at
text reassign_note
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
ticket_attachments {
uuid ticket_attachment_id PK
uuid ticket_id FK "FK → tickets"
varchar attachment_file_name
bigint file_size
varchar mime_type
varchar file_url
timestamp created_at
}
ticket_status_history {
bigint ticket_status_history_id PK
uuid ticket_id FK "FK → tickets"
varchar old_status
varchar new_status
uuid updated_by FK "FK → users"
uuid assigned_to FK "FK → users"
uuid resolved_by FK "FK → users"
uuid rejected_by FK "FK → users"
timestamp created_at
}
templates {
uuid template_id PK
varchar template_name
varchar template_language
varchar template_category "MARKETING / UTILITY / AUTHENTICATION"
varchar template_status "APPROVED / REJECTED / PENDING / PAUSED"
varchar template_whatsapp_id
jsonb template_components
varchar template_quality_score "GREEN / YELLOW / RED / UNKNOWN"
uuid channel_id FK "FK → channels"
boolean template_is_used_in_campaign
boolean template_is_ticket "default: false"
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
campaigns {
uuid campaign_id PK
varchar campaign_name
text campaign_description
varchar campaign_status "DRAFT / SCHEDULED / RUNNING / COMPLETED / CANCELLED"
uuid template_id FK "FK → templates"
timestamp campaign_scheduled_at
timestamp campaign_started_at
timestamp campaign_completed_at
integer campaign_total_recipients
integer campaign_sent_count
integer campaign_failed_count
integer campaign_delivered_count
integer campaign_read_count
uuid channel_id FK "FK → channels"
uuid workspace_id FK "FK → workspaces"
uuid campaign_recipient_list_id FK "FK → campaign_recipient_lists"
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
campaign_recipient_lists {
uuid campaign_recipient_list_id PK
varchar campaign_recipient_list_name
text campaign_recipient_list_description
jsonb campaign_recipient_list_variables
uuid workspace_id FK "FK → workspaces"
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
campaign_recipient_list_items {
uuid campaign_recipient_list_item_id PK
uuid campaign_recipient_list_id FK "FK → campaign_recipient_lists"
uuid contact_id FK "FK → contacts"
jsonb variable_values
timestamp created_at
}
campaign_recipients {
uuid campaign_recipient_id PK
uuid campaign_id FK "FK → campaigns"
uuid contact_id FK "FK → contacts"
uuid conversation_id FK "FK → conversations"
uuid conversation_message_id FK "FK → conversation_messages"
varchar status "PENDING / SENT / DELIVERED / READ / FAILED"
timestamp created_at
timestamp updated_at
}
message_templates {
serial message_template_id PK
varchar message_template_name
text message_template_content
varchar message_template_shortcut
uuid workspace_id FK "FK → workspaces"
uuid channel_id FK "FK → channels"
jsonb message_template_variables
boolean message_template_is_global
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
notes {
uuid note_id PK
varchar note_title
text note_description
uuid workspace_id FK "FK → workspaces"
uuid contact_id FK "FK → contacts"
uuid created_by FK "FK → users"
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
conversation_resolve_category {
uuid resolve_category_id PK
varchar resolve_category_name
varchar resolve_category_color
text resolve_category_description
uuid workspace_id FK "FK → workspaces"
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
conversation_resolve_categories {
uuid conversation_resolve_category_id PK
uuid conversation_id FK "FK → conversations"
uuid resolve_category_id FK "FK → conversation_resolve_category"
timestamp created_at
}
office_hours_schedule {
uuid office_hours_schedule_id PK
uuid workspace_id FK "FK → workspaces"
integer day_of_week "0-6"
time start_time
time end_time
varchar timezone
boolean is_active "default: true"
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
channel_office_hours_schedule {
uuid channel_office_hours_schedule_id PK
uuid channel_id FK "FK → channels"
integer day_of_week "0-6"
time start_time
time end_time
varchar timezone
boolean is_active "default: true"
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
office_hours_auto_responses {
uuid office_hours_auto_response_id PK
uuid workspace_id FK "FK → workspaces"
text during_office_hours_message
text outside_office_hours_message
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
channel_office_hours_auto_responses {
uuid channel_office_hours_auto_response_id PK
uuid channel_id FK "FK → channels"
text during_office_hours_message
text outside_office_hours_message
timestamp created_at
timestamp updated_at
timestamp deleted_at
}
users ||--o{ sessions : "has"
users ||--o{ workspace_members : "belongs_to"
users ||--o{ api_keys : "owns"
users ||--o{ workspace_invitations : "invited by"
workspaces ||--o{ workspace_members : "contains"
workspaces ||--o{ channels : "has"
workspaces ||--o{ tags : "has"
workspaces ||--o{ office_hours_schedule : "has"
workspaces ||--o{ office_hours_auto_responses : "has"
workspaces ||--o{ campaigns : "has"
workspaces ||--o{ message_templates : "has"
workspaces ||--o{ conversation_resolve_category : "has"
workspaces ||--o{ campaign_recipient_lists : "has"
workspaces ||--o{ notes : "has"
channels ||--o{ contacts : "receives"
channels ||--o{ conversations : "has"
channels ||--o{ templates : "has"
channels ||--o{ campaigns : "uses"
channels ||--o{ member_channels : "has"
channels ||--o{ channel_office_hours_schedule : "has"
channels ||--o{ channel_office_hours_auto_responses : "has"
contacts ||--o{ conversations : "initiates"
contacts ||--o{ contact_tags : "has"
contacts ||--o{ campaign_recipient_list_items : "included in"
contacts ||--o{ notes : "has"
tags ||--o{ contact_tags : "applied to"
conversations ||--o{ conversation_messages : "contains"
conversations ||--o{ tickets : "generates"
conversations ||--o{ conversation_resolve_categories : "categorized"
conversations ||--o| conversations : "chained"
conversation_messages ||--o{ message_attachments : "has"
conversation_messages ||--o{ conversation_message_reactions : "has"
conversation_messages ||--o| conversation_messages : "replies to"
tickets ||--o{ ticket_attachments : "has"
tickets ||--o{ ticket_status_history : "tracks"
templates ||--o{ campaigns : "used by"
campaigns ||--o{ campaign_recipients : "sends to"
campaign_recipient_lists ||--o{ campaign_recipient_list_items : "contains"
conversation_resolve_category ||--o{ conversation_resolve_categories : "links"
workspace_members ||--o{ member_channels : "has access"
```
---
## 5.6 Statistik Final
| Metrik | Jumlah |
|--------|--------|
| **Total API Endpoints** | ~60+ |
| **Total Service Modules (BE)** | 47 |
| **Total Database Tables** | 30+ |
| **Total FE Component Files** | ~200+ |
| **Total FE Hooks** | ~30+ |
| **Total FE Pages** | ~40+ |
| **Chat Module Files (FE)** | 82 |
| **Campaign Module Files (FE)** | 33 |
| **Ticket Module Files (FE)** | 25 |
| **Settings Pages** | 20+ |
| **Auth Methods Supported** | 6 (better-auth active, 5备用) |
| **External Channel Integrations** | 3 active (WA, IG, TG) |
| **Real-time Mechanisms** | 3 (Pusher, Socket.IO, WebSocket) |
| **User Roles** | 3 (owner, admin, member) |
| **Permission Resources** | ~10 (channel, contact, conversation, message, ticket, template, campaign, tag, note, workspace) |
| **Conversation Statuses** | 4 (UNASSIGNED, ASSIGNED, RESOLVED, CLOSED) |
| **Ticket Statuses** | 6 (TODO, IN_PROGRESS, RESOLVED, DONE, REJECTED, DELETED) |
| **Campaign Statuses** | 5 (DRAFT, SCHEDULED, RUNNING, COMPLETED, CANCELLED) |
---
## 5.7 Daftar File Report
| # | File | Isi |
|---|------|-----|
| 1 | `00-EXECUTIVE-SUMMARY.md` | Ringkasan eksekutif, daftar isi, statistik |
| 2 | `01-OVERALL-ARCHITECTURE.md` | Arsitektur sistem, user journey, navigasi, data flow, multi-channel message flow |
| 3 | `02-INTEGRATION-FLOW.md` | API integration map, real-time flow, auth integration, file upload flow, middleware stack |
| 4 | `03-FE-FLOWCHARTS.md` | FE routing, auth flow, chat flow (82 files detail), campaign flow, ticket flow, settings flow, SWR data fetching pattern |
| 5 | `04-BE-FLOWCHARTS.md` | BE service architecture, auth RBAC, chat engine, conversation status, campaign execution, ticket lifecycle, webhook processing, service module pattern, error handling |
| 6 | `05-SUMMARY-TABLES.md` | Ringkasan fitur, tech stack, RBAC matrix, API catalog, database ERD, statistik final |
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment