bulltrackers-module 1.0.504 → 1.0.506

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 (49) hide show
  1. package/functions/generic-api/user-api/ADDING_LEGACY_ROUTES_GUIDE.md +345 -0
  2. package/functions/generic-api/user-api/CODE_REORGANIZATION_PLAN.md +320 -0
  3. package/functions/generic-api/user-api/COMPLETE_REFACTORING_PLAN.md +116 -0
  4. package/functions/generic-api/user-api/FIRESTORE_PATHS_INVENTORY.md +171 -0
  5. package/functions/generic-api/user-api/FIRESTORE_PATH_MIGRATION_REFERENCE.md +710 -0
  6. package/functions/generic-api/user-api/FIRESTORE_PATH_VALIDATION.md +109 -0
  7. package/functions/generic-api/user-api/MIGRATION_PLAN.md +499 -0
  8. package/functions/generic-api/user-api/README_MIGRATION.md +152 -0
  9. package/functions/generic-api/user-api/REFACTORING_COMPLETE.md +106 -0
  10. package/functions/generic-api/user-api/REFACTORING_STATUS.md +85 -0
  11. package/functions/generic-api/user-api/VERIFICATION_MIGRATION_NOTES.md +206 -0
  12. package/functions/generic-api/user-api/helpers/ORGANIZATION_COMPLETE.md +126 -0
  13. package/functions/generic-api/user-api/helpers/alerts/subscription_helpers.js +327 -0
  14. package/functions/generic-api/user-api/helpers/{test_alert_helpers.js → alerts/test_alert_helpers.js} +1 -1
  15. package/functions/generic-api/user-api/helpers/collection_helpers.js +23 -45
  16. package/functions/generic-api/user-api/helpers/core/compression_helpers.js +68 -0
  17. package/functions/generic-api/user-api/helpers/core/data_lookup_helpers.js +213 -0
  18. package/functions/generic-api/user-api/helpers/core/path_resolution_helpers.js +486 -0
  19. package/functions/generic-api/user-api/helpers/core/user_status_helpers.js +77 -0
  20. package/functions/generic-api/user-api/helpers/data/computation_helpers.js +299 -0
  21. package/functions/generic-api/user-api/helpers/data/instrument_helpers.js +55 -0
  22. package/functions/generic-api/user-api/helpers/data/portfolio_helpers.js +238 -0
  23. package/functions/generic-api/user-api/helpers/data/social_helpers.js +55 -0
  24. package/functions/generic-api/user-api/helpers/data_helpers.js +85 -2750
  25. package/functions/generic-api/user-api/helpers/{dev_helpers.js → dev/dev_helpers.js} +0 -1
  26. package/functions/generic-api/user-api/helpers/{on_demand_fetch_helpers.js → fetch/on_demand_fetch_helpers.js} +33 -115
  27. package/functions/generic-api/user-api/helpers/metrics/personalized_metrics_helpers.js +360 -0
  28. package/functions/generic-api/user-api/helpers/{notification_helpers.js → notifications/notification_helpers.js} +0 -1
  29. package/functions/generic-api/user-api/helpers/profile/pi_profile_helpers.js +200 -0
  30. package/functions/generic-api/user-api/helpers/profile/profile_view_helpers.js +125 -0
  31. package/functions/generic-api/user-api/helpers/profile/user_profile_helpers.js +178 -0
  32. package/functions/generic-api/user-api/helpers/recommendations/recommendation_helpers.js +65 -0
  33. package/functions/generic-api/user-api/helpers/{review_helpers.js → reviews/review_helpers.js} +23 -107
  34. package/functions/generic-api/user-api/helpers/search/pi_request_helpers.js +177 -0
  35. package/functions/generic-api/user-api/helpers/search/pi_search_helpers.js +70 -0
  36. package/functions/generic-api/user-api/helpers/{user_sync_helpers.js → sync/user_sync_helpers.js} +54 -127
  37. package/functions/generic-api/user-api/helpers/{verification_helpers.js → verification/verification_helpers.js} +4 -43
  38. package/functions/generic-api/user-api/helpers/watchlist/watchlist_analytics_helpers.js +95 -0
  39. package/functions/generic-api/user-api/helpers/watchlist/watchlist_data_helpers.js +139 -0
  40. package/functions/generic-api/user-api/helpers/watchlist/watchlist_generation_helpers.js +306 -0
  41. package/functions/generic-api/user-api/helpers/{watchlist_helpers.js → watchlist/watchlist_management_helpers.js} +62 -213
  42. package/functions/generic-api/user-api/index.js +9 -9
  43. package/functions/task-engine/handler_creator.js +7 -6
  44. package/package.json +1 -1
  45. package/functions/generic-api/API_MIGRATION_PLAN.md +0 -436
  46. package/functions/generic-api/user-api/helpers/FALLBACK_CONDITIONS.md +0 -98
  47. package/functions/generic-api/user-api/helpers/HISTORY_STORAGE_LOCATION.md +0 -66
  48. package/functions/generic-api/user-api/helpers/subscription_helpers.js +0 -512
  49. /package/functions/generic-api/user-api/helpers/{alert_helpers.js → alerts/alert_helpers.js} +0 -0
