abmp-npm 1.8.43 → 1.8.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CONTACT_EMAIL_UPDATE_DEBUG.md +313 -0
  2. package/DEBUG_QUICKSTART.md +133 -0
  3. package/backend/cms-data-methods.js +8 -0
  4. package/backend/consts.js +22 -7
  5. package/backend/contacts-methods-DEBUG.js +237 -0
  6. package/backend/contacts-methods-TEST.js +271 -0
  7. package/backend/contacts-methods.js +6 -1
  8. package/backend/daily-pull/consts.js +0 -3
  9. package/backend/daily-pull/process-member-methods.js +1 -1
  10. package/backend/daily-pull/sync-to-cms-methods.js +10 -6
  11. package/backend/daily-pull/utils.js +3 -3
  12. package/backend/data-hooks.js +29 -0
  13. package/backend/elevated-modules.js +2 -0
  14. package/backend/http-functions/httpFunctions.js +86 -0
  15. package/backend/http-functions/index.js +3 -0
  16. package/backend/http-functions/interests.js +37 -0
  17. package/backend/index.js +6 -1
  18. package/backend/jobs.js +15 -3
  19. package/backend/login/index.js +7 -0
  20. package/backend/login/login-methods-factory.js +24 -0
  21. package/backend/login/qa-login-methods.js +72 -0
  22. package/backend/login/sso-methods.js +158 -0
  23. package/backend/members-data-methods.js +271 -94
  24. package/backend/pac-api-methods.js +3 -4
  25. package/backend/routers/index.js +3 -0
  26. package/backend/routers/methods.js +177 -0
  27. package/backend/routers/utils.js +118 -0
  28. package/backend/search-filters-methods.js +3 -0
  29. package/backend/tasks/consts.js +19 -0
  30. package/backend/tasks/index.js +6 -0
  31. package/backend/tasks/migration-methods.js +26 -0
  32. package/backend/tasks/tasks-configs.js +124 -0
  33. package/backend/tasks/tasks-helpers-methods.js +419 -0
  34. package/backend/tasks/tasks-process-methods.js +545 -0
  35. package/backend/test-methods.js +118 -0
  36. package/backend/utils.js +85 -41
  37. package/package.json +13 -2
  38. package/pages/LoadingPage.js +20 -0
  39. package/pages/Profile.js +2 -2
  40. package/pages/QAPage.js +39 -0
  41. package/pages/SaveAlerts.js +13 -0
  42. package/pages/SelectBannerImages.js +46 -0
  43. package/pages/deleteConfirm.js +19 -0
  44. package/pages/index.js +5 -0
  45. package/pages/personalDetails.js +12 -8
  46. package/public/consts.js +6 -23
  47. package/public/sso-auth-methods.js +43 -0
  48. package/backend/routers-methods.js +0 -186
  49. package/backend/routers-utils.js +0 -158
  50. package/backend/tasks.js +0 -37
@@ -1,16 +1,14 @@
1
1
  const { COLLECTIONS } = require('../public/consts');
2
2
 
3
+ const { MEMBERSHIPS_TYPES } = require('./consts');
3
4
  const { updateMemberContactInfo } = require('./contacts-methods');
4
- const { MEMBER_ACTIONS } = require('./daily-pull');
5
+ const { MEMBER_ACTIONS } = require('./daily-pull/consts');
5
6
  const { wixData } = require('./elevated-modules');
6
- const { createSiteMember, getCurrentMember } = require('./members-area-methods');
7
+ const { createSiteMember } = require('./members-area-methods');
7
8
  const {
8
- createBatches,
9
+ chunkArray,
9
10
  normalizeUrlForComparison,
10
11
  queryAllItems,
11
- formatDateToMonthYear,
12
- getAddressDisplayOptions,
13
- isStudent,
14
12
  generateGeoHash,
15
13
  } = require('./utils');
16
14
 
@@ -48,7 +46,7 @@ async function createContactAndMemberIfNew(memberData) {
48
46
  ...memberData,
49
47
  contactId,
50
48
  };
