bulltrackers-module 1.0.501 → 1.0.503
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.
- package/functions/computation-system/workflows/data_feeder_pipeline.yaml +65 -15
- package/functions/computation-system/workflows/datafeederpipelineinstructions.md +6 -6
- package/functions/generic-api/API_MIGRATION_PLAN.md +436 -0
- package/functions/generic-api/user-api/helpers/collection_helpers.js +215 -0
- package/functions/generic-api/user-api/helpers/data_helpers.js +237 -58
- package/functions/generic-api/user-api/helpers/on_demand_fetch_helpers.js +109 -28
- package/functions/generic-api/user-api/helpers/review_helpers.js +101 -17
- package/functions/generic-api/user-api/helpers/subscription_helpers.js +219 -35
- package/functions/generic-api/user-api/helpers/user_sync_helpers.js +121 -46
- package/functions/generic-api/user-api/helpers/verification_helpers.js +42 -3
- package/functions/generic-api/user-api/helpers/watchlist_helpers.js +212 -62
- package/package.json +1 -1
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
# Data Feeder Pipeline (V3.
|
|
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
|
-
#
|
|
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 }
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|