@@ -250,10 +250,10 @@ async function handleRequest(message, context, configObj, dependencies) {
250
250
  await handlePopularInvestorUpdate(taskData, configObj, dependencies);
251
251
  break;
252
252
  case 'ON_DEMAND_USER_UPDATE':
253
- // For ON_DEMAND_USER_UPDATE, the entire payload IS the task data
254
- // (not wrapped in a 'data' field like other task types)
255
- // Extract task data from payload, excluding 'type'
256
- const onDemandTaskData = data || {
253
+ // For ON_DEMAND_USER_UPDATE, the payload contains both top-level fields (cid, username, etc.)
254
+ // and a nested 'data' object (includeSocial, since, etc.)
255
+ // Merge both to create the complete task data
256
+ const onDemandTaskData = {
257
257
  cid: payload.cid,
258
258
  username: payload.username,
259
259
  requestId: payload.requestId,
@@ -262,11 +262,12 @@ async function handleRequest(message, context, configObj, dependencies) {
262
262
  effectiveRequestedBy: payload.effectiveRequestedBy,
263
263
  metadata: payload.metadata,
264
264
  priority: payload.priority,
265
- data: payload.data // Include nested data object (includeSocial, since, etc.)
265
+ // Merge nested data object if it exists
266
+ ...(data || payload.data || {})
266
267
  };
267
268
 
268
269
  if (!onDemandTaskData.cid || !onDemandTaskData.username) {
269
- logger.log('ERROR', `[TaskEngine] ON_DEMAND_USER_UPDATE missing required fields (cid or username)`, { payload, onDemandTaskData });
270
+ logger.log('ERROR', `[TaskEngine] ON_DEMAND_USER_UPDATE missing required fields (cid or username)`, { payload, data, onDemandTaskData });
270
271
  return;
271
272
  }
272
273
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.504",
3
+ "version": "1.0.506",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -1,436 +0,0 @@
1
- # API Migration Plan: User-Centric Collection Structure
2
-
3
- ## Status: ✅ **MIGRATION COMPLETE**
4
-
5
- ## Overview
6
- This document outlines the migration of the User API to use the new user-centric collection structure from the `collection_registry.js`. The goal is to use eToro CIDs and Post IDs for document paths where possible, while maintaining backward compatibility with existing user signup data.
7
-
8
- **All endpoints have been updated to use the new collection structure with backward compatibility fallbacks.**
9
-
10
- ## Summary of Completed Changes
11
-
12
- ### ✅ Phase 1: Low-Risk, High-Value
13
- 1. **User Social Posts** - ✅ Updated `getUserSocialPosts()` to use `signedInUsers/{cid}/posts` with legacy fallback
14
- 2. **User Sync Requests** - ✅ Updated `requestUserSync()` and `getUserSyncStatus()` to use `signedInUsers/{cid}/syncRequests` and `signedInUsers/{cid}/syncStatus`
15
- 3. **User Notifications** - ⚠️ **Note:** Currently uses Firebase UID for frontend auth compatibility. CID lookup helper created but not yet integrated into notification writes (alert system handles this separately)
16
-
17
- ### ✅ Phase 2: Medium Complexity
18
- 4. **User Portfolio** - ✅ Updated `getUserPortfolio()` and `findLatestPortfolioDate()` to try:
19
- - `signedInUsers/{cid}/portfolio/latest` (user-centric)
20
- - `SignedInUserPortfolioData/{date}/{cid}` (root data)
21
- - `signed_in_users/19M/snapshots/{date}/parts/part_X` (legacy fallback)
22
- 5. **User Trade History** - ✅ Helper function `findLatestPortfolioDate()` updated to support new structure (used internally by `getUserComputations()`)
23
-
24
- ### ✅ Phase 3: Higher Complexity
25
- 6. **User Watchlists** - ✅ All CRUD operations updated:
26
- - `getUserWatchlists()` - reads from `signedInUsers/{cid}/watchlists`
27
- - `getWatchlist()` - reads from new structure
28
- - `createWatchlist()` - writes to both structures
29
- - `updateWatchlist()` - updates both structures
30
- - `deleteWatchlist()` - deletes from both structures
31
- 7. **User Verification** - ✅ Updated `finalizeVerification()` to write to `signedInUsers/{cid}/verification` and store user mapping
32
- 8. **Alert Subscriptions** - ✅ Dual structure implemented:
33
- - Per-watchlist: `signedInUsers/{cid}/watchlists/{watchlistId}/subscriptions/{piCid}`
34
- - Global: `signedInUsers/{cid}/subscriptions/{piCid}`
35
- - All CRUD operations updated
36
- 9. **Popular Investor Data** - ✅ All endpoints updated:
37
- - Fetch Requests: `popularInvestors/{piCid}/fetchRequests/{requestId}`
38
- - Fetch Status: `popularInvestors/{piCid}/fetchStatus`
39
- - User Fetch Requests: `popularInvestors/{piCid}/userFetchRequests/{userCid}` (added to registry)
40
- - Reviews: `popularInvestors/{piCid}/reviews/{reviewId}`
41
- - Profile Views: `popularInvestors/{piCid}/profileViews/{date}`
42
- - Individual Views: `popularInvestors/{piCid}/views/{viewId}`
43
-
44
- ## Current vs New Collection Paths
45
-
46
- ### 1. User Portfolio Data ✅ **COMPLETED**
47
-
48
- **Current:**
49
- - `signed_in_users/19M/snapshots/{date}/parts/part_{shardIndex}` (sharded, legacy)
50
- - Lookup: Search through all parts to find user's CID
51
-
52
- **New (Registry):**
53
- - **Root Data (for computations):** `SignedInUserPortfolioData/{date}/{cid}` ✅ **IMPLEMENTED**
54
- - **User-Centric (for fallback):** `signedInUsers/{cid}/portfolio/latest` ✅ **IMPLEMENTED**
55
-
56
- **API Endpoints Affected:**
57
- - `GET /user/me/portfolio` - `getUserPortfolio()` in `data_helpers.js` ✅ **UPDATED**
58
-
59
- **Migration Strategy:**
60
- 1. ✅ Try new structure first: `signedInUsers/{cid}/portfolio/latest`
61
- 2. ✅ Fallback to root data: `SignedInUserPortfolioData/{date}/{cid}` (find latest date)
62
- 3. ✅ Fallback to legacy: `signed_in_users/19M/snapshots/{date}/parts/part_X` (for backward compatibility)
63
-
64
- ---
65
-
66
- ### 2. User Trade History Data ✅ **COMPLETED** (Helper function updated)
67
-
68
- **Current:**
69
- - `signed_in_user_history/19M/snapshots/{date}/parts/part_{shardIndex}` (sharded, legacy)
70
- - Lookup: Search through all parts to find user's CID
71
-
72
- **New (Registry):**
73
- - **Root Data (for computations):** `SignedInUserTradeHistoryData/{date}/{cid}` ✅ **IMPLEMENTED**
74
- - **User-Centric (for fallback):** `signedInUsers/{cid}/tradeHistory/latest` ✅ **IMPLEMENTED**
75
-
76
- **API Endpoints Affected:**
77
- - None currently exposed, but used internally by `getUserComputations()` ✅ **Helper function updated**
78
-
79
- **Migration Strategy:**
80
- 1. ✅ Try new structure first: `signedInUsers/{cid}/tradeHistory/latest`
81
- 2. ✅ Fallback to root data: `SignedInUserTradeHistoryData/{date}/{cid}` (find latest date)
82
- 3. ✅ Fallback to legacy: `signed_in_user_history/19M/snapshots/{date}/parts/part_X`
83
-
84
- ---
85
-
86
- ### 3. User Social Posts ✅ **COMPLETED**
87
-
88
- **Current:**
89
- - `signed_in_users_social/{cid}/posts/{postId}` ✅ **Already using CID!**
90
-
91
- **New (Registry):**
92
- - **Root Data (for computations):** `SignedInUserSocialPostData/{date}/{cid}` ✅ **IMPLEMENTED**
93
- - **User-Centric (for fallback):** `signedInUsers/{cid}/posts/{postId}` ✅ **IMPLEMENTED**
94
-
95
- **API Endpoints Affected:**
96
- - `GET /user/me/social-posts` - `getUserSocialPosts()` in `data_helpers.js` ✅ **UPDATED**
97
-
98
- **Migration Strategy:**
99
- 1. ✅ Try new user-centric: `signedInUsers/{cid}/posts` (ordered by `fetchedAt` desc)
100
- 2. ✅ Fallback to current: `signed_in_users_social/{cid}/posts` (for backward compatibility)
101
- 3. Note: Root data structure is different (nested under date), so not suitable for direct API access
102
-
103
- ---
104
-
105
- ### 4. User Notifications
106
-
107
- **Current:**
108
- - `user_notifications/{firebaseUid}/notifications/{notificationId}`
109
-
110
- **New (Registry - Updated for CID consistency):**
111
- - `signedInUsers/{cid}/notifications/{notificationId}` ⚠️ **Change to use CID instead of Firebase UID**
112
-
113
- **API Endpoints Affected:**
114
- - Frontend `SyncNotificationContext` - reads directly, but API can provide CID lookup
115
- - `POST /user/dev/test-alert` - `sendTestAlert()` in `test_alert_helpers.js` (needs update)
116
- - Alert system - `processAlertForPI()` in `alert_helpers.js` (needs update)
117
-
118
- **Migration Strategy:**
119
- 1. **API Lookup:** When frontend requests notifications, API looks up CID from Firebase UID
120
- 2. **Write:** All notification writes use CID (alert system, test alerts, etc.)
121
- 3. **Read:** API endpoint to get notifications by CID (frontend can call with Firebase UID, API resolves to CID)
122
- 4. **Note:** Frontend `SyncNotificationContext` may need to be updated to use CID, or API can provide a mapping endpoint
123
-
124
- **Implementation:**
125
- - ✅ Create helper: `getCidFromFirebaseUid(db, firebaseUid)` - looks up CID from `signedInUsers` collection (in `collection_helpers.js`)
126
- - ⚠️ Update all notification writes to use CID (alert system and test alerts already handle this via Firebase UID lookup)
127
- - ⚠️ Update frontend to either:
128
- - Option A: Use CID directly (if available in auth context)
129
- - Option B: Call API endpoint that accepts Firebase UID and returns notifications (API does CID lookup)
130
-
131
- **Note:** Notifications currently use Firebase UID for frontend authentication compatibility. The helper function exists but notification writes are handled separately by the alert system which already performs CID→Firebase UID mapping.
132
-
133
- ---
134
-
135
- ### 5. User Verification ✅ **COMPLETED**
136
-
137
- **Current:**
138
- - `user_verifications/{username}` (keyed by username)
139
-
140
- **New (Registry):**
141
- - `signedInUsers/{cid}/verification` ✅ **IMPLEMENTED** (singleton document, not subcollection)
142
-
143
- **API Endpoints Affected:**
144
- - `POST /user/verify/init` - `initiateVerification()` in `verification_helpers.js` (still uses legacy for init)
145
- - `POST /user/verify/finalize` - `finalizeVerification()` in `verification_helpers.js` ✅ **UPDATED**
146
- - `GET /user/me/verification` - `getUserVerification()` in `data_helpers.js` (should read from new structure)
147
-
148
- **Migration Strategy:**
149
- 1. **During Init:**
150
- - Store in legacy location: `user_verifications/{username}` (for backward compatibility)
151
- - Note: CID not available yet, but that's okay - we'll migrate during finalize
152
- 2. **During Finalize:** ✅ **IMPLEMENTED**
153
- - API call to eToro returns `realCID` in the response schema
154
- - Store in new location: `signedInUsers/{realCID}/verification` (singleton document)
155
- - Also update legacy location for backward compatibility
156
- - Store user mapping: `signedInUsers/{realCID}` → `{ etoroCID: realCID, username, ... }` ✅ **IMPLEMENTED**
157
- 3. **Read:** Try new structure first (using CID), fallback to legacy (using username)
158
-
159
- **Implementation Notes:**
160
- - The eToro API response during `finalizeVerification()` includes:
161
- - `realCID`: The eToro CID we want to use
162
- - `gcid`: Group CID (may be different)
163
- - `username`: Username
164
- - `aboutMeShort`: Contains the OTP code
165
- - We can use `realCID` immediately during finalization
166
- - Store both CID and username in the verification document for lookup flexibility
167
-
168
- ---
169
-
170
- ### 6. User Watchlists ✅ **COMPLETED**
171
-
172
- **Current:**
173
- - `watchlists/{userCid}/lists/{watchlistId}` ✅ **Already using CID!**
174
- - `public_watchlists/{watchlistId}` (for public watchlists) ✅ **Already correct!**
175
-
176
- **New (Registry):**
177
- - `signedInUsers/{cid}/watchlists/{watchlistId}` (private watchlists) ✅ **IMPLEMENTED**
178
- - `public_watchlists/{watchlistId}` (public watchlists) ✅ **No change needed**
179
-
180
- **API Endpoints Affected:**
181
- - `GET /user/me/watchlists` - `getUserWatchlists()` in `watchlist_helpers.js` ✅ **UPDATED**
182
- - `GET /user/me/watchlists/:id` - `getWatchlist()` in `watchlist_helpers.js` ✅ **UPDATED**
183
- - `POST /user/me/watchlists` - `createWatchlist()` in `watchlist_helpers.js` ✅ **UPDATED**
184
- - `PUT /user/me/watchlists/:id` - `updateWatchlistById()` in `watchlist_helpers.js` ✅ **UPDATED**
185
- - `DELETE /user/me/watchlists/:id` - `deleteWatchlist()` in `watchlist_helpers.js` ✅ **UPDATED**
186
- - `POST /user/me/watchlists/:id/copy` - `copyWatchlist()` in `watchlist_helpers.js` (uses same write logic)
187
- - `POST /user/me/watchlists/:id/publish` - `publishWatchlistVersion()` in `watchlist_helpers.js` (uses public_watchlists)
188
- - `GET /user/public-watchlists` - `getPublicWatchlists()` in `watchlist_helpers.js` (uses public_watchlists)
189
-
190
- **Migration Strategy:**
191
- 1. ✅ **Private Watchlists:** Migrated from `watchlists/{cid}/lists` to `signedInUsers/{cid}/watchlists`
192
- 2. ✅ **Public Watchlists:** Keep in separate collection (already correct)
193
- 3. ✅ **Read:** Try new structure first, fallback to legacy
194
- 4. ✅ **Write:** Write to both locations during migration period
195
-
196
- ---
197
-
198
- ### 7. User Sync Requests & Status ✅ **COMPLETED**
199
-
200
- **Current:**
201
- - `user_sync_requests/{cid}/requests/{requestId}` (history)
202
- - `user_sync_requests/{cid}/global/latest` (singleton)
203
-
204
- **New (Registry):**
205
- - `signedInUsers/{cid}/syncStatus` (singleton) ✅ **IMPLEMENTED**
206
- - `signedInUsers/{cid}/syncRequests/{requestId}` (history) ✅ **IMPLEMENTED**
207
-
208
- **API Endpoints Affected:**
209
- - `POST /user/:userCid/sync` - `requestUserSync()` in `user_sync_helpers.js` ✅ **UPDATED**
210
- - `GET /user/:userCid/sync-status` - `getUserSyncStatus()` in `user_sync_helpers.js` ✅ **UPDATED**
211
-
212
- **Migration Strategy:**
213
- 1. ✅ **Write:** Write to both locations during migration
214
- 2. ✅ **Read:** Try new structure first, fallback to legacy
215
- 3. **Note:** Task engine already writes to new location, so API should read from there
216
-
217
- ---
218
-
219
- ### 8. Alert Subscriptions ✅ **COMPLETED**
220
-
221
- **Current (Actual paths in code):**
222
- - `watchlist_subscriptions/{userCid}/alerts/{piCid}`
223
-
224
- **New (Registry - Dual Structure):**
225
- - **Per-User Subscriptions:** `signedInUsers/{cid}/subscriptions/{piCid}` (global subscriptions, not tied to a watchlist) ✅ **IMPLEMENTED**
226
- - **Per-Watchlist Subscriptions:** `signedInUsers/{cid}/watchlists/{watchlistId}/subscriptions/{piCid}` (subscriptions for a PI within a specific watchlist) ✅ **IMPLEMENTED**
227
-
228
- **API Endpoints Affected:**
229
- - `POST /user/me/subscriptions` - `subscribeToAlerts()` in `subscription_helpers.js` ✅ **UPDATED**
230
- - `GET /user/me/subscriptions` - `getUserSubscriptions()` in `subscription_helpers.js` ✅ **UPDATED**
231
- - `PUT /user/me/subscriptions/:piCid` - `updateSubscription()` in `subscription_helpers.js` ✅ **UPDATED**
232
- - `DELETE /user/me/subscriptions/:piCid` - `unsubscribeFromAlerts()` in `subscription_helpers.js` ✅ **UPDATED**
233
- - `POST /user/me/watchlists/:id/subscribe-all` - `subscribeToWatchlist()` in `subscription_helpers.js` ✅ **UPDATED**
234
-
235
- **Migration Strategy:**
236
- 1. ✅ **Dual Structure:**
237
- - If subscription has `watchlistId`, store in: `signedInUsers/{cid}/watchlists/{watchlistId}/subscriptions/{piCid}`
238
- - If subscription is global (no watchlistId), store in: `signedInUsers/{cid}/subscriptions/{piCid}`
239
- 2. ✅ **Read:**
240
- - For watchlist-specific: Read from `signedInUsers/{cid}/watchlists/{watchlistId}/subscriptions/{piCid}`
241
- - For global: Read from `signedInUsers/{cid}/subscriptions/{piCid}`
242
- - For "all subscriptions": Read from both locations and merge
243
- 3. ✅ **Write:** Write to both new structure and legacy during migration period
244
- 4. **Backward Compatibility:** Legacy structure `watchlist_subscriptions/{userCid}/alerts/{piCid}` can be migrated by checking if `watchlistId` exists in the subscription data
245
-
246
- ---
247
-
248
- ### 9. Popular Investor Data ✅ **COMPLETED**
249
-
250
- **Current (Actual paths in code):**
251
- - `pi_fetch_requests/{piCid}/requests/{requestId}` ⚠️ **Different from registry**
252
- - `pi_fetch_requests/{piCid}/user_requests/{userCid}` ⚠️ **Different from registry**
253
- - `pi_fetch_requests/{piCid}/global/latest` ⚠️ **Different from registry**
254
- - `pi_reviews/{reviewId}` (flat collection, reviewId = `{userCid}_{piCid}`) ⚠️ **Different from registry**
255
- - `profile_views/{piCid}_{date}` (flat collection with compound key) ⚠️ **Different from registry**
256
- - `profile_views/individual_views/views/{individualViewId}` ⚠️ **Different from registry**
257
-
258
- **New (Registry):**
259
- - `popularInvestors/{piCid}/fetchRequests/{requestId}` ✅ **IMPLEMENTED**
260
- - `popularInvestors/{piCid}/fetchStatus` (singleton) ✅ **IMPLEMENTED**
261
- - `popularInvestors/{piCid}/userFetchRequests/{userCid}` ✅ **IMPLEMENTED** (for user rate limits, added to registry)
262
- - `popularInvestors/{piCid}/reviews/{reviewId}` ✅ **IMPLEMENTED**
263
- - `popularInvestors/{piCid}/profileViews/{date}` ✅ **IMPLEMENTED**
264
- - `popularInvestors/{piCid}/views/{viewId}` ✅ **IMPLEMENTED**
265
-
266
- **API Endpoints Affected:**
267
- - `POST /user/pi/:piCid/request-fetch` - `requestPiFetch()` in `on_demand_fetch_helpers.js` ✅ **UPDATED**
268
- - `GET /user/pi/:piCid/fetch-status` - `getPiFetchStatus()` in `on_demand_fetch_helpers.js` ✅ **UPDATED**
269
- - `POST /user/review` - `submitReview()` in `review_helpers.js` ✅ **UPDATED**
270
- - `GET /user/reviews/:piCid` - `getReviews()` in `review_helpers.js` ✅ **UPDATED**
271
- - `GET /user/me/review/:piCid` - `getUserReview()` in `review_helpers.js` ✅ **UPDATED**
272
- - `POST /user/pi/:piCid/track-view` - `trackProfileView()` in `data_helpers.js` ✅ **UPDATED**
273
- - `GET /user/me/pi-personalized-metrics` - `getSignedInUserPIPersonalizedMetrics()` in `data_helpers.js` (reads profile views)
274
-
275
- **Migration Strategy:**
276
- 1. ✅ **Fetch Requests:** Migrated from `pi_fetch_requests/{piCid}/requests` to `popularInvestors/{piCid}/fetchRequests`
277
- 2. ✅ **Fetch Status:** Migrated from `pi_fetch_requests/{piCid}/global/latest` to `popularInvestors/{piCid}/fetchStatus`
278
- 3. ✅ **User Fetch Requests:** Migrated from `pi_fetch_requests/{piCid}/user_requests/{userCid}` to `popularInvestors/{piCid}/userFetchRequests/{userCid}`
279
- 4. ✅ **Reviews:** Migrated from flat `pi_reviews/{reviewId}` to `popularInvestors/{piCid}/reviews/{reviewId}`
280
- - **Solution:** Keep reviewId as `{userCid}_{piCid}`, but store under PI's subcollection
281
- 5. ✅ **Profile Views:** Migrated
282
- - From `profile_views/{piCid}_{date}` to `popularInvestors/{piCid}/profileViews/{date}`
283
- - From `profile_views/individual_views/views/{viewId}` to `popularInvestors/{piCid}/views/{viewId}`
284
- - **Solution:** Parse compound key to extract date, store under PI's subcollection
285
- 6. ✅ **Read:** Try new structure first, fallback to legacy
286
- 7. ✅ **Write:** Write to both locations during migration period
287
-
288
- ---
289
-
290
- ## Implementation Priority
291
-
292
- ### Phase 1: Low-Risk, High-Value (Start Here) ✅ **COMPLETED**
293
- 1. ✅ **User Social Posts** - Simple path change, already using CID
294
- 2. ✅ **User Sync Requests** - Task engine already writes to new location
295
- 3. ⚠️ **User Notifications** - Update to use CID (add CID lookup helper) - Helper created, but notifications use Firebase UID for frontend compatibility
296
-
297
- ### Phase 2: Medium Complexity ✅ **COMPLETED**
298
- 5. ✅ **User Portfolio** - Needs fallback logic, but straightforward
299
- 6. ✅ **User Trade History** - Similar to portfolio, used internally
300
-
301
- ### Phase 3: Higher Complexity ✅ **COMPLETED**
302
- 7. ✅ **User Watchlists** - Multiple endpoints, but clear migration path
303
- 8. ✅ **User Verification** - CID available during finalize (from API response), migrate during finalization
304
- 9. ✅ **Popular Investor Data** - Multiple subcollections (fetch requests, reviews, profile views) need migration
305
- 10. ✅ **Alert Subscriptions** - Dual structure (per-user vs per-watchlist) implemented
306
-
307
- ---
308
-
309
- ## Helper Functions Needed
310
-
311
- ### 1. CID Lookup Helper ✅ **COMPLETED**
312
- Helper to get CID from Firebase UID (for API endpoints that receive Firebase UID):
313
- ```javascript
314
- async function getCidFromFirebaseUid(db, firebaseUid) {
315
- const userDoc = await db.collection('signedInUsers').doc(firebaseUid).get();
316
- if (!userDoc.exists) return null;
317
- const data = userDoc.data();
318
- return data.etoroCID || null;
319
- }
320
- ```
321
- ✅ **IMPLEMENTED** in `collection_helpers.js`
322
-
323
- ### 2. Collection Path Resolver ✅ **COMPLETED**
324
- Create a helper that uses `collectionRegistry` to resolve paths:
325
- ```javascript
326
- function getUserCollectionPath(category, subcategory, params) {
327
- const { collectionRegistry } = dependencies;
328
- return collectionRegistry.getCollectionPath('signedInUsers', subcategory, params);
329
- }
330
- ```
331
- ✅ **IMPLEMENTED** in `collection_helpers.js` as `getCollectionPath()`
332
-
333
- ### 3. Dual Read Helper ✅ **COMPLETED**
334
- Helper to read from new structure with legacy fallback:
335
- ```javascript
336
- async function readWithFallback(newPath, legacyPath, db) {
337
- // Try new structure first
338
- const newDoc = await db.doc(newPath).get();
339
- if (newDoc.exists) return newDoc.data();
340
-
341
- // Fallback to legacy
342
- const legacyDoc = await db.doc(legacyPath).get();
343
- if (legacyDoc.exists) return legacyDoc.data();
344
-
345
- return null;
346
- }
347
- ```
348
- ✅ **IMPLEMENTED** in `collection_helpers.js`
349
-
350
- ### 4. Dual Write Helper ✅ **COMPLETED**
351
- Helper to write to both locations during migration:
352
- ```javascript
353
- async function writeDual(newPath, legacyPath, data, db) {
354
- const batch = db.batch();
355
- batch.set(db.doc(newPath), data);
356
- batch.set(db.doc(legacyPath), data);
357
- await batch.commit();
358
- }
359
- ```
360
- ✅ **IMPLEMENTED** in `collection_helpers.js`
361
-
362
- ---
363
-
364
- ## Migration Checklist
365
-
366
- ### Immediate Actions ✅ **ALL COMPLETED**
367
- - [x] Inject `collectionRegistry` into API dependencies ✅ **COMPLETED** (in `index.js` line 50)
368
- - [x] Create helper functions for path resolution and dual read/write ✅ **COMPLETED** (`collection_helpers.js`)
369
- - [x] Update `getUserSocialPosts()` to use new path ✅ **COMPLETED**
370
- - [x] Update `getUserPortfolio()` to use new path with fallback ✅ **COMPLETED**
371
- - [x] Update sync request/status endpoints to use new paths ✅ **COMPLETED**
372
-
373
- ### Short-Term (Next Sprint) ✅ **ALL COMPLETED**
374
- - [x] Update watchlist endpoints to use new paths ✅ **COMPLETED**
375
- - [x] Update verification endpoints (handle CID lookup challenge) ✅ **COMPLETED**
376
- - [x] Update Popular Investor data endpoints (fetch requests, reviews, profile views) ✅ **COMPLETED**
377
- - [x] Update subscription endpoints (dual structure: per-user and per-watchlist) ✅ **COMPLETED**
378
- - [ ] Add migration logging to track usage of legacy vs new paths (optional enhancement)
379
-
380
- ### Long-Term (Post-Migration)
381
- - [ ] Remove legacy path fallbacks once migration is complete (after 1-2 month migration period)
382
- - [ ] Create data migration script to move existing data to new structure
383
- - [ ] Update documentation
384
-
385
- ---
386
-
387
- ## Testing Strategy
388
-
389
- 1. **Unit Tests:** Test each helper function with both new and legacy paths
390
- 2. **Integration Tests:** Test API endpoints with:
391
- - Data only in new structure
392
- - Data only in legacy structure
393
- - Data in both (should prefer new)
394
- - No data (should return 404)
395
- 3. **Migration Tests:** Verify dual writes work correctly
396
- 4. **Backward Compatibility:** Ensure existing users can still access their data
397
-
398
- ---
399
-
400
- ## Notes
401
-
402
- - **CID Consistency:** Use eToro CID (`realCID`) everywhere for user identification. Firebase UID is only used for authentication, but all data paths should use CID.
403
- - **CID Lookup:** When frontend provides Firebase UID, API should look up CID from `signedInUsers/{firebaseUid}` mapping document.
404
- - **Verification:** CID is available immediately during `finalizeVerification()` from the eToro API response (`realCID` field).
405
- - **Post IDs:** Social posts already use post IDs correctly
406
- - **Backward Compatibility:** Maintain legacy paths during migration period (estimate 1-2 months)
407
- - **Data Migration:** Will need a separate script to migrate existing data to new structure
408
- - **User Mapping:** The `signedInUsers` collection should maintain a mapping: `signedInUsers/{firebaseUid}` → `{ etoroCID, etoroUsername, ... }` for CID lookup
409
-
410
- ## Path Structure Verification
411
-
412
- All implemented paths match the `collection_registry.js` definitions:
413
-
414
- ### Signed-In Users (User-Centric)
415
- - ✅ `signedInUsers/{cid}/portfolio/latest` - matches registry
416
- - ✅ `signedInUsers/{cid}/tradeHistory/latest` - matches registry
417
- - ✅ `signedInUsers/{cid}/posts/{postId}` - matches registry
418
- - ✅ `signedInUsers/{cid}/verification` - matches registry (singleton)
419
- - ✅ `signedInUsers/{cid}/watchlists/{watchlistId}` - matches registry
420
- - ✅ `signedInUsers/{cid}/syncRequests/{requestId}` - matches registry
421
- - ✅ `signedInUsers/{cid}/syncStatus` - matches registry (singleton)
422
- - ✅ `signedInUsers/{cid}/subscriptions/{piCid}` - matches registry
423
- - ✅ `signedInUsers/{cid}/watchlists/{watchlistId}/subscriptions/{piCid}` - matches registry
424
-
425
- ### Popular Investors
426
- - ✅ `popularInvestors/{piCid}/fetchRequests/{requestId}` - matches registry
427
- - ✅ `popularInvestors/{piCid}/fetchStatus` - matches registry (singleton)
428
- - ✅ `popularInvestors/{piCid}/userFetchRequests/{userCid}` - matches registry (added during migration)
429
- - ✅ `popularInvestors/{piCid}/reviews/{reviewId}` - matches registry
430
- - ✅ `popularInvestors/{piCid}/profileViews/{date}` - matches registry
431
- - ✅ `popularInvestors/{piCid}/views/{viewId}` - matches registry
432
-
433
- ### Root Data (Date-Based)
434
- - ✅ `SignedInUserPortfolioData/{date}/{cid}` - matches registry
435
- - ✅ `SignedInUserTradeHistoryData/{date}/{cid}` - matches registry
436
- - ✅ `SignedInUserSocialPostData/{date}/{cid}` - matches registry
@@ -1,98 +0,0 @@
1
- # Profile Page Fallback Conditions
2
-
3
- ## Overview
4
-
5
- This document explains when and why the profile page uses fallback mode instead of computation data.
6
-
7
- ## Data Storage Locations
8
-
9
- ### Signed-In User Computations
10
- - **Category**: `popular-investor` (NOT `signed_in_user`)
11
- - **Reason**: ResultCommitter ignores category metadata when computation is in a non-core folder
12
- - **Computation**: `SignedInUserProfileMetrics` is in `calculations/popular-investor/` folder
13
- - **Path**: `/unified_insights/{date}/results/popular-investor/computations/SignedInUserProfileMetrics`
14
-
15
- ### Signed-In User Raw Data
16
- - **Portfolio**: `signed_in_users/{blockId}/snapshots/{date}/parts/part_*`
17
- - **History**: `signed_in_user_history/{blockId}/snapshots/{date}/parts/part_*` (or `signed_in_users_history` depending on config)
18
- - **Social**: `signed_in_users_social/{cid}/posts/{postId}`
19
-
20
- ## Fallback Conditions
21
-
22
- ### Frontend Fallback Logic (UserProfile.tsx)
23
-
24
- The profile page uses fallback mode when:
25
-
26
- 1. **Computation Not Found**:
27
- - API call to `/user/me/computations?computation=SignedInUserProfileMetrics` returns empty `data: {}`
28
- - OR the computation exists but doesn't contain data for the user's CID
29
-
30
- 2. **API Error**:
31
- - Any error fetching the computation (network error, 500, etc.)
32
-
33
- 3. **Data Structure Issues**:
34
- - Computation exists but `metricsResponse.data[latestDate]?.SignedInUserProfileMetrics` is undefined/null
35
-
36
- ### Backend Fallback Logic (getUserComputations)
37
-
38
- The API returns `isFallback: false` but `data: {}` when:
39
-
40
- 1. **Wrong Category Lookup** (FIXED):
41
- - Was looking in `signed_in_user` category
42
- - Should look in `popular-investor` category
43
-
44
- 2. **No Data for User**:
45
- - Computation document exists but doesn't contain `data[String(effectiveCid)]`
46
- - This can happen if:
47
- - Computation ran but user wasn't included (shouldn't happen with targetCid optimization)
48
- - Data is sharded and shard doesn't contain user's data
49
- - Data exists but in wrong format
50
-
51
- 3. **Date Mismatch**:
52
- - Computation exists for different date than requested
53
- - `mode=latest` tries to find latest date, but if none found, returns empty
54
-
55
- ## Current Issue
56
-
57
- The user reported:
58
- - API returns `"isFallback": false, "data": {}`
59
- - Profile page shows fallback mode
60
- - Computation exists at `/unified_insights/2025-12-29/results/popular-investor/computations/SignedInUserProfileMetrics/_shards/shard_0`
61
-
62
- **Root Cause**: The `getUserComputations` endpoint was looking in the wrong category (`signed_in_user` instead of `popular-investor`).
63
-
64
- **Fix Applied**: Updated `getUserComputations` to look in `popular-investor` category for `SignedInUserProfileMetrics`.
65
-
66
- ## Verification Steps
67
-
68
- 1. Check computation exists:
69
- ```
70
- /unified_insights/2025-12-29/results/popular-investor/computations/SignedInUserProfileMetrics
71
- ```
72
-
73
- 2. Check if data is sharded:
74
- - If `_sharded: true`, check `_shards` subcollection
75
- - Merge all shards to get full data
76
-
77
- 3. Check if user's CID exists in data:
78
- - Data structure: `{ "29312236": { ...metrics... } }`
79
- - Must use `String(cid)` as key
80
-
81
- 4. Check API response:
82
- - Should return `data: { "2025-12-29": { "SignedInUserProfileMetrics": { ... } } }`
83
- - If empty, check logs for why lookup failed
84
-
85
- ## History Data Storage
86
-
87
- Signed-in user trade history is stored in:
88
- - Collection: `signed_in_user_history` (or `signed_in_users_history` depending on config)
89
- - Path: `{collection}/{blockId}/snapshots/{date}/parts/part_*`
90
- - Block ID: Typically `19M` (canary block)
91
-
92
- The task engine stores history via:
93
- ```javascript
94
- await batchManager.addToTradingHistoryBatch(String(cid), blockId, today, historyData, 'signed_in_user');
95
- ```
96
-
97
- This uses `getHistoryCollection('signed_in_user')` which returns `signedInHistory` collection.
98
-
@@ -1,66 +0,0 @@
1
- # Signed-In User History Storage Location
2
-
3
- ## Current Issue
4
-
5
- History data is being stored in the **wrong collection** because `FIRESTORE_COLLECTION_SIGNED_IN_HISTORY` is not defined in the task engine config, causing it to fall back to `NormalUserTradeHistory`.
6
-
7
- ## Storage Path Structure
8
-
9
- For signed-in users, history data should be stored at:
10
- ```
11
- {signed_in_user_history}/{blockId}/snapshots/{date}/parts/part_{shardIndex}
12
- ```
13
-
14
- Where:
15
- - `signed_in_user_history` = Collection name (default: `signed_in_user_history`)
16
- - `blockId` = `'19M'` (canary block ID)
17
- - `date` = Date string in format `YYYY-MM-DD` (e.g., `2025-12-29`)
18
- - `shardIndex` = `cid % TOTAL_SHARDS` (modulo sharding for even distribution)
19
-
20
- ## Example Path
21
-
22
- For user CID `29312236` on date `2025-12-29`:
23
- ```
24
- signed_in_user_history/19M/snapshots/2025-12-29/parts/part_{shardIndex}
25
- ```
26
-
27
- Where `shardIndex = 29312236 % TOTAL_SHARDS` (typically 7 shards, so `29312236 % 7 = 1`)
28
-
29
- ## Fix Applied
30
-
31
- Added `FIRESTORE_COLLECTION_SIGNED_IN_HISTORY` to `taskEngine_config.js`:
32
- ```javascript
33
- FIRESTORE_COLLECTION_SIGNED_IN_HISTORY: getEnvVar('FIRESTORE_COLLECTION_SIGNED_IN_HISTORY', 'signed_in_user_history'),
34
- ```
35
-
36
- ## Before Fix
37
-
38
- Without this config, the batch manager was using:
39
- - `config.FIRESTORE_COLLECTION_SIGNED_IN_HISTORY` = `undefined`
40
- - Fallback: `config.FIRESTORE_COLLECTION_NORMAL_HISTORY` = `'NormalUserTradeHistory'`
41
-
42
- So data was being stored at:
43
- ```
44
- NormalUserTradeHistory/19M/snapshots/2025-12-29/parts/part_{shardIndex}
45
- ```
46
-
47
- ## After Fix
48
-
49
- With the config added, data will be stored at:
50
- ```
51
- signed_in_user_history/19M/snapshots/2025-12-29/parts/part_{shardIndex}
52
- ```
53
-
54
- ## Verification
55
-
56
- To verify history data exists for a user:
57
- 1. Calculate shard index: `userCid % 7` (or whatever TOTAL_SHARDS is)
58
- 2. Check path: `signed_in_user_history/19M/snapshots/2025-12-29/parts/part_{shardIndex}`
59
- 3. Look for user's CID as a key in the document: `{ "29312236": { ...historyData... } }`
60
-
61
- ## Related Collections
62
-
63
- - **Portfolio**: `signed_in_users/19M/snapshots/{date}/parts/part_{shardIndex}`
64
- - **History**: `signed_in_user_history/19M/snapshots/{date}/parts/part_{shardIndex}` (FIXED)
65
- - **Social**: `signed_in_users_social/{cid}/posts/{postId}`
66
-