51
- const updatedResult = await wixData.update(COLLECTIONS.MEMBERS_DATA, memberDataWithContactId);
49
+ const updatedResult = await updateMember(memberDataWithContactId);
52
50
  memberDataWithContactId = {
53
51
  ...memberDataWithContactId,
54
52
  ...updatedResult,
@@ -60,79 +58,6 @@ async function createContactAndMemberIfNew(memberData) {
60
58
  }
61
59
  }
62
60
 
63
- /**
64
- * Validates member token and retrieves member data
65
- * @param {string} memberIdInput - The member ID to validate
66
- * @returns {Promise<{memberData: Object|null, isValid: boolean}>} Validation result with member data
67
- */
68
- async function validateMemberToken(memberIdInput) {
69
- const invalidTokenResponse = { memberData: null, isValid: false };
70
-
71
- if (!memberIdInput) {
72
- return invalidTokenResponse;
73
- }
74
-
75
- try {
76
- const member = await getCurrentMember();
77
- if (!member || !member._id) {
78
- console.log(
79
- 'member not found from members.getCurrentMember() for memberIdInput',
80
- memberIdInput
81
- );
82
- return invalidTokenResponse;
83
- }
84
-
85
- // Query member data using elevated permissions (suppressAuth equivalent)
86
- const { items } = await wixData
87
- .query(COLLECTIONS.MEMBERS_DATA)
88
- .eq('contactId', member._id)
89
- .find();
90
-
91
- console.log('items', items[0]);
92
- console.log('member._id', member._id);
93
-
94
- if (!items[0]?._id) {
95
- const errorMessage = `No record found in DB for logged in Member [Corrupted Data - Duplicate Members? ] - There is no match in DB for currentMember: ${JSON.stringify(
96
- { memberIdInput, currentMemberId: member._id }
97
- )}`;
98
- console.error(errorMessage);
99
- throw new Error('CORRUPTED_MEMBER_DATA');
100
- }
101
-
102
- console.log(`Id found in DB for memberIdInput :${memberIdInput} is ${items[0]?._id}`);
103
-
104
- const memberData = items[0];
105
-
106
- // Format membership dates
107
- memberData.memberships = memberData.memberships.map(membership => ({
108
- ...membership,
109
- membersince: formatDateToMonthYear(membership.membersince),
110
- }));
111
-
112
- const savedMemberId = memberData?._id;
113
- const isValid = savedMemberId === memberIdInput;
114
-
115
- if (!savedMemberId || !isValid) {
116
- return invalidTokenResponse;
117
- }
118
-
119
- // Check if member is dropped
120
- if (memberData.action === MEMBER_ACTIONS.DROP) {
121
- return invalidTokenResponse;
122
- }
123
-
124
- // Add computed properties
125
- memberData.addressDisplayOption = getAddressDisplayOptions(memberData);
126
- console.log('memberData', memberData);
127
- memberData.isStudent = isStudent(memberData);
128
-
129
- return { memberData, isValid };
130
- } catch (error) {
131
- console.error('Error in validateMemberToken:', error);
132
- throw error;
133
- }
134
- }
135
-
136
61
  /** Performs bulk save operation for member data
137
62
  * @param { Array } memberDataList - Array of member data objects to save
138
63
  * @returns { Promise < Object >} - Bulk save operation result
@@ -144,7 +69,7 @@ async function bulkSaveMembers(memberDataList) {
144
69
 
145
70
  try {
146
71
  // bulkSave all with batches of 1000 items as this is the Velo limit for bulkSave
147
- const batches = createBatches(memberDataList, 1000);
72
+ const batches = chunkArray(memberDataList, 1000);
148
73
  return await Promise.all(
149
74
  batches.map(batch => wixData.bulkSave(COLLECTIONS.MEMBERS_DATA, batch))
150
75
  );
@@ -183,7 +108,7 @@ async function findMemberById(memberId) {
183
108
  * @param {boolean} options.excludeDropped - Whether to exclude dropped members (default: true)
184
109
  * @param {boolean} options.excludeSearchedMember - Whether to exclude a specific member (default: false)
185
110
  * @param {string|number} [options.memberId] - Member ID to exclude when excludeSearchedMember is true (optional)
186
- * @param {boolean} [options.queryAllMatches=false] - Whether to query all matches or just the first one (default: false)
111
+ * @param {boolean} [options.normalizeSlugForComparison=false] - Whether to normalize the slug for comparison (default: false)
187
112
  * @returns {Promise<Object|null>} - Member data or null if not found
188
113
  */
