@clinchdate/api-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.MD +711 -0
  2. package/dist/client.d.ts +30 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +196 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/config/environments.d.ts +24 -0
  7. package/dist/config/environments.d.ts.map +1 -0
  8. package/dist/config/environments.js +24 -0
  9. package/dist/config/environments.js.map +1 -0
  10. package/dist/config.d.ts +21 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +44 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/hooks/useAuth.d.ts +13 -0
  15. package/dist/hooks/useAuth.d.ts.map +1 -0
  16. package/dist/hooks/useAuth.js +66 -0
  17. package/dist/hooks/useAuth.js.map +1 -0
  18. package/dist/hooks/useCall.d.ts +55 -0
  19. package/dist/hooks/useCall.d.ts.map +1 -0
  20. package/dist/hooks/useCall.js +148 -0
  21. package/dist/hooks/useCall.js.map +1 -0
  22. package/dist/hooks/useChat.d.ts +13 -0
  23. package/dist/hooks/useChat.d.ts.map +1 -0
  24. package/dist/hooks/useChat.js +76 -0
  25. package/dist/hooks/useChat.js.map +1 -0
  26. package/dist/hooks/useMatch.d.ts +15 -0
  27. package/dist/hooks/useMatch.d.ts.map +1 -0
  28. package/dist/hooks/useMatch.js +72 -0
  29. package/dist/hooks/useMatch.js.map +1 -0
  30. package/dist/hooks/useUser.d.ts +22 -0
  31. package/dist/hooks/useUser.d.ts.map +1 -0
  32. package/dist/hooks/useUser.js +170 -0
  33. package/dist/hooks/useUser.js.map +1 -0
  34. package/dist/index.d.ts +27 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +26 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/interceptors/auth.interceptor.d.ts +14 -0
  39. package/dist/interceptors/auth.interceptor.d.ts.map +1 -0
  40. package/dist/interceptors/auth.interceptor.js +61 -0
  41. package/dist/interceptors/auth.interceptor.js.map +1 -0
  42. package/dist/interceptors/error.interceptor.d.ts +12 -0
  43. package/dist/interceptors/error.interceptor.d.ts.map +1 -0
  44. package/dist/interceptors/error.interceptor.js +50 -0
  45. package/dist/interceptors/error.interceptor.js.map +1 -0
  46. package/dist/interceptors/response.interceptor.d.ts +2 -0
  47. package/dist/interceptors/response.interceptor.d.ts.map +1 -0
  48. package/dist/interceptors/response.interceptor.js +2 -0
  49. package/dist/interceptors/response.interceptor.js.map +1 -0
  50. package/dist/services/auth.service.d.ts +12 -0
  51. package/dist/services/auth.service.d.ts.map +1 -0
  52. package/dist/services/auth.service.js +37 -0
  53. package/dist/services/auth.service.js.map +1 -0
  54. package/dist/services/base.service.d.ts +10 -0
  55. package/dist/services/base.service.d.ts.map +1 -0
  56. package/dist/services/base.service.js +22 -0
  57. package/dist/services/base.service.js.map +1 -0
  58. package/dist/services/call.service.d.ts +80 -0
  59. package/dist/services/call.service.d.ts.map +1 -0
  60. package/dist/services/call.service.js +57 -0
  61. package/dist/services/call.service.js.map +1 -0
  62. package/dist/services/chat.service.d.ts +16 -0
  63. package/dist/services/chat.service.d.ts.map +1 -0
  64. package/dist/services/chat.service.js +33 -0
  65. package/dist/services/chat.service.js.map +1 -0
  66. package/dist/services/index.d.ts +23 -0
  67. package/dist/services/index.d.ts.map +1 -0
  68. package/dist/services/index.js +23 -0
  69. package/dist/services/index.js.map +1 -0
  70. package/dist/services/match.service.d.ts +14 -0
  71. package/dist/services/match.service.d.ts.map +1 -0
  72. package/dist/services/match.service.js +23 -0
  73. package/dist/services/match.service.js.map +1 -0
  74. package/dist/services/notification.service.d.ts +16 -0
  75. package/dist/services/notification.service.d.ts.map +1 -0
  76. package/dist/services/notification.service.js +31 -0
  77. package/dist/services/notification.service.js.map +1 -0
  78. package/dist/services/payment.service.d.ts +15 -0
  79. package/dist/services/payment.service.d.ts.map +1 -0
  80. package/dist/services/payment.service.js +32 -0
  81. package/dist/services/payment.service.js.map +1 -0
  82. package/dist/services/user.service.d.ts +16 -0
  83. package/dist/services/user.service.d.ts.map +1 -0
  84. package/dist/services/user.service.js +34 -0
  85. package/dist/services/user.service.js.map +1 -0
  86. package/dist/types/auth.types.d.ts +49 -0
  87. package/dist/types/auth.types.d.ts.map +1 -0
  88. package/dist/types/auth.types.js +2 -0
  89. package/dist/types/auth.types.js.map +1 -0
  90. package/dist/types/chat.types.d.ts +35 -0
  91. package/dist/types/chat.types.d.ts.map +1 -0
  92. package/dist/types/chat.types.js +2 -0
  93. package/dist/types/chat.types.js.map +1 -0
  94. package/dist/types/common.types.d.ts +39 -0
  95. package/dist/types/common.types.d.ts.map +1 -0
  96. package/dist/types/common.types.js +25 -0
  97. package/dist/types/common.types.js.map +1 -0
  98. package/dist/types/index.d.ts +2 -0
  99. package/dist/types/index.d.ts.map +1 -0
  100. package/dist/types/index.js +2 -0
  101. package/dist/types/index.js.map +1 -0
  102. package/dist/types/match.types.d.ts +40 -0
  103. package/dist/types/match.types.d.ts.map +1 -0
  104. package/dist/types/match.types.js +2 -0
  105. package/dist/types/match.types.js.map +1 -0
  106. package/dist/types/notification.types.d.ts +26 -0
  107. package/dist/types/notification.types.d.ts.map +1 -0
  108. package/dist/types/notification.types.js +2 -0
  109. package/dist/types/notification.types.js.map +1 -0
  110. package/dist/types/payment.types.d.ts +47 -0
  111. package/dist/types/payment.types.d.ts.map +1 -0
  112. package/dist/types/payment.types.js +2 -0
  113. package/dist/types/payment.types.js.map +1 -0
  114. package/dist/types/user.types.d.ts +50 -0
  115. package/dist/types/user.types.d.ts.map +1 -0
  116. package/dist/types/user.types.js +2 -0
  117. package/dist/types/user.types.js.map +1 -0
  118. package/package.json +120 -0
