bulltrackers-module 1.0.500 → 1.0.502

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.
@@ -1,7 +1,5 @@
1
- # Data Feeder Pipeline (V3.2 - Automatic Root Data Indexing)
1
+ # Data Feeder Pipeline (V3.3 - Optimized Waits & Isolated Testing)
2
2
  # Starts at 22:00 UTC via Cloud Scheduler.
3
- # UPDATED: Removed intermediate root data indexer calls - each fetcher now automatically triggers indexing.
4
- # Only the global verification run at midnight remains.
5
3
 
6
4
  main:
7
5
  params: [input]
@@ -11,13 +9,74 @@ main:
11
9
  - project: '${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}'
12
10
  - location: "europe-west1"
13
11
 
14
- # --- TEST MODE ---
12
+ # ==========================================
13
+ # TEST MODE ROUTING
14
+ # ==========================================
15
15
  - check_test_mode:
16
16
  switch:
17
17
  - condition: '${input != null and "target_step" in input}'
18
18
  steps:
19
19
  - route_test:
20
20
  switch:
21
+ # --- ISOLATED FUNCTION TESTS (Run & Stop) ---
22
+ # Use these to test a single function without triggering waits or next steps.
23
+
24
+ - condition: '${input.target_step == "test_price"}'
25
+ steps:
26
+ - call_price_iso:
27
+ call: http.post
28
+ args:
29
+ url: '${"https://" + location + "-" + project + ".cloudfunctions.net/price-fetcher"}'
30
+ auth: { type: OIDC }
31
+ timeout: 300
32
+ - return_price:
33
+ return: "Test Complete: Price Fetcher executed."
34
+
35
+ - condition: '${input.target_step == "test_insights"}'
36
+ steps:
37
+ - call_insights_iso:
38
+ call: http.post
39
+ args:
40
+ url: '${"https://" + location + "-" + project + ".cloudfunctions.net/insights-fetcher"}'
41
+ auth: { type: OIDC }
42
+ timeout: 300
43
+ - return_insights:
44
+ return: "Test Complete: Insights Fetcher executed."
45
+
46
+ - condition: '${input.target_step == "test_rankings"}'
47
+ steps:
48
+ - call_rankings_iso:
49
+ call: http.post
50
+ args:
51
+ url: '${"https://" + location + "-" + project + ".cloudfunctions.net/fetch-popular-investors"}'
52
+ auth: { type: OIDC }
53
+ timeout: 300
54
+ - return_rankings:
55
+ return: "Test Complete: Popular Investors executed."
56
+
57
+ - condition: '${input.target_step == "test_social"}'
58
+ steps:
59
+ - call_social_iso:
60
+ call: http.post
61
+ args:
62
+ url: '${"https://" + location + "-" + project + ".cloudfunctions.net/social-orchestrator"}'
63
+ auth: { type: OIDC }
64
+ timeout: 300
65
+ - return_social:
66
+ return: "Test Complete: Social Orchestrator executed."
67
+
68
+ - condition: '${input.target_step == "test_indexer"}'
69
+ steps:
70
+ - call_indexer_iso:
71
+ call: http.post
72
+ args:
73
+ url: '${"https://" + location + "-" + project + ".cloudfunctions.net/root-data-indexer"}'
74
+ auth: { type: OIDC }
75
+ timeout: 300
76
+ - return_indexer:
77
+ return: "Test Complete: Root Data Indexer executed."
78
+
79
+ # --- PHASE JUMPS (Resumes flow from that point) ---
21
80
  - condition: '${input.target_step == "market"}'
22
81
  next: phase_2200_price
23
82
  - condition: '${input.target_step == "midnight"}'
@@ -43,14 +102,10 @@ main:
43
102
  call: sys.log
44
103
  args: { severity: "WARNING", text: "Price fetch timed out/failed. Proceeding." }
45
104
 
105
+ # FIXED: Only one 10-minute wait here now.
46
106
  - wait_10_after_price:
47
107
  call: sys.sleep
48
- args: { seconds: 600 } # 10 Minutes
49
- # NOTE: Price fetcher now automatically triggers root data indexer after completion
50
-
51
- - wait_10_before_insights:
52
- call: sys.sleep
53
- args: { seconds: 600 }
108
+ args: { seconds: 600 }
54
109
 
55
110
  - phase_2200_insights:
56
111
  try:
@@ -69,7 +124,6 @@ main:
69
124
  - wait_10_after_insights:
70
125
  call: sys.sleep
71
126
  args: { seconds: 600 }
72
- # NOTE: Insights fetcher now automatically triggers root data indexer after completion
73
127
 
74
128
  # ==========================================
75
129
  # PHASE 2: WAIT FOR MIDNIGHT
@@ -105,7 +159,6 @@ main:
105
159
  - wait_10_after_rankings:
106
160
  call: sys.sleep
107
161
  args: { seconds: 600 }
108
- # NOTE: Popular investor rankings fetcher now automatically triggers root data indexer after completion
109
162
 
110
163
  - phase_0000_social:
111
164
  try:
@@ -129,7 +182,6 @@ main:
129
182
  call: http.post
130
183
  args:
131
184
  url: '${"https://" + location + "-" + project + ".cloudfunctions.net/root-data-indexer"}'
132
- # No targetDate = Global verification run (all intermediate indexing is now automatic)
133
185
  auth: { type: OIDC }
134
186
 
135
187
  # ==========================================
@@ -165,9 +217,7 @@ main:
165
217
  - wait_10_in_loop:
166
218
  call: sys.sleep
167
219
  args: { seconds: 600 }
168
- # NOTE: Social tasks are handled by task engine, which automatically triggers root data indexer after batch completion
169
220
 
170
- # FIX 5: Correct assign syntax (must be a list)
171
221
  - increment_loop:
172
222
  assign:
173
223
  - i: '${i + 1}'
@@ -11,12 +11,12 @@ Below is a quick-reference guide for manually triggering the **Data Feeder Pipel
11
11
 
12
12
  ### Test Commands
13
13
 
14
- | Phase to Test | JSON Input | Action & Description |
15
- | :--- | :--- | :--- |
16
- | **Market Data (Phase 1)** | `{"target_step": "market"}` | **Runs:** Price Fetcher & Insights Fetcher.<br>**Note:** Automatically triggers indexing after each fetch. Workflow will pause at "Wait for Midnight" upon completion. |
17
- | **Midnight Phase (Phase 3)** | `{"target_step": "midnight"}` | **Runs:** Popular Investor Rankings, Midnight Social Orchestrator, and Global Index Verification.<br>**Note:** Use this to test the critical 00:00 UTC logic without waiting for the daily schedule. |
18
- | **Social Loop (Phase 4)** | `{"target_step": "social"}` | **Runs:** Enters the recurring 3-hour social fetch loop.<br>**Warning:** Triggers `social_loop_start`, which begins with a 3-hour sleep (`wait_3_hours`) before the first execution. |
19
- | **Standard Run** | `{}` | **Runs:** The full 24-hour cycle starting from 22:00 UTC (Market Close). |
14
+ Cloud Function,Input JSON to Use
15
+ Price Fetcher,"{"target_step": "test_price"}"
16
+ Insights Fetcher,"{"target_step": "test_insights"}"
17
+ Popular Investors,"{"target_step": "test_rankings"}"
18
+ Social Orchestrator,"{"target_step": "test_social"}"
19
+ Root Indexer,"{"target_step": "test_indexer"}"
20
20
 
21
21
  ---
22
22
 
@@ -0,0 +1,436 @@
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