Phase 2 scaffold: mitra online status & pairing logic
Add mitra online/offline status with heartbeat-based auto-offline, customer-mitra pairing via Valkey pub/sub blast, session management, and control center dashboard with real-time stats. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
322
requirement/phase2-plan.md
Normal file
322
requirement/phase2-plan.md
Normal file
@@ -0,0 +1,322 @@
|
||||
# Phase 2 Implementation Plan: Mitra Online Status & Pairing Logic
|
||||
|
||||
## Summary of Clarified Requirements
|
||||
|
||||
| Topic | Decision |
|
||||
|---|---|
|
||||
| Mitra default status | Offline on app open; must explicitly toggle online |
|
||||
| Auto-offline | Yes — on app close / connection loss |
|
||||
| Online/offline logs | Backend + control center only; mitra don't see their own logs |
|
||||
| Multi-device | No — single device per mitra |
|
||||
| Customer active requests | One at a time; one active session at a time |
|
||||
| Search timeout | 60 seconds, then "no bestie available" message |
|
||||
| Cancel while searching | Yes |
|
||||
| Pairing blast | Valkey pub/sub (in-app real-time); push notifications deferred |
|
||||
| Race condition | First-come-first-served |
|
||||
| Mitra decline/ignore | Allowed; request stays open until 60s timeout or customer cancel |
|
||||
| Session end | Explicit end only (time-based deferred to next phase) |
|
||||
| Payment placeholder | `pending_payment` status, skip to `active` for now |
|
||||
| Max customer per mitra | Global config (not per-mitra) |
|
||||
| Reroute | Forced assignment (acceptance-based deferred) |
|
||||
| Reroute target | Online mitras only |
|
||||
| Dashboard refresh | Polling-based auto-refresh now; SSE/push later |
|
||||
|
||||
---
|
||||
|
||||
## 1. Database Changes
|
||||
|
||||
### 1.1 New table: `mitra_online_status`
|
||||
|
||||
Tracks the current online/offline state per mitra.
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| `id` | `SERIAL PRIMARY KEY` | |
|
||||
| `mitra_id` | `INT REFERENCES mitras(id)` | Unique — one row per mitra |
|
||||
| `is_online` | `BOOLEAN DEFAULT false` | |
|
||||
| `last_online_at` | `TIMESTAMPTZ` | Last time they went online |
|
||||
| `last_offline_at` | `TIMESTAMPTZ` | Last time they went offline |
|
||||
| `updated_at` | `TIMESTAMPTZ` | |
|
||||
|
||||
### 1.2 New table: `mitra_online_logs`
|
||||
|
||||
Append-only log for control center reporting.
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| `id` | `SERIAL PRIMARY KEY` | |
|
||||
| `mitra_id` | `INT REFERENCES mitras(id)` | |
|
||||
| `status` | `VARCHAR` | `'online'` or `'offline'` |
|
||||
| `timestamp` | `TIMESTAMPTZ DEFAULT now()` | |
|
||||
|
||||
### 1.3 New table: `chat_sessions`
|
||||
|
||||
Tracks pairing and session lifecycle.
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| `id` | `SERIAL PRIMARY KEY` | |
|
||||
| `customer_id` | `INT REFERENCES customers(id)` | |
|
||||
| `mitra_id` | `INT REFERENCES mitras(id)` | Nullable — null while searching |
|
||||
| `status` | `VARCHAR` | See statuses below |
|
||||
| `created_at` | `TIMESTAMPTZ DEFAULT now()` | |
|
||||
| `paired_at` | `TIMESTAMPTZ` | When mitra accepted |
|
||||
| `ended_at` | `TIMESTAMPTZ` | When session explicitly ended |
|
||||
| `ended_by` | `VARCHAR` | `'customer'`, `'mitra'`, or `'system'` |
|
||||
|
||||
**Session statuses:**
|
||||
- `searching` — customer requested, waiting for mitra
|
||||
- `pending_acceptance` — blast sent, waiting for a mitra to accept
|
||||
- `pending_payment` — mitra accepted, payment placeholder (auto-skip for now)
|
||||
- `active` — session in progress
|
||||
- `completed` — ended explicitly
|
||||
- `cancelled` — customer cancelled during search
|
||||
- `expired` — 60s timeout, no mitra accepted
|
||||
- `rerouted` — session reassigned by control center (old record status)
|
||||
|
||||
### 1.4 New table: `chat_request_notifications`
|
||||
|
||||
Tracks which mitras were notified of a pairing request (for blast tracking).
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| `id` | `SERIAL PRIMARY KEY` | |
|
||||
| `session_id` | `INT REFERENCES chat_sessions(id)` | |
|
||||
| `mitra_id` | `INT REFERENCES mitras(id)` | |
|
||||
| `notified_at` | `TIMESTAMPTZ DEFAULT now()` | |
|
||||
| `response` | `VARCHAR` | `'accepted'`, `'declined'`, `'ignored'` |
|
||||
| `responded_at` | `TIMESTAMPTZ` | |
|
||||
|
||||
### 1.5 Extend `app_config`
|
||||
|
||||
Add new config key:
|
||||
|
||||
| Key | Value (JSONB) | Purpose |
|
||||
|---|---|---|
|
||||
| `max_customers_per_mitra` | `{ "value": 3 }` | Global max concurrent sessions per mitra |
|
||||
|
||||
---
|
||||
|
||||
## 2. Backend Changes
|
||||
|
||||
### 2.1 Valkey (Redis-compatible) Setup
|
||||
|
||||
- Add Valkey client as Fastify plugin (`src/plugins/valkey.js`)
|
||||
- Used for pub/sub channels:
|
||||
- `mitra:{mitra_id}:requests` — notify specific mitra of incoming chat requests
|
||||
- `session:{session_id}:status` — notify customer of session status changes
|
||||
- This pub/sub infrastructure will be reused for real-time chat in the next phase
|
||||
|
||||
### 2.2 New Public Routes — Mitra
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|---|---|---|
|
||||
| `POST` | `/api/mitra/status/online` | Set mitra online |
|
||||
| `POST` | `/api/mitra/status/offline` | Set mitra offline |
|
||||
| `GET` | `/api/mitra/status` | Get own current status |
|
||||
| `GET` | `/api/mitra/chat-requests/incoming` | SSE/long-poll endpoint for incoming chat request notifications |
|
||||
| `POST` | `/api/mitra/chat-requests/:sessionId/accept` | Accept a chat request |
|
||||
| `POST` | `/api/mitra/chat-requests/:sessionId/decline` | Decline a chat request |
|
||||
| `GET` | `/api/mitra/sessions/active` | Get mitra's active sessions |
|
||||
| `POST` | `/api/mitra/sessions/:sessionId/end` | End a session |
|
||||
|
||||
### 2.3 New Public Routes — Client
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|---|---|---|
|
||||
| `POST` | `/api/client/chat/request` | Start a pairing request |
|
||||
| `GET` | `/api/client/chat/request/:sessionId/status` | SSE/long-poll for pairing status updates |
|
||||
| `POST` | `/api/client/chat/request/:sessionId/cancel` | Cancel a pairing request |
|
||||
| `GET` | `/api/client/sessions/active` | Get customer's active session |
|
||||
| `POST` | `/api/client/sessions/:sessionId/end` | End a session |
|
||||
|
||||
### 2.4 New Internal Routes — Control Center
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|---|---|---|
|
||||
| `GET` | `/internal/dashboard/stats` | Active chats, active mitras, active requests, customers per mitra |
|
||||
| `GET` | `/internal/config/max-customers-per-mitra` | Get current max config |
|
||||
| `PATCH` | `/internal/config/max-customers-per-mitra` | Update max config |
|
||||
| `GET` | `/internal/sessions` | List sessions (filterable by status) |
|
||||
| `GET` | `/internal/sessions/:sessionId` | Session detail |
|
||||
| `POST` | `/internal/sessions/:sessionId/reroute` | Reroute session to another mitra (forced) |
|
||||
| `GET` | `/internal/mitras/online` | List online mitras with active session count |
|
||||
| `GET` | `/internal/mitras/:mitraId/online-logs` | Online/offline log for a mitra |
|
||||
|
||||
### 2.5 New Services
|
||||
|
||||
| Service | Responsibilities |
|
||||
|---|---|
|
||||
| `mitra-status.service.js` | Toggle online/offline, log entries, auto-offline logic |
|
||||
| `pairing.service.js` | Create pairing request, blast to mitras, handle accept/decline, timeout (60s), cancel |
|
||||
| `session.service.js` | Session lifecycle (active, end, reroute), active session queries |
|
||||
| `dashboard.service.js` | Aggregate stats for control center dashboard |
|
||||
|
||||
### 2.6 Pairing Flow (Backend Detail)
|
||||
|
||||
```
|
||||
1. Customer calls POST /api/client/chat/request
|
||||
2. Backend creates chat_session (status: searching)
|
||||
3. Backend queries available mitras:
|
||||
- is_online = true
|
||||
- active_session_count < max_customers_per_mitra config
|
||||
4. If no mitra available → immediately return "no bestie available"
|
||||
5. If mitras available:
|
||||
- Update session status → pending_acceptance
|
||||
- Create chat_request_notifications for each available mitra
|
||||
- Publish to each mitra's Valkey channel
|
||||
- Start 60s timeout timer (via setTimeout or scheduled job)
|
||||
6. Mitra calls POST /api/mitra/chat-requests/:sessionId/accept
|
||||
- Check session still in pending_acceptance (first-come-first-served)
|
||||
- If yes → pair, set session status → pending_payment → active (skip payment)
|
||||
- Publish status update to customer's session channel
|
||||
- Mark other mitra notifications as "ignored"
|
||||
7. On 60s timeout with no accept:
|
||||
- Set session status → expired
|
||||
- Publish timeout to customer's session channel
|
||||
8. On customer cancel:
|
||||
- Set session status → cancelled
|
||||
- Notify mitras to dismiss the request
|
||||
```
|
||||
|
||||
### 2.7 Auto-Offline Mechanism
|
||||
|
||||
- Mitra app sends periodic heartbeat (`POST /api/mitra/status/heartbeat`) every 15 seconds
|
||||
- Backend tracks last heartbeat timestamp in `mitra_online_status`
|
||||
- A background interval (every 30s) checks for mitras with `is_online = true` but no heartbeat in the last 45 seconds → auto-set offline
|
||||
- This handles force-close, network loss, and crashes
|
||||
|
||||
---
|
||||
|
||||
## 3. Mitra App Changes
|
||||
|
||||
### 3.1 New BLoC: `StatusBloc`
|
||||
|
||||
Manages online/offline toggle and heartbeat.
|
||||
|
||||
**Events:** `ToggleOnline`, `ToggleOffline`, `HeartbeatTick`, `AutoOfflineDetected`
|
||||
**States:** `StatusInitial`, `StatusOnline`, `StatusOffline`, `StatusLoading`, `StatusError`
|
||||
|
||||
- On `ToggleOnline` → call API, start heartbeat timer (15s interval)
|
||||
- On `ToggleOffline` → call API, stop heartbeat timer
|
||||
- On app lifecycle `paused`/`detached` → call offline API, stop heartbeat
|
||||
|
||||
### 3.2 New BLoC: `ChatRequestBloc`
|
||||
|
||||
Handles incoming chat request notifications.
|
||||
|
||||
**Events:** `StartListening`, `StopListening`, `RequestReceived`, `AcceptRequest`, `DeclineRequest`
|
||||
**States:** `Idle`, `IncomingRequest(session)`, `Accepting`, `Accepted(session)`, `Declined`, `Error`
|
||||
|
||||
- Listens to SSE/long-poll endpoint for incoming requests while mitra is online
|
||||
- Shows incoming request UI overlay/bottom sheet
|
||||
|
||||
### 3.3 Screen Changes
|
||||
|
||||
| Screen | Changes |
|
||||
|---|---|
|
||||
| Home screen | Add online/offline toggle switch at the top; show active session count |
|
||||
| New: Incoming request sheet | Bottom sheet showing customer request with Accept/Decline buttons |
|
||||
| New: Active sessions screen | List of current active sessions (name, duration) with End button |
|
||||
|
||||
### 3.4 App Lifecycle Handling
|
||||
|
||||
- Use `WidgetsBindingObserver` to detect app going to background/foreground
|
||||
- On `paused`/`detached` → call offline API
|
||||
- On `resumed` → do NOT auto-set online; mitra must explicitly toggle
|
||||
|
||||
---
|
||||
|
||||
## 4. Client App Changes
|
||||
|
||||
### 4.1 New BLoC: `PairingBloc`
|
||||
|
||||
Manages the pairing request lifecycle.
|
||||
|
||||
**Events:** `RequestPairing`, `CancelPairing`, `PairingStatusUpdate`, `PairingTimeout`
|
||||
**States:** `PairingInitial`, `Searching`, `BestieFound(mitraName)`, `Paired(session)`, `NoBestieAvailable`, `Cancelled`, `Error`
|
||||
|
||||
- On `RequestPairing` → call API, start listening for status updates, start 60s local timer
|
||||
- On status update `active` → emit `BestieFound` briefly, then `Paired`
|
||||
- On timeout / expired → emit `NoBestieAvailable`
|
||||
|
||||
### 4.2 Screen Changes
|
||||
|
||||
| Screen | Changes |
|
||||
|---|---|
|
||||
| Home screen | Add "Mulai Curhat" CTA button (disabled if already has active session) |
|
||||
| New: Searching screen | "Searching for Bestie..." with animation and Cancel button |
|
||||
| New: Bestie found screen | Brief "Bestie ditemukan, menghubungkan kamu ke Bestie" message |
|
||||
| New: Session active screen | Shows paired bestie name, End Session button |
|
||||
| New: No bestie screen | "No bestie available, try again later" with retry button |
|
||||
|
||||
### 4.3 Navigation Updates
|
||||
|
||||
Add new routes in GoRouter:
|
||||
- `/chat/searching` — searching screen
|
||||
- `/chat/session/:sessionId` — active session screen
|
||||
|
||||
---
|
||||
|
||||
## 5. Control Center Changes
|
||||
|
||||
### 5.1 New Pages
|
||||
|
||||
| Page | Purpose |
|
||||
|---|---|
|
||||
| Dashboard page | Real-time stats: active chats, online mitras, pending requests, customers per mitra breakdown |
|
||||
| Session management page | Table of all sessions (filterable by status), reroute action |
|
||||
| Session detail page | Session info, customer/mitra details, reroute button |
|
||||
|
||||
### 5.2 Updated Pages
|
||||
|
||||
| Page | Changes |
|
||||
|---|---|
|
||||
| Settings page | Add "Max customers per mitra" config input |
|
||||
| Mitra management page | Add online status indicator column, online/offline log link |
|
||||
|
||||
### 5.3 Dashboard Auto-Refresh
|
||||
|
||||
- Use React Query with `refetchInterval: 10000` (10s polling)
|
||||
- Later can be swapped to SSE push without changing component logic
|
||||
|
||||
---
|
||||
|
||||
## 6. Implementation Order
|
||||
|
||||
Work should be done in this sequence to allow incremental testing:
|
||||
|
||||
| Step | What | Apps affected |
|
||||
|---|---|---|
|
||||
| 1 | Database migration (new tables + config) | Backend |
|
||||
| 2 | Valkey plugin setup | Backend |
|
||||
| 3 | Mitra online/offline status API + service + heartbeat | Backend |
|
||||
| 4 | Mitra app: status toggle + heartbeat + lifecycle handling | Mitra app |
|
||||
| 5 | Control center: max config + mitra online status column + logs | Control center |
|
||||
| 6 | Pairing service + chat request APIs (mitra + client) | Backend |
|
||||
| 7 | Client app: pairing flow (request, searching, found, paired screens) | Client app |
|
||||
| 8 | Mitra app: incoming request notification + accept/decline | Mitra app |
|
||||
| 9 | Session management APIs (end, reroute, list) | Backend |
|
||||
| 10 | Client/mitra app: active session screen + end session | Both apps |
|
||||
| 11 | Control center: dashboard + session management + reroute | Control center |
|
||||
|
||||
---
|
||||
|
||||
## 7. New Dependencies
|
||||
|
||||
| App | Package | Purpose |
|
||||
|---|---|---|
|
||||
| Backend | `ioredis` or `@valkey/valkey-glide` | Valkey/Redis client for pub/sub |
|
||||
| Mitra app | `dio` (existing) | SSE via streaming response |
|
||||
| Client app | `dio` (existing) | SSE via streaming response |
|
||||
|
||||
No major new dependencies needed — leveraging existing stack.
|
||||
|
||||
---
|
||||
|
||||
## 8. Note for Next Phase
|
||||
|
||||
**Valkey vs FCM — complementary, not competing:**
|
||||
- **Valkey pub/sub** is the real-time transport for in-app events (pairing blasts, status updates, and later chat messages). Works when the app is in foreground.
|
||||
- **FCM (Firebase Cloud Messaging)** should be added in the next phase for push notifications when the app is backgrounded/closed (e.g. "New message from customer"). Not needed this phase because mitras auto-go offline when the app is backgrounded — they only receive pairing requests while in foreground.
|
||||
- Architecture: Valkey for real-time transport, FCM for push notifications. They layer on top of each other.
|
||||
59
requirement/phase2.md
Normal file
59
requirement/phase2.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# PRD: Mitra online status And Pairing Logic
|
||||
|
||||
# Overview
|
||||
|
||||
**Goal:** To build mitra online status and pairing logic between Mitra and Customer
|
||||
|
||||
**Success looks like:** Mitra can set status to online. System can pair Mitra and Customer for chat acceptance request. System can determine how many Customer connected with Mitra
|
||||
|
||||
## Background
|
||||
- We need our Mitra do determine whether they are able to receive chat request or not through Online or Offline status
|
||||
- System can keep track which Mitra is online and how many active session they currently handle
|
||||
- System can have logic on how to pair Customer with Mitra. Either it is round robin or based round robin with weight
|
||||
- Control center can determine how many Customer can Mitra handle globally
|
||||
- When needed, control center user can re route request to other Mitra
|
||||
|
||||
|
||||
# Functional Requirement
|
||||
|
||||
## Mitra online status
|
||||
Mitra online status functional requirement:
|
||||
1. Mitra can set their status online of offline from mobile app
|
||||
2. Mitra with status online will be visible by system for pairing process
|
||||
3. Online status will have their log. showing when they were online and when they were offline.
|
||||
4. Be noted, that Mitra can be online and offline multiple times a day
|
||||
|
||||
## Customer Chat Request
|
||||
Customer chat request on this phase will be just simple pairing, with lead to pairing process only. the real process will be on next phase.
|
||||
|
||||
Functional requirement:
|
||||
1. Customer click CTA "Mulai Curhat"
|
||||
2. System will look for mitra using Pairing method defined in different section
|
||||
3. While system look for mitra and waiting for Mitra's confirmation, customer app will shows "Searching for Bestie" page
|
||||
4. When mitra confirm chat request, customer app will briefly shows "Bestie ditemukan, menghubungkan kamu ke Bestie"
|
||||
5. Customer will then showed a screen that they had paired with bestie named "bestie name"
|
||||
|
||||
## Mitra Pairing Backend
|
||||
Pairing is a process in backend for system to look for Mitra's availability. Functional requirement:
|
||||
1. Control Center user can config how many Customer, Mitra can handle
|
||||
2. System will keep track of Mitra and Customer active chat session
|
||||
3. When pairing request is coming, system will look for Mitra that is online and has active chat session less than maximum Mitra can handle
|
||||
4. System then blast to all available Mitra that there is incoming chat Request
|
||||
5. When one of the Mitra accept, system will pair them and start the session
|
||||
6. Pairing flow will be:
|
||||
1. Customer request for pairing
|
||||
2. System blast to all available mitra
|
||||
3. One of the Mitra will accept the Chat Request
|
||||
4. Cutomer will receive notification that Bestie (Mitra {display name}) is accepting the request, it will trigger payment flow. Payment flow will be skipped on this phase
|
||||
5. Once payment confirmed (which is skipped), System will show session start screen
|
||||
|
||||
|
||||
## Control Center Chat Management
|
||||
Control center will have following functionality:
|
||||
1. Control Center user can set how many Customer can be handled by a Mitra
|
||||
2. Upon Customer count per Mitra changes, system will only affect new incoming chat request, *AND NOT* the existing one
|
||||
3. When needed, Control Center user can reroute (re assign) existing Chat Session to other Mitra. It can be rerouted both during mid session or when the session is waiting for payment. Payment is on next phase, so be prepare for it
|
||||
4. Control Center user can see how many active chat, how many active Mitra, how many active request and how many Customer per Mitra
|
||||
|
||||
## Tech Stack
|
||||
Use existing tech stack, and extend if needed, such as valkey for pubsub.
|
||||
Reference in New Issue
Block a user