package/README.MD ADDED
@@ -0,0 +1,711 @@
1
+ # ClinchDate — Complete Technical Documentation
2
+
3
+ > Multi-platform dating app targeting Cameroon & West Africa & the World.
4
+ > Backend API · Web App · Mobile App · Admin Panel · Shared API Client SDK
5
+
6
+ ---
7
+
8
+ ## Table of Contents
9
+
10
+ 1. [Architecture Overview](#architecture-overview)
11
+ 2. [Repository Structure](#repository-structure)
12
+ 3. [Backend API](#backend-api)
13
+ 4. [API Client SDK](#api-client-sdk)
14
+ 5. [Twilio Calling System](#twilio-calling-system)
15
+ 6. [Database Models](#database-models)
16
+ 7. [API Routes Reference](#api-routes-reference)
17
+ 8. [Environment Setup](#environment-setup)
18
+ 9. [Deployment](#deployment)
19
+ 10. [Work Completed](#work-completed)
20
+ 11. [Remaining Work](#remaining-work)
21
+ 12. [Known Issues & Audit Findings](#known-issues--audit-findings)
22
+
23
+ ---
24
+
25
+ ## Architecture Overview
26
+
27
+ ClinchDate uses a **polyrepo architecture** with five independent codebases sharing a single API client SDK.
28
+
29
+ ```
30
+ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐
31
+ │ Mobile App │ │ Web App │ │ Admin Panel │
32
+ │ React Native │ │ React │ │ React │
33
+ │ Expo Router │ │ Vite │ │ Vite │
34
+ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
35
+ │ │ │
36
+ └──────────┬───────┴───────────────────┘
37
+
38
+ ┌────────▼────────┐
39
+ │ @clinchdate/ │
40
+ │ api-client SDK │ ← Mandatory for all frontends
41
+ │ (npm package) │
42
+ └────────┬────────┘
43
+
44
+ ┌────────▼────────┐
45
+ │ Backend API │
46
+ │ Node/Express │
47
+ │ MongoDB Atlas │
48
+ └────────┬────────┘
49
+
50
+ ┌─────────────┼──────────────┐
51
+ │ │ │
52
+ ┌───▼──┐ ┌─────▼────┐ ┌────▼────┐
53
+ │Twilio│ │ Stripe │ │Firebase │
54
+ │Video │ │ Payments │ │ Auth │
55
+ │Voice │ │ │ │ Push │
56
+ └──────┘ └──────────┘ └─────────┘
57
+ ```
58
+
59
+ **Tech Stack:**
60
+ - **Backend:** Node.js, Express, TypeScript, MongoDB (Mongoose), Socket.IO
61
+ - **Mobile:** React Native, Expo Router, TypeScript
62
+ - **Web:** React, Vite, TypeScript
63
+ - **Admin:** React, Vite, TypeScript
64
+ - **SDK:** TypeScript, Axios
65
+ - **Auth:** Firebase Authentication + JWT refresh tokens
66
+ - **Payments:** Stripe (subscriptions, one-time purchases)
67
+ - **Calling:** Twilio Video (peer-to-peer) + Twilio Voice (Client SDK)
68
+ - **Storage:** AWS S3 + CloudFront CDN
69
+ - **Email:** SendGrid dynamic templates
70
+ - **SMS/Verify:** Twilio Verify + Messaging Services
71
+ - **Images:** Cloudinary
72
+ - **Maps:** Google Maps + Places API
73
+
74
+ ---
75
+
76
+ ## Repository Structure
77
+
78
+ ### Backend — `clinchdate-backend/`
79
+
80
+ ```
81
+ src/
82
+ ├── config/
83
+ │ ├── database.config.ts # MongoDB connection
84
+ │ ├── firebase.config.ts # Firebase Admin SDK
85
+ │ ├── stripe.config.ts # Stripe client
86
+ │ └── twilio.config.ts # ★ Twilio config (NEW)
87
+
88
+ ├── models/
89
+ │ ├── index.ts # Barrel exports all models
90
+ │ ├── User.model.ts # ✅ Indexes fixed
91
+ │ ├── Admin.model.ts # ✅ Indexes fixed
92
+ │ ├── Profile.model.ts # ✅ Indexes fixed
93
+ │ ├── Match.model.ts # ✅ Indexes fixed
94
+ │ ├── Swipe.model.ts # ✅ Indexes fixed
95
+ │ ├── Conversation.model.ts # ✅ Indexes fixed
96
+ │ ├── Message.model.ts # ✅ Indexes fixed
97
+ │ ├── Chat.model.ts # ✅ Indexes fixed (⚠️ duplicates Message — see audit)
98
+ │ ├── Payment.model.ts # ✅ Indexes fixed
99
+ │ ├── Subscription.model.ts # ✅ Indexes fixed
100
+ │ ├── Report.model.ts # ✅ Indexes fixed
101
+ │ ├── SupportTicket.model.ts # ✅ Indexes fixed
102
+ │ ├── Announcement.model.ts # ✅ Indexes fixed
103
+ │ ├── BlockedUser.model.ts # ✅ Indexes fixed
104
+ │ ├── Boost.model.ts # ✅ Indexes fixed
105
+ │ ├── Event.model.ts # ✅ Indexes fixed
106
+ │ ├── Notification.model.ts # ✅ Indexes fixed
107
+ │ ├── Verification.model.ts # ✅ Indexes fixed
108
+ │ ├── Withdrawal.model.ts # ✅ Indexes fixed (⚠️ missing userId — see audit)
109
+ │ ├── Settings.model.ts # ✅ Already clean
110
+ │ └── Call.model.ts # ★ NEW — Twilio call records
111
+
112
+ ├── services/
113
+ │ ├── twilio.service.ts # ★ NEW — Twilio SDK wrapper
114
+ │ └── call.service.ts # ★ NEW — Call business logic
115
+
116
+ ├── controllers/
117
+ │ ├── call.controller.ts # ★ NEW — Call API handlers
118
+ │ └── twilio-webhook.controller.ts # ★ NEW — Webhook handlers
119
+
120
+ ├── middleware/
121
+ │ ├── auth.middleware.ts # Firebase JWT validation
122
+ │ └── twilio-webhook.middleware.ts # ★ NEW — Signature validation
123
+
124
+ ├── routes/
125
+ │ ├── call.routes.ts # ★ NEW — /api/v1/calls/*
126
+ │ └── webhook.routes.ts # ★ NEW — /api/v1/webhooks/twilio/*
127
+
128
+ ├── setup/
129
+ │ └── call.setup.ts # ★ NEW — Bootstrap wiring
130
+
131
+ ├── utils/
132
+ │ └── logger.ts # Winston/Pino logger
133
+
134
+ ├── constants/
135
+ │ ├── status.constant.ts # Enum definitions
136
+ │ └── notification.constant.ts # Notification types
137
+
138
+ └── server.ts # Express entry point
139
+ ```
140
+
141
+ ### API Client SDK — `clinchdate-client/`
142
+
143
+ ```
144
+ src/
145
+ ├── config.ts # ★ NEW — SDK configuration singleton
146
+ ├── client.ts # ★ NEW — HTTP client (axios + interceptors)
147
+
148
+ ├── services/
149
+ │ ├── auth.service.ts # Login, register, refresh, logout
150
+ │ ├── user.service.ts # User CRUD, search, preferences
151
+ │ ├── profile.service.ts # Profile management, photos
152
+ │ ├── match.service.ts # Matching, swiping, super likes
153
+ │ ├── chat.service.ts # Conversations, messages, read receipts
154
+ │ ├── call.service.ts # ✅ FIXED — Video & audio calls
155
+ │ ├── payment.service.ts # Stripe payments, subscriptions
156
+ │ ├── event.service.ts # Events CRUD, RSVP
157
+ │ ├── notification.service.ts # Push notifications, preferences
158
+ │ ├── report.service.ts # User reports, blocking
159
+ │ ├── support.service.ts # Support tickets
160
+ │ ├── admin.service.ts # Admin dashboard operations
161
+ │ ├── settings.service.ts # User settings
162
+ │ └── upload.service.ts # File uploads (S3/Cloudinary)
163
+
164
+ ├── types/ # Shared TypeScript interfaces
165
+ │ ├── user.types.ts
166
+ │ ├── match.types.ts
167
+ │ ├── chat.types.ts
168
+ │ └── ...
169
+
170
+ └── index.ts # Barrel export
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Backend API
176
+
177
+ ### Quick Start
178
+
179
+ ```bash
180
+ # 1. Clone and install
181
+ cd clinchdate-backend
182
+ npm install
183
+
184
+ # 2. Environment setup
185
+ cp .env.example .env
186
+ # Edit .env with your actual credentials (see Environment Setup section)
187
+
188
+ # 3. Install Twilio dependency (NEW)
189
+ npm install twilio
190
+
191
+ # 4. Wire up calling system — add to server.ts:
192
+ # import { setupCallSystem } from './setup/call.setup';
193
+ # setupCallSystem(app); // after your existing route setup
194
+
195
+ # 5. Run
196
+ npm run dev
197
+ ```
198
+
199
+ ### Server Startup Checklist
200
+
201
+ When the server starts, you should see:
202
+
203
+ ```
204
+ ✅ MongoDB connected
205
+ ✅ Firebase Admin initialized
206
+ ✅ Twilio config loaded and validated
207
+ ✅ Call routes mounted: /api/v1/calls
208
+ ✅ Twilio webhook routes mounted: /api/v1/webhooks/twilio
209
+ ✅ Stale call cleanup job started (every 2 min)
210
+ ✅ Socket.IO ready
211
+ 🚀 Server running on port 3000
212
+ ```
213
+
214
+ If you see **zero** Mongoose duplicate index warnings, the model fixes are working correctly.
215
+
216
+ ---
217
+
218
+ ## API Client SDK
219
+
220
+ The SDK is the **mandatory** interface between all frontends and the backend. No frontend should make direct HTTP calls — everything goes through the SDK.
221
+
222
+ ### Installation
223
+
224
+ ```bash
225
+ # From the SDK repo
226
+ npm install
227
+
228
+ # Or link locally during development
229
+ npm link
230
+ cd ../clinchdate-web && npm link @clinchdate/api-client
231
+ cd ../clinchdate-mobile && npm link @clinchdate/api-client
232
+ ```
233
+
234
+ ### Initialization
235
+
236
+ The SDK must be configured **once** at app startup before any API calls.
237
+
238
+ #### React Native (Mobile)
239
+
240
+ ```typescript
241
+ // App.tsx or app/_layout.tsx
242
+ import { configure } from '@clinchdate/api-client';
243
+ import AsyncStorage from '@react-native-async-storage/async-storage';
244
+
245
+ configure({
246
+ baseURL: 'https://api.clinchdate.com/api/v1',
247
+ platform: 'ios', // or 'android'
248
+ getToken: () => AsyncStorage.getItem('accessToken'),
249
+ getRefreshToken: () => AsyncStorage.getItem('refreshToken'),
250
+ onTokenRefreshed: async (tokens) => {
251
+ await AsyncStorage.setItem('accessToken', tokens.accessToken);
252
+ await AsyncStorage.setItem('refreshToken', tokens.refreshToken);
253
+ },
254
+ onAuthFailure: () => {
255
+ // Navigate to login screen
256
+ router.replace('/login');
257
+ },
258
+ });
259
+ ```
260
+
261
+ #### React (Web / Admin)
262
+
263
+ ```typescript
264
+ // main.tsx or App.tsx
265
+ import { configure } from '@clinchdate/api-client';
266
+
267
+ configure({
268
+ baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api/v1',
269
+ platform: 'web', // or 'admin'
270
+ getToken: async () => localStorage.getItem('accessToken'),
271
+ getRefreshToken: async () => localStorage.getItem('refreshToken'),
272
+ onTokenRefreshed: async (tokens) => {
273
+ localStorage.setItem('accessToken', tokens.accessToken);
274
+ localStorage.setItem('refreshToken', tokens.refreshToken);
275
+ },
276
+ onAuthFailure: () => {
277
+ window.location.href = '/login';
278
+ },
279
+ });
280
+ ```
281
+
282
+ ### SDK Features
283
+
284
+ The `client.ts` HTTP client provides:
285
+
286
+ - **Automatic token attachment** — every request gets `Authorization: Bearer <token>`
287
+ - **Silent token refresh** — 401 responses trigger automatic refresh with queued retries
288
+ - **Request queuing** — concurrent requests during refresh are queued, not duplicated
289
+ - **Retry logic** — 5xx and timeout errors auto-retry with exponential backoff (1s, 2s, 4s)
290
+ - **Response envelope unwrapping** — `{ success: true, data: T }` → returns `T` directly
291
+ - **File uploads** — `httpClient.upload()` with progress callback
292
+ - **Error normalization** — all errors become `ApiError` with `statusCode`, `message`, `isNetworkError`, `isAuthError`
293
+ - **Debug mode** — set `debug: true` to log all requests/responses
294
+ - **Platform header** — `X-Platform: ios|android|web|admin` for analytics
295
+
296
+ ### Using Services
297
+
298
+ ```typescript
299
+ import { authService } from '@clinchdate/api-client';
300
+ import { matchService } from '@clinchdate/api-client';
301
+ import { callService } from '@clinchdate/api-client';
302
+
303
+ // Auth
304
+ const user = await authService.login({ email, password });
305
+ const tokens = await authService.refreshToken(refreshToken);
306
+
307
+ // Matching
308
+ const matches = await matchService.getMatches();
309
+ await matchService.swipeRight(userId);
310
+
311
+ // Calling
312
+ const { callId, token, roomName } = await callService.initiateVideoCall(receiverId, matchId);
313
+ const voiceToken = await callService.getAudioToken();
314
+ const history = await callService.getCallHistory(20);
315
+ ```
316
+
317
+ ### Error Handling
318
+
319
+ ```typescript
320
+ import { ApiError } from '@clinchdate/api-client';
321
+
322
+ try {
323
+ await matchService.swipeRight(userId);
324
+ } catch (error) {
325
+ if (error instanceof ApiError) {
326
+ if (error.isNetworkError) {
327
+ showToast('No internet connection');
328
+ } else if (error.isAuthError) {
329
+ // SDK already triggers onAuthFailure — this rarely fires
330
+ showToast('Please log in again');
331
+ } else if (error.statusCode === 429) {
332
+ showToast('Too many swipes! Slow down.');
333
+ } else {
334
+ showToast(error.message); // Server error message
335
+ }
336
+ }
337
+ }
338
+ ```
339
+
340
+ ---
341
+
342
+ ## Twilio Calling System
343
+
344
+ ### Architecture
345
+
346
+ ```
347
+ Mobile/Web App
348
+
349
+ ├─ POST /calls/video/initiate ───┐
350
+ ├─ POST /calls/video/accept ───┤
351
+ ├─ POST /calls/audio/initiate ───┤ Authenticated
352
+ ├─ GET /calls/audio/token ───┤ API Routes
353
+ ├─ GET /calls/history ───┘
354
+
355
+ │ ┌──────────────────────────────┐
356
+ │ │ CallController │ Validates input
357
+ │ └─────────────┬────────────────┘
358
+ │ │
359
+ │ ┌─────────────▼────────────────┐
360
+ │ │ CallService │ Business logic + DB
361
+ │ └─────────────┬────────────────┘
362
+ │ │
363
+ │ ┌─────────────▼────────────────┐
364
+ │ │ TwilioService │ Twilio SDK calls
365
+ │ └─────────────┬────────────────┘
366
+ │ │
367
+ │ ▼
368
+ │ Twilio Cloud
369
+ │ │
370
+ │ ┌─────────────▼────────────────┐
371
+ │ │ Webhook Endpoints │ Status callbacks
372
+ │ │ /webhooks/twilio/video-* │ (signature validated)
373
+ │ │ /webhooks/twilio/voice-* │
374
+ │ └──────────────────────────────┘
375
+ ```
376
+
377
+ ### Video Call Flow
378
+
379
+ ```
380
+ Caller Backend Twilio Receiver
381
+ │ │ │ │
382
+ ├── POST /video/initiate ──▶ │ │
383
+ │ ├── Create Room ──────────▶│ │
384
+ │ ├── Save Call (RINGING) ───▶ DB │
385
+ │ ├── Generate Token ────────▶│ │
386
+ │ ◀── { callId, token } ──┤ │ │
387
+ │ ├── Push Notification ─────────────────────────────▶│
388
+ │ │ │ │
389
+ │ Connect to Room ─────────────────────────────────▶│ │
390
+ │ │ │ │
391
+ │ │ │ POST /video/accept ──┤
392
+ │ │ ◀── Accept Call ────────┤ │
393
+ │ ├── Update (IN_PROGRESS) ──▶ DB │
394
+ │ ├── Generate Token ────────▶│ │
395
+ │ │ { token } ──────────────────────────────────────▶│
396
+ │ │ │ │
397
+ │ ◀════════════ Video Connection Established ════════════════════════════════▶│
398
+ │ │ │ │
399
+ │── POST /video/end ──────▶│ │ │
400
+ │ ├── End Room ─────────────▶│ │
401
+ │ ├── Update (COMPLETED) ────▶ DB │
402
+ │ ◀── { duration } ───────┤ │ │
403
+ ```
404
+
405
+ ### Audio Call Flow
406
+
407
+ Audio calls use **Twilio Client SDK** (not Video rooms). The flow:
408
+
409
+ 1. Both users fetch voice tokens via `GET /calls/audio/token` when entering a call-capable screen
410
+ 2. Caller hits `POST /calls/audio/initiate` — backend creates a `Call` record with status `RINGING`
411
+ 3. Caller's Twilio Client SDK makes an outbound call, which triggers the TwiML App webhook
412
+ 4. Backend's `/webhooks/twilio/voice-incoming` returns TwiML that dials the receiver's identity
413
+ 5. Receiver's Twilio Client SDK receives the incoming call
414
+ 6. Status callbacks update the `Call` record throughout the lifecycle
415
+
416
+ ### Twilio Setup Checklist
417
+
418
+ Before calling works, set up these Twilio resources:
419
+
420
+ 1. **Twilio Account** — [console.twilio.com](https://console.twilio.com)
421
+ 2. **API Key** — Console > Account > Keys > Create Standard Key
422
+ - Copy the SID (starts with `SK`) → `TWILIO_API_KEY_SID`
423
+ - Copy the Secret → `TWILIO_API_KEY_SECRET`
424
+ 3. **TwiML App** — Console > Voice > TwiML Apps > Create
425
+ - Voice Request URL: `https://api.clinchdate.com/api/v1/webhooks/twilio/voice-incoming`
426
+ - Copy the SID (starts with `AP`) → `TWILIO_TWIML_APP_SID`
427
+ 4. **Phone Number** (optional, for PSTN) — Console > Phone Numbers > Buy a Number
428
+
429
+ ### Stale Call Cleanup
430
+
431
+ A background job runs every 2 minutes (configurable via `CALL_RINGING_TIMEOUT_MINUTES`) to clean up calls stuck in `RINGING` status. These are calls where the receiver never answered — they get moved to `NO_ANSWER` status, and any associated Twilio rooms are ended.
432
+
433
+ ---
434
+
435
+ ## Database Models
436
+
437
+ ### 21 Models (20 existing + 1 new)
438
+
439
+ | Model | Collection | Purpose | Status |
440
+ |-------|-----------|---------|--------|
441
+ | User | users | Core user accounts, auth | ✅ Fixed |
442
+ | Admin | admins | Admin panel users | ✅ Fixed |
443
+ | Profile | profiles | User dating profiles | ✅ Fixed |
444
+ | Match | matches | Mutual matches between users | ✅ Fixed |
445
+ | Swipe | swipes | Like/pass/super-like actions | ✅ Fixed |
446
+ | Conversation | conversations | Chat conversation metadata | ✅ Fixed |
447
+ | Message | messages | Individual chat messages | ✅ Fixed |
448
+ | Chat | chats | ⚠️ Duplicate messaging system | ✅ Fixed |
449
+ | **Call** | **calls** | **★ Video & audio call records** | **NEW** |
450
+ | Payment | payments | Stripe payment records | ✅ Fixed |
451
+ | Subscription | subscriptions | Plus/Platinum plans | ✅ Fixed |
452
+ | Boost | boosts | Profile visibility boosts | ✅ Fixed |
453
+ | Event | events | Community events | ✅ Fixed |
454
+ | Notification | notifications | Push/email/SMS notifications | ✅ Fixed |
455
+ | Report | reports | User safety reports | ✅ Fixed |
456
+ | SupportTicket | support_tickets | Customer support | ✅ Fixed |
457
+ | Announcement | announcements | Admin broadcasts | ✅ Fixed |
458
+ | BlockedUser | blocked_users | User blocking | ✅ Fixed |
459
+ | Verification | verifications | Identity verification | ✅ Fixed |
460
+ | Withdrawal | withdrawals | ⚠️ Missing userId field | ✅ Fixed |
461
+ | Settings | settings | User preferences | ✅ Clean |
462
+
463
+ ### Index Strategy
464
+
465
+ All models now use **explicit `schema.index()` calls** as the single source of truth. No inline `index: true` declarations. This eliminates the ~25 duplicate index warnings that were appearing at startup.
466
+
467
+ **Rules applied:**
468
+ - `unique: true` on a field already creates an index — no separate index needed
469
+ - Single-field indexes that are prefixes of existing compound indexes were removed
470
+ - `sparse: true` preserved on optional unique fields to allow multiple nulls
471
+
472
+ ---
473
+
474
+ ## API Routes Reference
475
+
476
+ ### Authentication — `/api/v1/auth`
477
+
478
+ | Method | Path | Description |
479
+ |--------|------|-------------|
480
+ | POST | /register | Create account |
481
+ | POST | /login | Email/password login |
482
+ | POST | /firebase-login | Firebase social login |
483
+ | POST | /refresh-token | Refresh JWT |
484
+ | POST | /forgot-password | Send reset email |
485
+ | POST | /reset-password | Reset with token |
486
+ | POST | /verify-phone | Send SMS verification |
487
+ | POST | /confirm-phone | Confirm SMS code |
488
+ | POST | /logout | Invalidate tokens |
489
+
490
+ ### Users & Profiles — `/api/v1/users`, `/api/v1/profiles`
491
+
492
+ | Method | Path | Description |
493
+ |--------|------|-------------|
494
+ | GET | /users/me | Current user |
495
+ | PUT | /users/me | Update user |
496
+ | DELETE | /users/me | Soft delete account |
497
+ | GET | /profiles/:id | View profile |
498
+ | PUT | /profiles/me | Update profile |
499
+ | POST | /profiles/photos | Upload photos |
500
+ | DELETE | /profiles/photos/:id | Remove photo |
501
+
502
+ ### Matching — `/api/v1/matches`, `/api/v1/swipes`
503
+
504
+ | Method | Path | Description |
505
+ |--------|------|-------------|
506
+ | GET | /matches | List matches |
507
+ | GET | /matches/discover | Discovery feed |
508
+ | POST | /swipes | Swipe (like/pass/super-like) |
509
+ | DELETE | /matches/:id | Unmatch |
510
+
511
+ ### Calls — `/api/v1/calls` (★ NEW)
512
+
513
+ | Method | Path | Auth | Description |
514
+ |--------|------|------|-------------|
515
+ | POST | /video/initiate | ✅ | Start video call |
516
+ | POST | /video/accept | ✅ | Accept video call |
517
+ | POST | /video/reject | ✅ | Reject video call |
518
+ | POST | /video/end | ✅ | End video call |
519
+ | GET | /video/status/:callId | ✅ | Room status |
520
+ | GET | /video/active-rooms | ✅ | Admin: list active rooms |
521
+ | POST | /audio/initiate | ✅ | Start audio call |
522
+ | POST | /audio/accept | ✅ | Accept audio call |
523
+ | POST | /audio/reject | ✅ | Reject audio call |
524
+ | POST | /audio/end | ✅ | End audio call |
525
+ | GET | /audio/token | ✅ | Get Twilio voice token |
526
+ | GET | /history | ✅ | Call history |
527
+ | POST | /report-quality | ✅ | Report call quality |
528
+ | GET | /recordings/:callId | ✅ | Get recordings |
529
+
530
+ ### Webhooks — `/api/v1/webhooks/twilio` (★ NEW)
531
+
532
+ | Method | Path | Auth | Description |
533
+ |--------|------|------|-------------|
534
+ | POST | /video-status | Twilio Sig | Video room events |
535
+ | POST | /voice-status | Twilio Sig | Voice call events |
536
+ | POST | /voice-incoming | Twilio Sig | TwiML App handler |
537
+ | POST | /voice-complete | Twilio Sig | Dial action complete |
538
+ | POST | /voice-fallback | Twilio Sig | Error fallback |
539
+
540
+ ### Payments — `/api/v1/payments`, `/api/v1/subscriptions`
541
+
542
+ | Method | Path | Description |
543
+ |--------|------|-------------|
544
+ | POST | /payments/create-intent | Stripe PaymentIntent |
545
+ | POST | /payments/confirm | Confirm payment |
546
+ | GET | /subscriptions/plans | List plans |
547
+ | POST | /subscriptions/subscribe | Start subscription |
548
+ | POST | /subscriptions/cancel | Cancel subscription |
549
+ | POST | /webhooks/stripe | Stripe webhooks |
550
+
551
+ ### Other Endpoints
552
+
553
+ | Prefix | Purpose |
554
+ |--------|---------|
555
+ | `/api/v1/conversations` | Chat conversations |
556
+ | `/api/v1/messages` | Chat messages |
557
+ | `/api/v1/events` | Community events |
558
+ | `/api/v1/notifications` | Notification preferences |
559
+ | `/api/v1/reports` | User safety reports |
560
+ | `/api/v1/support` | Support tickets |
561
+ | `/api/v1/admin` | Admin panel operations |
562
+
563
+ ---
564
+
565
+ ## Environment Setup
566
+
567
+ ### Backend `.env`
568
+
569
+ The `.env` file includes **all original variables** from `.env.example` plus the new Twilio calling variables. Key sections:
570
+
571
+ | Section | Key Variables | Where to Get Them |
572
+ |---------|--------------|-------------------|
573
+ | Database | `MONGODB_URI` | MongoDB Atlas Console |
574
+ | Auth | `JWT_SECRET`, `JWT_REFRESH_SECRET` | Generate with `crypto.randomBytes(64)` |
575
+ | Firebase | `FIREBASE_PROJECT_ID`, `FIREBASE_PRIVATE_KEY` | Firebase Console > Service Accounts |
576
+ | AWS | `AWS_ACCESS_KEY_ID`, `AWS_S3_BUCKET` | AWS IAM Console |
577
+ | Stripe | `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET` | Stripe Dashboard > Developers |
578
+ | Twilio SMS | `TWILIO_ACCOUNT_SID`, `TWILIO_VERIFY_SERVICE_SID` | Twilio Console |
579
+ | **Twilio Calling** | `TWILIO_API_KEY_SID`, `TWILIO_TWIML_APP_SID` | **See Twilio Setup Checklist above** |
580
+ | SendGrid | `SENDGRID_API_KEY` | SendGrid Dashboard |
581
+ | Cloudinary | `CLOUDINARY_CLOUD_NAME` | Cloudinary Dashboard |
582
+ | Google | `GOOGLE_MAPS_API_KEY` | Google Cloud Console |
583
+
584
+ ### Production Overrides
585
+
586
+ For production deployment, these **must** be changed:
587
+
588
+ ```env
589
+ NODE_ENV=production
590
+ JWT_SECRET=<64-byte-random-hex>
591
+ JWT_REFRESH_SECRET=<64-byte-random-hex>
592
+ ADMIN_SECRET_KEY=<64-byte-random-hex>
593
+ ADMIN_DEFAULT_PASSWORD=<strong-unique-password>
594
+ TWILIO_WEBHOOK_VALIDATE=true # MUST be true in production
595
+ API_BASE_URL=https://api.clinchdate.com
596
+ CORS_ALLOWED_ORIGINS=https://clinchdate.com,https://admin.clinchdate.com
597
+ ```
598
+
599
+ ---
600
+
601
+ ## Deployment
602
+
603
+ | Component | Platform | Build Command |
604
+ |-----------|----------|--------------|
605
+ | Backend API | Render | `npm run build && npm start` |
606
+ | Web App | Vercel | `npm run build` |
607
+ | Admin Panel | Vercel | `npm run build` |
608
+ | Mobile (Android) | Play Store | `eas build --platform android` |
609
+ | Mobile (iOS) | App Store | `eas build --platform ios` |
610
+ | API Client SDK | npm | `npm run build && npm publish` |
611
+
612
+ ---
613
+
614
+ ## Work Completed
615
+
616
+ ### Session 1 — Backend & SDK Fixes
617
+
618
+ | # | Task | Files | Impact |
619
+ |---|------|-------|--------|
620
+ | 1 | **API Client SDK syntax fix** | `call.service.ts` | Fixed missing `<` on line 169, eliminating all 28 cascading TypeScript errors |
621
+ | 2 | **Mongoose duplicate index cleanup** | 20 model files | Removed all inline `index: true`, kept explicit `schema.index()` as single source of truth |
622
+
623
+ ### Session 2 — Twilio Calling System + Backend Audit
624
+
625
+ | # | Task | Files | Impact |
626
+ |---|------|-------|--------|
627
+ | 3 | **Call.model.ts** | 1 new model | Full call lifecycle persistence — participants, quality reports, recordings, stale cleanup |
628
+ | 4 | **twilio.config.ts** | 1 new config | Centralized Twilio env validation — fails fast at startup if anything is missing |
629
+ | 5 | **twilio.service.ts** | 1 new service | Twilio SDK wrapper — room creation, tokens (Video + Voice), TwiML generation, recordings, signature validation |
630
+ | 6 | **call.service.ts** | 1 new service | Business logic orchestration — match verification, active call checks, token generation, webhook processing, stale cleanup |
631
+ | 7 | **call.controller.ts** | 1 new controller | Express handlers for all 14 call API endpoints |
632
+ | 8 | **twilio-webhook.controller.ts** | 1 new controller | 5 webhook handlers with proper TwiML responses and error resilience |
633
+ | 9 | **twilio-webhook.middleware.ts** | 1 new middleware | Signature validation to prevent webhook spoofing |
634
+ | 10 | **call.routes.ts** | 1 new route | Authenticated routes for `/api/v1/calls/*` |
635
+ | 11 | **webhook.routes.ts** | 1 new route | Twilio webhook routes with `urlencoded` body parsing |
636
+ | 12 | **call.setup.ts** | 1 new bootstrap | One-line wiring: `setupCallSystem(app)` — creates everything and starts cleanup job |
637
+ | 13 | **Backend Audit** | 1 report | 5 critical, 7 important, 4 nice-to-have findings |
638
+
639
+ ### Session 3 — SDK Core + Env + Documentation
640
+
641
+ | # | Task | Files | Impact |
642
+ |---|------|-------|--------|
643
+ | 14 | **Backend .env** | 1 env file | Complete env with all original vars + Twilio calling additions |
644
+ | 15 | **SDK config.ts** | 1 new file | Configuration singleton with validation, platform-specific examples |
645
+ | 16 | **SDK client.ts** | 1 new file | Production HTTP client — auto-auth, silent refresh, request queuing, retry with backoff, error normalization, upload progress, response unwrapping |
646
+ | 17 | **README.md** | 1 doc | Complete technical documentation |
647
+
648
+ **Total: 33 files delivered across 3 sessions**
649
+
650
+ ---
651
+
652
+ ## Remaining Work
653
+
654
+ ### Priority 1 — Must Fix Before Launch
655
+
656
+ | Task | Effort | Details |
657
+ |------|--------|---------|
658
+ | Fix Withdrawal model (add `userId`) | 5 min | Add field + index |
659
+ | Resolve Chat vs Message duplication | 30 min | Pick one, remove the other |
660
+ | Add rate limiting | 1 hr | `express-rate-limit` + Redis |
661
+ | Add input validation (zod/joi) | 2-3 hr | Schema validation middleware |
662
+ | Harden Stripe webhooks | 1 hr | Signature validation + raw body |
663
+
664
+ ### Priority 2 — Frontend Integration
665
+
666
+ | Task | Effort | Details |
667
+ |------|--------|---------|
668
+ | Mobile: Light/dark theme system | 3-4 hr | Theme context, tokens, AsyncStorage persistence |
669
+ | Mobile: Wire call screens to SDK | 2-3 hr | Connect to Twilio Video/Voice SDKs |
670
+ | Web/Mobile: Implement i18n | 3-4 hr | 5 languages (EN, FR, DE, ES, PT) |
671
+ | Web/Mobile: Wire all API calls | 4-6 hr | Replace mocks with SDK service calls |
672
+ | Admin: Implement RBAC | 2-3 hr | Role-based access on admin routes |
673
+
674
+ ### Priority 3 — Polish
675
+
676
+ | Task | Effort | Details |
677
+ |------|--------|---------|
678
+ | Fix Profile `incrementViews` race condition | 5 min | Use `$inc` instead of `.save()` |
679
+ | Add soft-delete filter for Users | 30 min | Auto-filter `deletedAt: null` |
680
+ | Add TTL index for Boost expiration | 5 min | `{ expireAfterSeconds: 0 }` |
681
+ | Compound index optimization | 30 min | Remove redundant single-field indexes |
682
+
683
+ ---
684
+
685
+ ## Known Issues & Audit Findings
686
+
687
+ ### 🔴 Critical
688
+
689
+ 1. **Chat vs Message model duplication** — Two separate messaging systems exist. `Chat.model.ts` uses `matchId` + `senderId/recipientId`. `Message.model.ts` + `Conversation.model.ts` uses `conversationId` + `senderId/receiverId`. Pick one and remove the other.
690
+
691
+ 2. **Withdrawal.model.ts missing userId** — No way to associate withdrawals with users. Add `userId: ObjectId` field with an index.
692
+
693
+ ### 🟡 Important
694
+
695
+ 3. **No rate limiting** — Login, register, password reset endpoints are wide open to brute force.
696
+ 4. **No input validation middleware** — Controllers do basic null checks but no schema validation.
697
+ 5. **Profile.incrementViews race condition** — Uses `.save()` under concurrency. Use `$inc` atomic update.
698
+ 6. **Soft-deleted users visible in queries** — Match, Chat, and other models don't filter out `deletedAt` users.
699
+
700
+ ### 🟢 Minor
701
+
702
+ 7. **Inconsistent lean typing** — Some statics use `as unknown as Type[]`, should use `.lean<Type[]>()`
703
+ 8. **No cursor-based pagination** — List endpoints use `.skip()` which degrades at scale
704
+ 9. **Boost model lacks TTL index** — Expired boosts remain in the database indefinitely
705
+
706
+ ---
707
+
708
+ *Last updated: February 2026*
709
+ *Architecture: Adrian Ebesoh — CodersHub Innovations*
710
+
711
+