abmp-npm 1.8.43 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/consts.js +24 -7
- package/backend/daily-pull/consts.js +0 -3
- package/backend/daily-pull/sync-to-cms-methods.js +10 -6
- package/backend/daily-pull/utils.js +3 -3
- package/backend/data-hooks.js +29 -0
- package/backend/index.js +3 -1
- package/backend/jobs.js +14 -3
- package/backend/members-data-methods.js +231 -83
- package/backend/pac-api-methods.js +3 -4
- package/backend/search-filters-methods.js +3 -0
- package/backend/sso-methods.js +161 -0
- package/backend/tasks/consts.js +19 -0
- package/backend/tasks/index.js +6 -0
- package/backend/tasks/migration-methods.js +26 -0
- package/backend/tasks/tasks-configs.js +124 -0
- package/backend/tasks/tasks-helpers-methods.js +419 -0
- package/backend/tasks/tasks-process-methods.js +545 -0
- package/backend/utils.js +47 -28
- package/package.json +13 -2
- package/pages/LoadingPage.js +20 -0
- package/pages/Profile.js +2 -2
- package/pages/Save Alerts.js +14 -0
- package/pages/index.js +1 -0
- package/pages/personalDetails.js +12 -8
- package/public/Utils/sharedUtils.js +0 -1
- package/public/consts.js +8 -23
- package/public/index.js +0 -1
- package/public/sso-auth-methods.js +43 -0
- package/backend/routers-methods.js +0 -186
- package/backend/routers-utils.js +0 -158
- package/backend/tasks.js +0 -37
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
const { taskManager } = require('psdev-task-manager');
|
|
2
|
+
|
|
3
|
+
const { COLLECTIONS } = require('../../public/consts');
|
|
4
|
+
const { COMPILED_FILTERS_FIELDS, CONFIG_KEYS } = require('../consts');
|
|
5
|
+
const { wixData } = require('../elevated-modules');
|
|
6
|
+
const { updateWixMemberLoginEmail } = require('../members-area-methods');
|
|
7
|
+
const {
|
|
8
|
+
getAllEmptyAboutYouMembers,
|
|
9
|
+
getAllMembersWithExternalImages,
|
|
10
|
+
getMembersWithWixUrl,
|
|
11
|
+
getAllMembersWithoutContactFormEmail,
|
|
12
|
+
findMemberByWixDataId,
|
|
13
|
+
bulkSaveMembers,
|
|
14
|
+
getAllUpdatedLoginEmails,
|
|
15
|
+
getMembersByIds,
|
|
16
|
+
} = require('../members-data-methods');
|
|
17
|
+
const {
|
|
18
|
+
getCompleteStateList,
|
|
19
|
+
getAreasOfPracticeList,
|
|
20
|
+
getStateCityMap,
|
|
21
|
+
getCompiledFiltersOptions,
|
|
22
|
+
} = require('../search-filters-methods');
|
|
23
|
+
const { chunkArray, getSiteConfigs } = require('../utils');
|
|
24
|
+
|
|
25
|
+
const { TASKS_NAMES } = require('./consts');
|
|
26
|
+
const {
|
|
27
|
+
updateMemberRichContent,
|
|
28
|
+
updateMemberProfileImage,
|
|
29
|
+
getAWSTokens,
|
|
30
|
+
uploadMembersSitemap,
|
|
31
|
+
} = require('./tasks-helpers-methods');
|
|
32
|
+
|
|
33
|
+
const scheduleTaskForEmptyAboutYouMembers = async () => {
|
|
34
|
+
const createTasksFromMembers = members => {
|
|
35
|
+
const memberIds = members.map(member => member._id);
|
|
36
|
+
return {
|
|
37
|
+
name: TASKS_NAMES.convertHtmlToRichContent,
|
|
38
|
+
data: { memberIds },
|
|
39
|
+
type: 'scheduled',
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
const members = await getAllEmptyAboutYouMembers();
|
|
43
|
+
console.log('starting to schedule tasks for empty about you members');
|
|
44
|
+
const membersChunks = chunkArray(members, 1000);
|
|
45
|
+
for (const chunk of membersChunks) {
|
|
46
|
+
const toScheduleTask = createTasksFromMembers(chunk);
|
|
47
|
+
await taskManager().schedule(toScheduleTask);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
//this funciton takes ~0.5 seconds per member
|
|
52
|
+
const convertAboutYouHtmlToRichContent = async membersIds => {
|
|
53
|
+
const result = {};
|
|
54
|
+
|
|
55
|
+
// Process members in chunks of 30
|
|
56
|
+
const chunks = chunkArray(membersIds, 30);
|
|
57
|
+
|
|
58
|
+
for (const chunk of chunks) {
|
|
59
|
+
console.log(`Processing chunk of ${chunk.length} members`);
|
|
60
|
+
|
|
61
|
+
// Process each chunk concurrently using Promise.all
|
|
62
|
+
const chunkPromises = chunk.map(async memberId => {
|
|
63
|
+
console.log('memberId ======', memberId);
|
|
64
|
+
try {
|
|
65
|
+
await updateMemberRichContent(memberId);
|
|
66
|
+
return { memberId, success: true };
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('error in updating member', error);
|
|
69
|
+
return { memberId, success: false };
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Wait for all promises in the chunk to complete
|
|
74
|
+
const chunkResults = await Promise.all(chunkPromises);
|
|
75
|
+
|
|
76
|
+
// Update result object with chunk results
|
|
77
|
+
chunkResults.forEach(({ memberId, success }) => {
|
|
78
|
+
result[memberId] = success;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
async function compileFiltersOptions(field) {
|
|
86
|
+
const getNonCompiledFilter = field => {
|
|
87
|
+
const filterMap = {
|
|
88
|
+
[COMPILED_FILTERS_FIELDS.COMPILED_STATE_LIST]: getCompleteStateList,
|
|
89
|
+
[COMPILED_FILTERS_FIELDS.COMPILED_AREAS_OF_PRACTICES]: getAreasOfPracticeList,
|
|
90
|
+
[COMPILED_FILTERS_FIELDS.COMPILED_STATE_CITY_MAP]: getStateCityMap,
|
|
91
|
+
};
|
|
92
|
+
const filterFunction = filterMap[field];
|
|
93
|
+
if (!filterFunction) {
|
|
94
|
+
throw new Error(`Unknown filter field: ${field}`);
|
|
95
|
+
}
|
|
96
|
+
return filterFunction();
|
|
97
|
+
};
|
|
98
|
+
const [nonCompiledFilterData, compiledFiltersOptions] = await Promise.all([
|
|
99
|
+
getNonCompiledFilter(field),
|
|
100
|
+
getCompiledFiltersOptions(),
|
|
101
|
+
]);
|
|
102
|
+
compiledFiltersOptions[field] = nonCompiledFilterData;
|
|
103
|
+
await wixData.save(COLLECTIONS.COMPILED_STATE_CITY_MAP, compiledFiltersOptions);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const scheduleTaskForExternalProfileImages = async () => {
|
|
107
|
+
const createImageMigrationTasksFromMembers = members => {
|
|
108
|
+
const memberIds = members.map(member => member._id);
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
name: TASKS_NAMES.convertExternalProfilesToWixImages,
|
|
112
|
+
data: { memberIds },
|
|
113
|
+
type: 'scheduled',
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
const members = await getAllMembersWithExternalImages();
|
|
117
|
+
console.log('Starting to schedule tasks for external profile image migration');
|
|
118
|
+
const membersChunks = chunkArray(members, 300);
|
|
119
|
+
for (const chunk of membersChunks) {
|
|
120
|
+
const toScheduleTask = createImageMigrationTasksFromMembers(chunk);
|
|
121
|
+
await taskManager().schedule(toScheduleTask);
|
|
122
|
+
}
|
|
123
|
+
console.log(`Scheduled ${members.length} members for profile image migration`);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const convertExternalProfilesToWixImages = async membersIds => {
|
|
127
|
+
const result = {};
|
|
128
|
+
|
|
129
|
+
// Process members in chunks of 30 (optimal concurrent processing)
|
|
130
|
+
const chunks = chunkArray(membersIds, 30);
|
|
131
|
+
|
|
132
|
+
for (const chunk of chunks) {
|
|
133
|
+
console.log(`Processing profile image chunk of ${chunk.length} members`);
|
|
134
|
+
|
|
135
|
+
// Process each chunk concurrently using Promise.all
|
|
136
|
+
const chunkPromises = chunk.map(async memberId => {
|
|
137
|
+
console.log('Processing profile image for member:', memberId);
|
|
138
|
+
try {
|
|
139
|
+
const updateResult = await updateMemberProfileImage(memberId);
|
|
140
|
+
return {
|
|
141
|
+
memberId,
|
|
142
|
+
success: updateResult.success,
|
|
143
|
+
message: updateResult.message,
|
|
144
|
+
};
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error('Error updating member profile image:', error);
|
|
147
|
+
return { memberId, success: false, error: error.message };
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const chunkResults = await Promise.all(chunkPromises);
|
|
152
|
+
|
|
153
|
+
// Log results for this chunk
|
|
154
|
+
chunkResults.forEach(result => {
|
|
155
|
+
if (result.success) {
|
|
156
|
+
console.log(`✅ Successfully processed profile image for member ${result.memberId}`);
|
|
157
|
+
} else {
|
|
158
|
+
console.error(
|
|
159
|
+
`❌ Failed to process profile image for member ${result.memberId}: ${result.error}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Add small delay between chunks to avoid overwhelming the media manager
|
|
165
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
};
|
|
170
|
+
const updateSiteMapS3 = async () => {
|
|
171
|
+
const relevantMembers = await getMembersWithWixUrl();
|
|
172
|
+
console.log('number of profiles to upload', relevantMembers.length);
|
|
173
|
+
const [tokens, siteAssociation] = await Promise.all([
|
|
174
|
+
getAWSTokens(),
|
|
175
|
+
getSiteConfigs(CONFIG_KEYS.SITE_ASSOCIATION),
|
|
176
|
+
]);
|
|
177
|
+
// const creds = await getNewStsSessionToken(tokens.AWS_ACCESS_KEY_ID, tokens.AWS_SECRET_ACCESS_KEY, 3600);
|
|
178
|
+
// console.log("creds",creds); // verify it’s fresh
|
|
179
|
+
try {
|
|
180
|
+
const chunkSize = 50000;
|
|
181
|
+
console.log('Total items will be split into', relevantMembers.length / chunkSize);
|
|
182
|
+
const chunks = chunkArray(relevantMembers, chunkSize);
|
|
183
|
+
console.log(`Uploading ${chunks.length} sitemap files...`);
|
|
184
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
185
|
+
const index = i + 1;
|
|
186
|
+
const fileName = `profiles-sitemap-${index}.xml`;
|
|
187
|
+
console.log(
|
|
188
|
+
`Uploading chunk ${index}/${chunks.length} -> ${fileName} (${chunks[i].length} items)`
|
|
189
|
+
);
|
|
190
|
+
await uploadMembersSitemap({
|
|
191
|
+
members: chunks[i],
|
|
192
|
+
tokens,
|
|
193
|
+
destinationFileName: fileName,
|
|
194
|
+
siteAssociation,
|
|
195
|
+
});
|
|
196
|
+
console.log(`Uploaded ${fileName}`);
|
|
197
|
+
}
|
|
198
|
+
console.log('All sitemap files uploaded successfully');
|
|
199
|
+
} catch (e) {
|
|
200
|
+
console.error('Sitemap upload failed:', e?.message || e);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Schedules tasks to migrate contactFormEmail for all members who don't have it set
|
|
206
|
+
* This function gets all members missing contactFormEmail and schedules batch processing tasks
|
|
207
|
+
*/
|
|
208
|
+
const scheduleContactFormEmailMigration = async () => {
|
|
209
|
+
try {
|
|
210
|
+
console.log('Starting to schedule contactFormEmail migration tasks');
|
|
211
|
+
const createContactFormEmailMigrationTask = (members, chunkIndex) => {
|
|
212
|
+
const memberIds = members.map(member => member._id);
|
|
213
|
+
return {
|
|
214
|
+
name: TASKS_NAMES.migrateContactFormEmails,
|
|
215
|
+
data: { memberIds, chunkIndex },
|
|
216
|
+
type: 'scheduled',
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
const membersToMigrate = await getAllMembersWithoutContactFormEmail();
|
|
220
|
+
console.log(`Found ${membersToMigrate.length} members needing contactFormEmail migration`);
|
|
221
|
+
if (membersToMigrate.length === 0) {
|
|
222
|
+
console.log('No members need contactFormEmail migration');
|
|
223
|
+
return {
|
|
224
|
+
success: true,
|
|
225
|
+
message: 'No members need migration',
|
|
226
|
+
totalMembers: 0,
|
|
227
|
+
tasksScheduled: 0,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Process in chunks of 500 members per task (smaller chunks for reliability)
|
|
232
|
+
const membersChunks = chunkArray(membersToMigrate, 500);
|
|
233
|
+
console.log(`Creating ${membersChunks.length} migration tasks`);
|
|
234
|
+
|
|
235
|
+
for (let chunkIndex = 0; chunkIndex < membersChunks.length; chunkIndex++) {
|
|
236
|
+
const chunk = membersChunks[chunkIndex];
|
|
237
|
+
const migrationTask = createContactFormEmailMigrationTask(chunk, chunkIndex);
|
|
238
|
+
await taskManager().schedule(migrationTask);
|
|
239
|
+
console.log(`Scheduled task for chunk ${chunkIndex} with ${chunk.length} members`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
console.log(
|
|
243
|
+
`Successfully scheduled ${membersChunks.length} tasks for ${membersToMigrate.length} members`
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
success: true,
|
|
248
|
+
message: `Scheduled ${membersChunks.length} tasks for ${membersToMigrate.length} members`,
|
|
249
|
+
totalMembers: membersToMigrate.length,
|
|
250
|
+
tasksScheduled: membersChunks.length,
|
|
251
|
+
};
|
|
252
|
+
} catch (error) {
|
|
253
|
+
const errorMessage = `Failed to schedule contactFormEmail migration: ${error.message}`;
|
|
254
|
+
console.error(errorMessage);
|
|
255
|
+
throw new Error(errorMessage);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Migrates contactFormEmail for a batch of members
|
|
261
|
+
* Sets contactFormEmail to the same value as the member's current email
|
|
262
|
+
* @param {Object} data - Data object with memberIds and chunkIndex
|
|
263
|
+
* @param {Array} data.memberIds - Array of member IDs to process
|
|
264
|
+
* @param {number} data.chunkIndex - Index of the chunk
|
|
265
|
+
* @returns {Promise<Object>} - Result object with success/failure counts
|
|
266
|
+
*/
|
|
267
|
+
const migrateContactFormEmails = async data => {
|
|
268
|
+
const { memberIds, chunkIndex } = data;
|
|
269
|
+
const result = {
|
|
270
|
+
successful: 0,
|
|
271
|
+
failed: 0,
|
|
272
|
+
errors: [],
|
|
273
|
+
skipped: 0,
|
|
274
|
+
skippedIds: [],
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
console.log(
|
|
278
|
+
`Starting contactFormEmail migration for ${memberIds.length} members in chunk ${chunkIndex}`
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
// Get all members for this batch
|
|
283
|
+
const memberPromises = memberIds.map(async memberId => {
|
|
284
|
+
try {
|
|
285
|
+
const member = await findMemberByWixDataId(memberId);
|
|
286
|
+
|
|
287
|
+
// Skip if member already has contactFormEmail set
|
|
288
|
+
if (member.contactFormEmail) {
|
|
289
|
+
console.log(`Member ${memberId} already has contactFormEmail set`);
|
|
290
|
+
result.skipped++;
|
|
291
|
+
result.skippedIds.push(memberId);
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Skip if member doesn't have email
|
|
296
|
+
if (!member.email) {
|
|
297
|
+
console.log(`Member ${memberId} doesn't have email - skipping`);
|
|
298
|
+
result.skipped++;
|
|
299
|
+
result.skippedIds.push(memberId);
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
...member,
|
|
305
|
+
contactFormEmail: member.email,
|
|
306
|
+
};
|
|
307
|
+
} catch (error) {
|
|
308
|
+
console.error(`Error preparing member ${memberId}:`, error);
|
|
309
|
+
result.failed++;
|
|
310
|
+
result.errors.push({ memberId, error: error.message });
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const membersToUpdate = (await Promise.all(memberPromises)).filter(Boolean);
|
|
316
|
+
|
|
317
|
+
if (membersToUpdate.length === 0) {
|
|
318
|
+
console.log('No members need updating in this batch');
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
console.log(
|
|
323
|
+
`Started Updating ${membersToUpdate.length} members with contactFormEmail in chunk ${chunkIndex}`
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
// Process in smaller chunks for bulk update (1000 is Wix limit)
|
|
327
|
+
const updateChunks = chunkArray(membersToUpdate, 1000);
|
|
328
|
+
|
|
329
|
+
for (let chunkIndex = 0; chunkIndex < updateChunks.length; chunkIndex++) {
|
|
330
|
+
const chunk = updateChunks[chunkIndex];
|
|
331
|
+
try {
|
|
332
|
+
await bulkSaveMembers(chunk);
|
|
333
|
+
result.successful += chunk.length;
|
|
334
|
+
console.log(`✅ Successfully updated ${chunk.length} members in chunk ${chunkIndex}`);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
console.error(`❌ Error updating chunk ${chunkIndex}:`, error);
|
|
337
|
+
result.failed += chunk.length;
|
|
338
|
+
result.errors.push({
|
|
339
|
+
chunk: chunkIndex,
|
|
340
|
+
error: error.message,
|
|
341
|
+
memberCount: chunk.length,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
} catch (error) {
|
|
346
|
+
const errorMessage = `Failed to migrate contactFormEmail for chunk ${chunkIndex}: ${error.message}`;
|
|
347
|
+
console.error(errorMessage);
|
|
348
|
+
throw new Error(errorMessage);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
console.log(
|
|
352
|
+
`ContactFormEmail migration task completed: ${result.successful} successful, ${result.failed} failed, ${result.skipped} skipped, in chunk ${chunkIndex}`
|
|
353
|
+
);
|
|
354
|
+
return result;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
Schedules tasks to sync updated emails from the updated login emails database
|
|
359
|
+
* This function gets all updated emails and schedules batch processing tasks
|
|
360
|
+
*/
|
|
361
|
+
const scheduleEmailSync = async () => {
|
|
362
|
+
try {
|
|
363
|
+
console.log('Starting to schedule email sync tasks');
|
|
364
|
+
const createEmailSyncTask = (chunk, chunkIndex) => {
|
|
365
|
+
//To reduce stored Items size inside task data
|
|
366
|
+
const emailUpdates = chunk.map(emailUpdate => ({
|
|
367
|
+
memberId: emailUpdate.memberId,
|
|
368
|
+
loginEmail: emailUpdate.loginEmail,
|
|
369
|
+
}));
|
|
370
|
+
return {
|
|
371
|
+
name: TASKS_NAMES.syncMemberLoginEmails,
|
|
372
|
+
data: { emailUpdates, chunkIndex },
|
|
373
|
+
type: 'scheduled',
|
|
374
|
+
};
|
|
375
|
+
};
|
|
376
|
+
const updatedEmails = await getAllUpdatedLoginEmails();
|
|
377
|
+
console.log(`Found ${updatedEmails.length} updated email records`);
|
|
378
|
+
|
|
379
|
+
if (updatedEmails.length === 0) {
|
|
380
|
+
console.log('No updated emails found');
|
|
381
|
+
return {
|
|
382
|
+
success: true,
|
|
383
|
+
message: 'No updated emails to sync',
|
|
384
|
+
totalEmails: 0,
|
|
385
|
+
tasksScheduled: 0,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const emailChunks = chunkArray(updatedEmails, 500);
|
|
390
|
+
console.log(`Creating ${emailChunks.length} email sync tasks`);
|
|
391
|
+
|
|
392
|
+
for (let chunkIndex = 0; chunkIndex < emailChunks.length; chunkIndex++) {
|
|
393
|
+
const chunk = emailChunks[chunkIndex];
|
|
394
|
+
const syncTask = createEmailSyncTask(chunk, chunkIndex);
|
|
395
|
+
await taskManager().schedule(syncTask);
|
|
396
|
+
console.log(`Scheduled task for chunk ${chunkIndex} with ${chunk.length} email updates`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
console.log(
|
|
400
|
+
`Successfully scheduled ${emailChunks.length} tasks for ${updatedEmails.length} email updates`
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
success: true,
|
|
405
|
+
message: `Scheduled ${emailChunks.length} tasks for ${updatedEmails.length} email updates`,
|
|
406
|
+
totalEmails: updatedEmails.length,
|
|
407
|
+
tasksScheduled: emailChunks.length,
|
|
408
|
+
};
|
|
409
|
+
} catch (error) {
|
|
410
|
+
const errorMessage = `Failed to schedule email sync: ${error.message}`;
|
|
411
|
+
console.error(errorMessage);
|
|
412
|
+
throw new Error(errorMessage);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Syncs member emails with updated login emails
|
|
418
|
+
* @param {Object} data - Data object with emailUpdates and chunkIndex
|
|
419
|
+
* @param {Array} data.emailUpdates - Array of email update objects with memberId and loginEmail
|
|
420
|
+
* @param {number} data.chunkIndex - Index of the chunk
|
|
421
|
+
* @returns {Object} - Result object with success/failure counts
|
|
422
|
+
*/
|
|
423
|
+
const syncMemberLoginEmails = async data => {
|
|
424
|
+
const { emailUpdates, chunkIndex } = data;
|
|
425
|
+
const result = {
|
|
426
|
+
successful: 0,
|
|
427
|
+
failed: 0,
|
|
428
|
+
skipped: 0,
|
|
429
|
+
skippedIds: [],
|
|
430
|
+
missingMemberIds: [],
|
|
431
|
+
errors: [],
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
console.log(
|
|
435
|
+
`Starting email sync for ${emailUpdates.length} email updates in chunk ${chunkIndex}`
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
const memberIds = emailUpdates.map(update => update.memberId);
|
|
440
|
+
|
|
441
|
+
const existingMembers = await getMembersByIds(memberIds);
|
|
442
|
+
console.log(`Found ${existingMembers.length} existing members to update`);
|
|
443
|
+
const existingMemberIds = new Set(existingMembers.map(member => member.memberId));
|
|
444
|
+
const missingMemberIds = memberIds.filter(memberId => !existingMemberIds.has(memberId));
|
|
445
|
+
|
|
446
|
+
// Add missing member IDs to skipped count and log them
|
|
447
|
+
if (missingMemberIds.length > 0) {
|
|
448
|
+
console.log(
|
|
449
|
+
`Found ${missingMemberIds.length} members in emailUpdates but not in database:`,
|
|
450
|
+
missingMemberIds
|
|
451
|
+
);
|
|
452
|
+
result.missingMemberIds = result.missingMemberIds || [];
|
|
453
|
+
result.missingMemberIds.push(...missingMemberIds);
|
|
454
|
+
}
|
|
455
|
+
const emailUpdateMap = new Map();
|
|
456
|
+
emailUpdates.forEach(update => {
|
|
457
|
+
emailUpdateMap.set(update.memberId, update.loginEmail);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
const membersToUpdate = [];
|
|
461
|
+
|
|
462
|
+
for (const member of existingMembers) {
|
|
463
|
+
const newEmail = emailUpdateMap.get(member.memberId);
|
|
464
|
+
|
|
465
|
+
if (!newEmail) {
|
|
466
|
+
console.log(`No email update found for member ${member.memberId}`);
|
|
467
|
+
result.skipped++;
|
|
468
|
+
result.skippedIds.push(member.memberId);
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (member.email === newEmail) {
|
|
473
|
+
console.log(`Email already up to date for member ${member.memberId}`);
|
|
474
|
+
result.skipped++;
|
|
475
|
+
result.skippedIds.push(member.memberId);
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
membersToUpdate.push({
|
|
480
|
+
...member,
|
|
481
|
+
email: newEmail,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (membersToUpdate.length === 0) {
|
|
486
|
+
console.log('No members need email updates in this batch', chunkIndex);
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
console.log(
|
|
491
|
+
`Updating ${membersToUpdate.length} members with new emails in chunk ${chunkIndex}`
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
const updateChunks = chunkArray(membersToUpdate, 1000);
|
|
495
|
+
|
|
496
|
+
for (const chunk of updateChunks) {
|
|
497
|
+
try {
|
|
498
|
+
await bulkSaveMembers(chunk);
|
|
499
|
+
|
|
500
|
+
for (const member of chunk) {
|
|
501
|
+
await updateWixMemberLoginEmail(member, result);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
result.successful += chunk.length;
|
|
505
|
+
console.log(`✅ Successfully updated ${chunkIndex} ${chunk.length} members`);
|
|
506
|
+
} catch (error) {
|
|
507
|
+
console.error(`❌ Error updating chunk ${chunkIndex}:`, error);
|
|
508
|
+
result.failed += chunk.length;
|
|
509
|
+
result.errors.push({
|
|
510
|
+
chunk: chunkIndex,
|
|
511
|
+
error: error.message,
|
|
512
|
+
memberCount: chunk.length,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
// Log comprehensive results including Wix member updates
|
|
517
|
+
const wixStats = result.wixMemberUpdates || { successful: 0, failed: 0 };
|
|
518
|
+
console.log(`Login Emails sync task completed:`);
|
|
519
|
+
console.log(
|
|
520
|
+
` - Member data updates: ${result.successful} successful, ${result.failed} failed, ${result.skipped} skipped`
|
|
521
|
+
);
|
|
522
|
+
console.log(
|
|
523
|
+
` - Wix member login emails: ${wixStats.successful} successful, ${wixStats.failed} failed`
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
return result;
|
|
527
|
+
} catch (error) {
|
|
528
|
+
const errorMessage = `Failed to syncMemberLoginEmails for chunk ${chunkIndex} of length ${emailUpdates.length} with error: ${error.message}`;
|
|
529
|
+
console.error(errorMessage);
|
|
530
|
+
throw new Error(errorMessage);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
module.exports = {
|
|
535
|
+
scheduleTaskForEmptyAboutYouMembers,
|
|
536
|
+
convertAboutYouHtmlToRichContent,
|
|
537
|
+
compileFiltersOptions,
|
|
538
|
+
scheduleTaskForExternalProfileImages,
|
|
539
|
+
convertExternalProfilesToWixImages,
|
|
540
|
+
updateSiteMapS3,
|
|
541
|
+
scheduleContactFormEmailMigration,
|
|
542
|
+
migrateContactFormEmails,
|
|
543
|
+
scheduleEmailSync,
|
|
544
|
+
syncMemberLoginEmails,
|
|
545
|
+
};
|
package/backend/utils.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
const { auth } = require('@wix/essentials');
|
|
2
|
+
const { secrets } = require('@wix/secrets');
|
|
3
|
+
const { site } = require('@wix/urls');
|
|
1
4
|
const { encode } = require('ngeohash');
|
|
2
5
|
|
|
3
6
|
const { COLLECTIONS } = require('../public/consts');
|
|
4
7
|
|
|
5
8
|
const { CONFIG_KEYS, GEO_HASH_PRECISION } = require('./consts');
|
|
6
9
|
const { wixData } = require('./elevated-modules');
|
|
7
|
-
const
|
|
10
|
+
const elevatedGetSecretValue = auth.elevate(secrets.getSecretValue);
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Retrieves site configuration values from the database
|
|
@@ -95,17 +98,17 @@ const queryAllItems = async query => {
|
|
|
95
98
|
return allItems;
|
|
96
99
|
};
|
|
97
100
|
/**
|
|
98
|
-
*
|
|
99
|
-
* @param {Array} array - Array to
|
|
100
|
-
* @param {number}
|
|
101
|
-
* @returns {Array} - Array of
|
|
101
|
+
* Chunks large arrays into smaller chunks for processing
|
|
102
|
+
* @param {Array} array - Array to chunk
|
|
103
|
+
* @param {number} chunkSize - Size of each chunk
|
|
104
|
+
* @returns {Array} - Array of chunks
|
|
102
105
|
*/
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
for (let i = 0; i < array.length; i +=
|
|
106
|
-
|
|
106
|
+
const chunkArray = (array, chunkSize = 50) => {
|
|
107
|
+
const chunks = [];
|
|
108
|
+
for (let i = 0; i < array.length; i += chunkSize) {
|
|
109
|
+
chunks.push(array.slice(i, i + chunkSize));
|
|
107
110
|
}
|
|
108
|
-
return
|
|
111
|
+
return chunks;
|
|
109
112
|
};
|
|
110
113
|
|
|
111
114
|
const generateGeoHash = addresses => {
|
|
@@ -128,38 +131,54 @@ const normalizeUrlForComparison = url => {
|
|
|
128
131
|
return url.toLowerCase().replace(/-\d+$/, '');
|
|
129
132
|
};
|
|
130
133
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
* @param {string} memberId - The member ID to exclude from the check
|
|
135
|
-
* @returns {Promise<Object>} Result object with isUnique boolean
|
|
136
|
-
*/
|
|
137
|
-
async function checkUrlUniqueness(url, memberId) {
|
|
138
|
-
if (!url || !memberId) {
|
|
139
|
-
throw new Error('Missing required parameters: url and memberId are required');
|
|
140
|
-
}
|
|
134
|
+
async function getSecret(secretKey) {
|
|
135
|
+
return await elevatedGetSecretValue(secretKey).value;
|
|
136
|
+
}
|
|
141
137
|
|
|
138
|
+
async function getSiteBaseUrl() {
|
|
142
139
|
try {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
140
|
+
const result = await site.listPublishedSiteUrls({
|
|
141
|
+
filters: { primary: true },
|
|
142
|
+
});
|
|
143
|
+
const baseUrl = result.urls[0].url;
|
|
144
|
+
if (!baseUrl) {
|
|
145
|
+
throw new Error('No Base URL Found');
|
|
146
|
+
}
|
|
147
|
+
return baseUrl;
|
|
147
148
|
} catch (error) {
|
|
148
|
-
|
|
149
|
-
throw new Error(`Failed to check URL uniqueness: ${error.message}`);
|
|
149
|
+
throw new Error(`Failed to get site base URL: ${error?.message || error}`);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
function encodeXml(value) {
|
|
154
|
+
if (!value) return '';
|
|
155
|
+
return (
|
|
156
|
+
String(value)
|
|
157
|
+
.replace(/&/g, '&')
|
|
158
|
+
.replace(/</g, '<')
|
|
159
|
+
.replace(/>/g, '>')
|
|
160
|
+
// eslint-disable-next-line no-useless-escape
|
|
161
|
+
.replace(/\"/g, '"')
|
|
162
|
+
.replace(/'/g, ''')
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function formatDateOnly(dateStr) {
|
|
167
|
+
return new Date(dateStr).toISOString().slice(0, 10);
|
|
168
|
+
}
|
|
153
169
|
module.exports = {
|
|
154
170
|
getSiteConfigs,
|
|
155
171
|
retrieveAllItems,
|
|
156
|
-
|
|
172
|
+
chunkArray,
|
|
157
173
|
generateGeoHash,
|
|
158
174
|
isValidArray,
|
|
159
175
|
normalizeUrlForComparison,
|
|
160
176
|
queryAllItems,
|
|
161
|
-
checkUrlUniqueness,
|
|
162
177
|
formatDateToMonthYear,
|
|
163
178
|
isStudent,
|
|
164
179
|
getAddressDisplayOptions,
|
|
180
|
+
getSecret,
|
|
181
|
+
getSiteBaseUrl,
|
|
182
|
+
encodeXml,
|
|
183
|
+
formatDateOnly,
|
|
165
184
|
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abmp-npm",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"scripts": {
|
|
6
|
+
"check-cycles": "madge --circular .",
|
|
6
7
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
7
|
-
"lint": "eslint .",
|
|
8
|
+
"lint": "npm run check-cycles && eslint .",
|
|
8
9
|
"lint:fix": "eslint . --fix",
|
|
9
10
|
"format": "prettier --write \"**/*.{js,json,md}\"",
|
|
10
11
|
"format:check": "prettier --check \"**/*.{js,json,md}\"",
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
"eslint-plugin-promise": "^7.1.0",
|
|
24
25
|
"globals": "^15.10.0",
|
|
25
26
|
"husky": "^9.1.6",
|
|
27
|
+
"madge": "^8.0.0",
|
|
26
28
|
"prettier": "^3.3.3"
|
|
27
29
|
},
|
|
28
30
|
"dependencies": {
|
|
@@ -30,10 +32,19 @@
|
|
|
30
32
|
"@wix/crm": "^1.0.1061",
|
|
31
33
|
"@wix/data": "^1.0.303",
|
|
32
34
|
"@wix/essentials": "^0.1.28",
|
|
35
|
+
"@wix/identity": "^1.0.178",
|
|
36
|
+
"@wix/media": "^1.0.213",
|
|
33
37
|
"@wix/members": "^1.0.365",
|
|
34
38
|
"@wix/secrets": "^1.0.62",
|
|
35
39
|
"@wix/site-location": "^1.31.0",
|
|
40
|
+
"@wix/site-members": "^1.32.0",
|
|
41
|
+
"@wix/site-storage": "^1.22.0",
|
|
36
42
|
"@wix/site-window": "^1.44.0",
|
|
43
|
+
"@wix/urls": "^1.0.57",
|
|
44
|
+
"aws4": "^1.13.2",
|
|
45
|
+
"axios": "^1.13.1",
|
|
46
|
+
"crypto": "^1.0.1",
|
|
47
|
+
"jwt-js-decode": "^1.9.0",
|
|
37
48
|
"lodash": "^4.17.21",
|
|
38
49
|
"ngeohash": "^0.6.3",
|
|
39
50
|
"phone": "^3.1.67",
|