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>
323 lines
13 KiB
Markdown
323 lines
13 KiB
Markdown
# 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.
|