189
114
  async function getMemberBySlug({
@@ -191,7 +116,7 @@ async function getMemberBySlug({
191
116
  excludeDropped = true,
192
117
  excludeSearchedMember = false,
193
118
  memberId = null,
194
- queryAllMatches = false,
119
+ normalizeSlugForComparison = false,
195
120
  }) {
196
121
  if (!slug) return null;
197
122
 
@@ -205,17 +130,12 @@ async function getMemberBySlug({
205
130
  if (excludeSearchedMember && memberId) {
206
131
  query = query.ne('memberId', memberId);
207
132
  }
208
- let membersList;
209
- if (queryAllMatches) {
210
- query = query.limit(1000);
211
- membersList = await queryAllItems(query);
212
- } else {
213
- membersList = await query.find().then(res => res.items);
214
- }
133
+ query = query.limit(1000);
134
+ const membersList = await queryAllItems(query);
215
135
  let matchingMembers = membersList.filter(
216
136
  item => item.url && item.url.toLowerCase() === slug.toLowerCase()
217
137
  );
218
- if (queryAllMatches) {
138
+ if (normalizeSlugForComparison) {
219
139
  matchingMembers = membersList
220
140
  .filter(
221
141
  //remove trailing "-1", "-2", etc.
@@ -227,7 +147,7 @@ async function getMemberBySlug({
227
147
  const queryResultMsg = `Multiple members found with same slug ${slug} membersIds are : [${matchingMembers
228
148
  .map(member => member.memberId)
229
149
  .join(', ')}]`;
230
- if (!queryAllMatches) {
150
+ if (!normalizeSlugForComparison) {
231
151
  throw new Error(queryResultMsg);
232
152
  } else {
233
153
  console.log(queryResultMsg);
@@ -240,6 +160,59 @@ async function getMemberBySlug({
240
160
  }
241
161
  }
242
162
 
163
+ async function getMemberByContactId(contactId) {
164
+ if (!contactId) {
165
+ throw new Error('Contact ID is required');
166
+ }
167
+ try {
168
+ const members = await wixData
169
+ .query(COLLECTIONS.MEMBERS_DATA)
170
+ .eq('contactId', contactId)
171
+ .limit(2)
172
+ .find()
173
+ .then(res => res.items);
174
+ if (members.length > 1) {
175
+ throw new Error(
176
+ `[getMemberByContactId] Multiple members found with contactId ${contactId} membersIds are : [${members.map(member => member.memberId).join(', ')}]`
177
+ );
178
+ }
179
+ return members[0] || null;
180
+ } catch (error) {
181
+ throw new Error(
182
+ `[getMemberByContactId] Failed to retrieve member by contactId ${contactId} data: ${error.message}`
183
+ );
184
+ }
185
+ }
186
+ /**
187
+ * Gets all members with aboutyoustatus as null
188
+ * @returns {Promise<import('wix-data').WixDataQueryResult>} - WixDataQueryResult of member data
189
+ */
190
+ const getAllEmptyAboutYouMembers = async () => {
191
+ try {
192
+ const membersQuery = wixData
193
+ .query(COLLECTIONS.MEMBERS_DATA)
194
+ .isEmpty('aboutYourSelf')
195
+ .isNotEmpty('aboutYouHtml');
196
+ return await queryAllItems(membersQuery);
197
+ } catch (error) {
198
+ console.error('Error getting empty about you members:', error);
199
+ throw new Error(`Failed to get empty about you members: ${error.message}`);
200
+ }
201
+ };
202
+
203
+ /**
204
+ * updates member data
205
+ * @param {Object} memberToUpdate - The member data to update
206
+ * @returns {Promise<Object|null>} - Member data or null if not found
207
+ */
208
+ async function updateMember(memberToUpdate) {
209
+ try {
210
+ const updatedMember = await wixData.update(COLLECTIONS.MEMBERS_DATA, memberToUpdate);
211
+ return updatedMember;
212
+ } catch (error) {
213
+ throw new Error(`Failed to update member data: ${error.message}`);
214
+ }
215
+ }
243
216
  /**
244
217
  * Saves member registration data
245
218
  * @param {Object} data - Member data to save
@@ -271,7 +244,7 @@ async function saveRegistrationData(data, id) {
271
244
 
272
245
  await updateMemberContactInfo(data, existingMemberData);
273
246
 
274
- const saveData = await wixData.update(COLLECTIONS.MEMBERS_DATA, data);
247
+ const saveData = await updateMember(data);
275
248
  return {
276
249
  type: 'success',
277
250
  saveData,
@@ -318,12 +291,216 @@ async function urlExists(url, excludeMemberId) {
318
291
  }
319
292
  }
320
293
 
294
+ /**
295
+ * Checks URL uniqueness for a member
296
+ * @param {string} url - The URL to check
297
+ * @param {string} memberId - The member ID to exclude from the check
298
+ * @returns {Promise<Object>} Result object with isUnique boolean
299
+ */
300
+ async function checkUrlUniqueness(url, memberId) {
301
+ if (!url || !memberId) {
302
+ throw new Error('Missing required parameters: url and memberId are required');
303
+ }
304
+
305
+ try {
306
+ const trimmedUrl = url.trim();
307
+ const exists = await urlExists(trimmedUrl, memberId);
308
+
309
+ return { isUnique: !exists };
310
+ } catch (error) {
311
+ console.error('Error checking URL uniqueness:', error);
312
+ throw new Error(`Failed to check URL uniqueness: ${error.message}`);
313
+ }
314
+ }
315
+ /**
316
+ * Get all members with external profile images
317
+ * @returns {Promise<Array>} - Array of member IDs
318
+ */
319
+ async function getAllMembersWithExternalImages() {
320
+ try {
321
+ const membersQuery = await wixData
322
+ .query(COLLECTIONS.MEMBERS_DATA)
323
+ .isNotEmpty('profileImage')
324
+ .ne('profileImage', null);
325
+
326
+ const allItems = await queryAllItems(membersQuery);
327
+
328
+ // Filter for external images (not starting with 'wix:')
329
+ const membersWithExternalImages = allItems.filter(
330
+ member => member.profileImage && !member.profileImage.startsWith('wix:')
331
+ );
332
+
333
+ return membersWithExternalImages;
334
+ } catch (error) {
335
+ console.error('Error getting members with external images:', error);
336
+ return [];
337
+ }
338
+ }
339
+
340
+ async function getMembersWithWixUrl() {
341
+ const membersQuery = wixData
342
+ .query(COLLECTIONS.MEMBERS_DATA)
343
+ .eq('isVisible', true)
344
+ .eq('showWixUrl', true)
345
+ .ne('action', MEMBER_ACTIONS.DROP)
346
+ .ne('memberships.membertype', MEMBERSHIPS_TYPES.PAC_STAFF)
347
+ .isNotEmpty('url')
348
+ .limit(1000);
349
+ let currentResults = await membersQuery.find();
350
+ let i = 0;
351
+ const allItems = currentResults.items;
352
+ while (currentResults.hasNext()) {
353
+ if (i % 50 === 0) console.log(`page ${i}`);
354
+ currentResults = await currentResults.next();
355
+ allItems.push(...currentResults.items);
356
+ i++;
357
+ }
358
+ console.log('i is ', i);
359
+ const filtered = allItems.filter(item => typeof item.url === 'string' && !item.url.includes('/'));
360
+ console.log('filtered is ', filtered.length);
361
+ return filtered;
362
+ }
363
+
364
+ /**
365
+ * Gets all members who need contactFormEmail migration (missing contactFormEmail field)
366
+ * @returns {Promise<Array>} - Array of member data
367
+ */
368
+ const getAllMembersWithoutContactFormEmail = async () => {
369
+ try {
370
+ const membersQuery = wixData
371
+ .query(COLLECTIONS.MEMBERS_DATA)
372
+ .isEmpty('contactFormEmail')
373
+ .isNotEmpty('email')
374
+ .limit(1000);
375
+
376
+ const allItems = await queryAllItems(membersQuery);
377
+ return allItems;
378
+ } catch (error) {
379
+ console.error('Error getting members without contactFormEmail:', error);
380
+ throw new Error(`Failed to get members without contactFormEmail: ${error.message}`);
381
+ }
382
+ };
383
+
384
+ /* Gets all updated login emails from the updated emails database
385
+ * @returns {Promise<Array>} - Array of updated email data
386
+ */
387
+ const getAllUpdatedLoginEmails = async () => {
388
+ try {
389
+ const updatedEmailsQuery = await wixData
390
+ .query(COLLECTIONS.UPDATED_LOGIN_EMAILS)
391
+ .isNotEmpty('memberId')
392
+ .isNotEmpty('loginEmail')
393
+ .limit(1000);
394
+ return await queryAllItems(updatedEmailsQuery);
395
+ } catch (error) {
396
+ console.error('Error getting updated login emails:', error);
397
+ throw new Error(`Failed to get updated login emails: ${error.message}`);
398
+ }
399
+ };
400
+ /**
401
+ * Gets members by their member IDs for email sync
402
+ * @param {Array} memberIds - Array of member IDs to fetch
403
+ * @returns {Promise<Array>} - Array of member data
404
+ */
405
+ const getMembersByIds = async memberIds => {
406
+ try {
407
+ const membersQuery = wixData
408
+ .query(COLLECTIONS.MEMBERS_DATA)
409
+ .hasSome('memberId', memberIds)
410
+ .limit(1000);
411
+
412
+ return await queryAllItems(membersQuery);
413
+ } catch (error) {
414
+ console.error('Error getting members by IDs:', error);
415
+ throw new Error(`Failed to get members by IDs: ${error.message}`);
416
+ }
417
+ };
418
+
419
+ const getMemberByEmail = async email => {
420
+ try {
421
+ const members = await wixData
422
+ .query(COLLECTIONS.MEMBERS_DATA)
423
+ .eq('email', email)
424
+ .limit(2)
425
+ .find()
426
+ .then(res => res.items);
427
+ if (members.length > 1) {
428
+ throw new Error(
429
+ `[getMemberByEmail] Multiple members found with email ${email} membersIds are : [${members.map(member => member.memberId).join(', ')}]`
430
+ );
431
+ }
432
+ return members[0] || null;
433
+ } catch (error) {
434
+ console.error('Error getting member by email:', error);
435
+ throw new Error(`Failed to get member by email: ${error.message}`);
436
+ }
437
+ };
438
+
439
+ const getQAUsers = async () => {
440
+ try {
441
+ return await wixData
442
+ .query(COLLECTIONS.QA_USERS)
443
+ .include('member')
444
+ .find()
445
+ .then(res => res.items.map(item => item.member));
446
+ } catch (error) {
447
+ console.error('Error getting QA users:', error);
448
+ throw new Error(`Failed to get QA users: ${error.message}`);
449
+ }
450
+ };
451
+ async function getSiteMemberId(data) {
452
+ try {
453
+ console.log('data', data);
454
+ const memberId = data?.pac?.cst_recno;
455
+ if (!memberId) {
456
+ const errorMessage = `Member ID is missing in passed data ${JSON.stringify(data)}`;
457
+ console.error(errorMessage);
458
+ throw new Error(errorMessage);
459
+ }
460
+ const queryMemberResult = await wixData
461
+ .query(COLLECTIONS.MEMBERS_DATA)
462
+ .eq('memberId', Number(memberId))
463
+ .find()
464
+ .then(res => res.items);
465
+ if (!queryMemberResult.length || queryMemberResult.length > 1) {
466
+ throw new Error(
467
+ `Invalid Members count found in DB for email ${data.email} members count is : [${
468
+ queryMemberResult.length
469
+ }] membersIds are : [${queryMemberResult.map(member => member.memberId).join(', ')}]`
470
+ );
471
+ }
472
+ let memberData = queryMemberResult[0];
473
+ console.log('memberData', memberData);
474
+ const isNewUser = !memberData.contactId;
475
+ if (isNewUser) {
476
+ const memberDataWithContactId = await createContactAndMemberIfNew(memberData);
477
+ console.log('memberDataWithContactId', memberDataWithContactId);
478
+ memberData = memberDataWithContactId;
479
+ }
480
+ return memberData;
481
+ } catch (error) {
482
+ console.error('Error in getSiteMemberId', error.message);
483
+ throw error;
484
+ }
485
+ }
486
+
321
487
  module.exports = {
322
488
  findMemberByWixDataId,
323
489
  createContactAndMemberIfNew,
324
- validateMemberToken,
325
490
  saveRegistrationData,
326
491
  bulkSaveMembers,
327
492
  findMemberById,
328
493
  getMemberBySlug,
494
+ getMemberByContactId,
495
+ getAllEmptyAboutYouMembers,
496
+ updateMember,
497
+ getAllMembersWithExternalImages,
498
+ getMembersWithWixUrl,
499
+ getAllMembersWithoutContactFormEmail,
500
+ getAllUpdatedLoginEmails,
501
+ getMembersByIds,
502
+ getMemberByEmail,
503
+ getQAUsers,
504
+ getSiteMemberId,
505
+ checkUrlUniqueness,
329
506
  };
@@ -1,9 +1,8 @@
1
- const { secrets } = require('@wix/secrets');
2
-
3
- const { PAC_API_URL } = require('./daily-pull/consts');
1
+ const { PAC_API_URL } = require('./consts');
2
+ const { getSecret } = require('./utils');
4
3
 
5
4
  const getHeaders = async () => {
6
- const AUTH_TOKEN = await secrets.getSecretValue('members-data-api-key');
5
+ const AUTH_TOKEN = await getSecret('members-data-api-key');
7
6
  const headers = {
8
7
  Authorization: `Bearer ${AUTH_TOKEN}`,
9
8
  };
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ ...require('./methods'),
3
+ };
@@ -0,0 +1,177 @@
1
+ const { PAGES_PATHS } = require('../../public/consts');
2
+ //const { fetchAllItemsInParallel } = require('../cms-data-methods'); unused at host site
3
+ const { CONFIG_KEYS } = require('../consts');
4
+ const { getSiteConfigs } = require('../utils');
5
+
6
+ const { generateSEOTitle, stripHtmlTags, getMemberProfileData } = require('./utils');
7
+
8
+ const createRoutersHandlers = wixRouterMethods => {
9
+ const {
10
+ redirect,
11
+ ok,
12
+ notFound,
13
+ sendStatus,
14
+ WixRouterSitemapEntry: _WixRouterSitemapEntry,
15
+ } = wixRouterMethods; // These dependencies needs to be injected as they do not have an SDK equivalent for now
16
+
17
+ async function profileRouter(request) {
18
+ const slug = request.path[0];
19
+ if (!slug) {
20
+ return redirect(request.baseUrl);
21
+ }
22
+ try {
23
+ const siteConfigs = await getSiteConfigs();
24
+ const siteAssociation = siteConfigs[CONFIG_KEYS.SITE_ASSOCIATION];
25
+ const defaultSEODescription = siteConfigs[CONFIG_KEYS.DEFAULT_PROFILE_SEO_DESCRIPTION];
26
+ const siteLogoUrl = siteConfigs[CONFIG_KEYS.SITE_LOGO_URL];
27
+ const defaultProfileImage = siteConfigs[CONFIG_KEYS.DEFAULT_PROFILE_IMAGE];
28
+ const profileData = await getMemberProfileData(slug, siteAssociation);
29
+ if (profileData && profileData.showWixUrl) {
30
+ const ogImage = profileData.profileImage || profileData.logoImage || siteLogoUrl;
31
+ const seoTitle = generateSEOTitle({
32
+ fullName: profileData.fullName,
33
+ areasOfPractices: profileData.areasOfPractices,
34
+ siteAssociation,
35
+ });
36
+ // Use stripped HTML from aboutService rich text content
37
+ let description = stripHtmlTags(profileData.aboutService) || defaultSEODescription;
38
+
39
+ // Limit description to 160 characters for optimal SEO
40
+ if (description.length > 160) {
41
+ description = description.substring(0, 157) + '...';
42
+ }
43
+ const profileUrl = `${request.baseUrl}/${PAGES_PATHS.PROFILE}/${profileData.url}`;
44
+ const isPrivateMember = profileData.isPrivateMember;
45
+ const seoData = {
46
+ title: seoTitle,
47
+ description: description,
48
+ noIndex: isPrivateMember,
49
+ metaTags: [
50
+ {
51
+ name: 'description',
52
+ content: description,
53
+ },
54
+ {
55
+ name: 'keywords',
56
+ content:
57
+ `${profileData.fullName}, ${profileData.areasOfPractices ? profileData.areasOfPractices.slice(0, 3).join(', ') : ''}, ${siteAssociation}, ${profileData.city || ''}, ${profileData.state || ''}`
58
+ .replace(/,\s*,/g, ',')
59
+ .replace(/^,|,$/g, ''),
60
+ },
61
+ {
62
+ name: 'author',
63
+ content: profileData.fullName,
64
+ },
65
+ {
66
+ name: 'robots',
67
+ content: isPrivateMember ? 'noindex, nofollow' : 'index, follow',
68
+ },
69
+ // Open Graph tags
70
+ {
71
+ property: 'og:type',
72
+ content: 'profile',
73
+ },
74
+ {
75
+ property: 'og:title',
76
+ content: seoTitle,
77
+ },
78
+ {
79
+ property: 'og:description',
80
+ content: description,
81
+ },
82
+ {
83
+ property: 'og:image',
84
+ content: ogImage,
85
+ },
86
+ {
87
+ property: 'og:url',
88
+ content: profileUrl,
89
+ },
90
+ {
91
+ property: 'og:site_name',
92
+ content: `${siteAssociation} Members`,
93
+ },
94
+ // Twitter Card tags
95
+ {
96
+ name: 'twitter:card',
97
+ content: 'summary_large_image',
98
+ },
99
+ {
100
+ name: 'twitter:title',
101
+ content: seoTitle,
102
+ },
103
+ {
104
+ name: 'twitter:description',
105
+ content: description,
106
+ },
107
+ {
108
+ name: 'twitter:image',
109
+ content: ogImage,
110
+ },
111
+ // Additional SEO tags
112
+ {
113
+ name: 'geo.region',
114
+ content: profileData.state || '',
115
+ },
116
+ {
117
+ name: 'geo.placename',
118
+ content: profileData.city || '',
119
+ },
120
+ ].filter(tag => tag.content && tag.content.trim() !== ''), // Remove empty tags
121
+ };
122
+ return ok('profile', { ...profileData, defaultProfileImage }, seoData);
123
+ }
124
+ return notFound();
125
+ } catch (error) {
126
+ console.error(error);
127
+ return sendStatus('500', 'Internal Server Error');
128
+ }
129
+ }
130
+ function profileSiteMap(_sitemapRequest) {
131
+ return [];
132
+ // Commented out - currently disabled in host site
133
+ // try {
134
+ // const membersQuery = wixData
135
+ // .query(COLLECTIONS.MEMBERS_DATA)
136
+ // .eq('showWixUrl', true)
137
+ // .isNotEmpty('url')
138
+ // .ne('action', 'drop')
139
+ // .fields('url', 'fullName');
140
+
141
+ // const allMembers = await fetchAllItemsInParallel(membersQuery);
142
+
143
+ // const batchSize = 1000;
144
+ // const sitemapEntries = [];
145
+ // const totalItems = allMembers.items.length;
146
+
147
+ // for (let i = 0; i < totalItems; i += batchSize) {
148
+ // const batch = allMembers.items.slice(i, i + batchSize);
149
+ // const batchEntries = batch.map(member => {
150
+ // const entry = new WixRouterSitemapEntry(member.fullName);
151
+ // entry.pageName = 'profile';
152
+ // entry.url = `${PAGES_PATHS.PROFILE}/${member.url}`;
153
+ // entry.title = member.fullName;
154
+ // entry.changeFrequency = 'monthly';
155
+ // entry.priority = 1.0;
156
+ // return entry;
157
+ // });
158
+ // sitemapEntries.push(...batchEntries);
159
+ // }
160
+
161
+ // return sitemapEntries;
162
+ // } catch (error) {
163
+ // console.error('Error generating profile sitemap:', error);
164
+ // return [];
165
+ // }
166
+ }
167
+ //Add other routers here
168
+ return {
169
+ profileRouter,
170
+ profileSiteMap,
171
+ //Add other routers here
172
+ };
173
+ };
174
+
175
+ module.exports = {
176
+ createRoutersHandlers,
177
+ };