abmp-npm 2.0.77 → 2.0.78

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 (99) hide show
  1. package/package.json +8 -1
  2. package/.claude/skills/wix-data-query/SKILL.md +0 -167
  3. package/.claude/skills/wix-data-query/references/backup-archive.md +0 -136
  4. package/.claude/skills/wix-data-query/references/members-data-latest.md +0 -125
  5. package/.claude/skills/wix-data-query/references/recipes.md +0 -264
  6. package/.claude/worktrees/fix-save-button-disable/.husky/pre-commit +0 -19
  7. package/.claude/worktrees/fix-save-button-disable/.prettierignore +0 -7
  8. package/.claude/worktrees/fix-save-button-disable/.prettierrc.json +0 -16
  9. package/.claude/worktrees/fix-save-button-disable/README.md +0 -1
  10. package/.claude/worktrees/fix-save-button-disable/backend/README-member-contact-flows.md +0 -136
  11. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/daily-pull-execution-check.test.js +0 -124
  12. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/ensure-unique-urls-in-batch.test.js +0 -129
  13. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/login-email-sync.test.js +0 -49
  14. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/transient-retry-and-bulk-lookup.test.js +0 -129
  15. package/.claude/worktrees/fix-save-button-disable/backend/__tests__/url-uniqueness.test.js +0 -194
  16. package/.claude/worktrees/fix-save-button-disable/backend/automations-methods.js +0 -13
  17. package/.claude/worktrees/fix-save-button-disable/backend/cms-data-methods.js +0 -251
  18. package/.claude/worktrees/fix-save-button-disable/backend/consts.js +0 -58
  19. package/.claude/worktrees/fix-save-button-disable/backend/contacts-methods.js +0 -67
  20. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/bulk-process-methods.js +0 -222
  21. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/consts.js +0 -39
  22. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/index.js +0 -5
  23. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/process-member-methods.js +0 -213
  24. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/schedule-methods.js +0 -41
  25. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/sync-to-cms-methods.js +0 -161
  26. package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/utils.js +0 -151
  27. package/.claude/worktrees/fix-save-button-disable/backend/data-hooks.js +0 -29
  28. package/.claude/worktrees/fix-save-button-disable/backend/dev-only-methods.js +0 -58
  29. package/.claude/worktrees/fix-save-button-disable/backend/elevated-modules.js +0 -18
  30. package/.claude/worktrees/fix-save-button-disable/backend/forms-methods.js +0 -52
  31. package/.claude/worktrees/fix-save-button-disable/backend/http-functions/httpFunctions.js +0 -86
  32. package/.claude/worktrees/fix-save-button-disable/backend/http-functions/index.js +0 -3
  33. package/.claude/worktrees/fix-save-button-disable/backend/http-functions/interests.js +0 -35
  34. package/.claude/worktrees/fix-save-button-disable/backend/index.js +0 -19
  35. package/.claude/worktrees/fix-save-button-disable/backend/jobs.js +0 -158
  36. package/.claude/worktrees/fix-save-button-disable/backend/login/generate-member-session-token.js +0 -27
  37. package/.claude/worktrees/fix-save-button-disable/backend/login/index.js +0 -8
  38. package/.claude/worktrees/fix-save-button-disable/backend/login/qa-login-methods.js +0 -54
  39. package/.claude/worktrees/fix-save-button-disable/backend/login/sso-methods.js +0 -159
  40. package/.claude/worktrees/fix-save-button-disable/backend/member-contact-orchestration.js +0 -111
  41. package/.claude/worktrees/fix-save-button-disable/backend/members-area-methods.js +0 -88
  42. package/.claude/worktrees/fix-save-button-disable/backend/members-data-methods.js +0 -789
  43. package/.claude/worktrees/fix-save-button-disable/backend/pac-api-methods.js +0 -54
  44. package/.claude/worktrees/fix-save-button-disable/backend/routers/index.js +0 -3
  45. package/.claude/worktrees/fix-save-button-disable/backend/routers/methods.js +0 -191
  46. package/.claude/worktrees/fix-save-button-disable/backend/routers/utils.js +0 -128
  47. package/.claude/worktrees/fix-save-button-disable/backend/search-filters-methods.js +0 -124
  48. package/.claude/worktrees/fix-save-button-disable/backend/tasks/address-primary-methods.js +0 -256
  49. package/.claude/worktrees/fix-save-button-disable/backend/tasks/address-visibility-methods.js +0 -160
  50. package/.claude/worktrees/fix-save-button-disable/backend/tasks/consts.js +0 -34
  51. package/.claude/worktrees/fix-save-button-disable/backend/tasks/daily-pull-check-methods.js +0 -78
  52. package/.claude/worktrees/fix-save-button-disable/backend/tasks/email-normalize-methods.js +0 -134
  53. package/.claude/worktrees/fix-save-button-disable/backend/tasks/index.js +0 -10
  54. package/.claude/worktrees/fix-save-button-disable/backend/tasks/migration-methods.js +0 -46
  55. package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-configs.js +0 -251
  56. package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-helpers-methods.js +0 -433
  57. package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-process-methods.js +0 -606
  58. package/.claude/worktrees/fix-save-button-disable/backend/tasks/url-migration-methods.js +0 -378
  59. package/.claude/worktrees/fix-save-button-disable/backend/tasks/url-space-fix-methods.js +0 -174
  60. package/.claude/worktrees/fix-save-button-disable/backend/utils.js +0 -277
  61. package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/extract-duplicate-url-groups.js +0 -201
  62. package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/find-duplicate-ids.js +0 -159
  63. package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/find-duplicate-urls.js +0 -201
  64. package/.claude/worktrees/fix-save-button-disable/eslint.config.js +0 -120
  65. package/.claude/worktrees/fix-save-button-disable/index.js +0 -5
  66. package/.claude/worktrees/fix-save-button-disable/package-lock.json +0 -12160
  67. package/.claude/worktrees/fix-save-button-disable/package.json +0 -60
  68. package/.claude/worktrees/fix-save-button-disable/pages/ContactUs.js +0 -129
  69. package/.claude/worktrees/fix-save-button-disable/pages/Home.js +0 -779
  70. package/.claude/worktrees/fix-save-button-disable/pages/LearnMore.js +0 -27
  71. package/.claude/worktrees/fix-save-button-disable/pages/LoadingPage.js +0 -29
  72. package/.claude/worktrees/fix-save-button-disable/pages/Profile.js +0 -354
  73. package/.claude/worktrees/fix-save-button-disable/pages/QAPage.js +0 -39
  74. package/.claude/worktrees/fix-save-button-disable/pages/SaveAlerts.js +0 -13
  75. package/.claude/worktrees/fix-save-button-disable/pages/SelectBannerImages.js +0 -46
  76. package/.claude/worktrees/fix-save-button-disable/pages/deleteConfirm.js +0 -19
  77. package/.claude/worktrees/fix-save-button-disable/pages/index.js +0 -12
  78. package/.claude/worktrees/fix-save-button-disable/pages/personalDetails.js +0 -2486
  79. package/.claude/worktrees/fix-save-button-disable/public/Utils/homePage.js +0 -798
  80. package/.claude/worktrees/fix-save-button-disable/public/Utils/personalDetailsUtils.js +0 -25
  81. package/.claude/worktrees/fix-save-button-disable/public/Utils/sharedUtils.js +0 -235
  82. package/.claude/worktrees/fix-save-button-disable/public/consts.js +0 -113
  83. package/.claude/worktrees/fix-save-button-disable/public/index.js +0 -5
  84. package/.claude/worktrees/fix-save-button-disable/public/messages.js +0 -16
  85. package/.claude/worktrees/fix-save-button-disable/public/sso-auth-methods.js +0 -43
  86. package/.husky/pre-commit +0 -19
  87. package/.prettierignore +0 -7
  88. package/.prettierrc.json +0 -16
  89. package/backend/__tests__/daily-pull-execution-check.test.js +0 -124
  90. package/backend/__tests__/ensure-unique-urls-in-batch.test.js +0 -129
  91. package/backend/__tests__/login-email-sync.test.js +0 -49
  92. package/backend/__tests__/map-link.test.js +0 -115
  93. package/backend/__tests__/transient-retry-and-bulk-lookup.test.js +0 -129
  94. package/backend/__tests__/url-uniqueness.test.js +0 -194
  95. package/backend/__tests__/url-validation.test.js +0 -69
  96. package/dev-only-scripts/extract-duplicate-url-groups.js +0 -201
  97. package/dev-only-scripts/find-duplicate-ids.js +0 -159
  98. package/dev-only-scripts/find-duplicate-urls.js +0 -201
  99. package/eslint.config.js +0 -120
@@ -1,251 +0,0 @@
1
- const geohash = require('ngeohash');
2
-
3
- const { COLLECTIONS, MEMBERS_FIELDS } = require('../public/consts.js');
4
- const { findMainAddress } = require('../public/Utils/sharedUtils.js');
5
- const { calculateDistance, shuffleArray } = require('../public/Utils/sharedUtils.js');
6
-
7
- const {
8
- GEO_HASH_PRECISION,
9
- MAX__MEMBERS_SEARCH_RESULTS,
10
- WIX_QUERY_MAX_LIMIT,
11
- MEMBERSHIPS_TYPES,
12
- } = require('./consts.js');
13
- const { wixData } = require('./elevated-modules');
14
-
15
- function buildMembersSearchQuery(data) {
16
- console.log('data: ', JSON.stringify(data));
17
- const { filter, isSearchingNearby, includeStudents = false } = data;
18
- const isUserLocationEnabled = filter.latitude !== 0 || filter.longitude !== 0;
19
- filter.searchText = filter.searchText || '';
20
- filter.stateSearch = filter.stateSearch || '';
21
- filter.practiceAreasSearch = filter.practiceAreasSearch || '';
22
- filter.practiceAreas = filter.practiceAreas || [];
23
- filter.state = filter.state || [];
24
- filter.citySearch = filter.citySearch || '';
25
- filter.city = filter.city || [];
26
- filter.latitude = filter.latitude || 0;
27
- filter.longitude = filter.longitude || 0;
28
- filter.postalcode = filter.postalcode || '';
29
- return {
30
- get: () => {
31
- let query = wixData
32
- .query(COLLECTIONS.MEMBERS_DATA)
33
- .ne('optOut', true)
34
- .ne('action', 'drop')
35
- .ne('memberships.membertype', MEMBERSHIPS_TYPES.PAC_STAFF)
36
- .eq('isVisible', true);
37
- let filterConfig = [
38
- {
39
- filterKey: 'practiceAreas',
40
- queryMethod: 'hasSome',
41
- queryField: 'areasOfPractices',
42
- condition: value => value && value.length > 0,
43
- fallback: {
44
- filterKey: 'practiceAreasSearch',
45
- queryMethod: 'contains',
46
- queryField: 'areasOfPractices',
47
- condition: value => value && value.trim() !== '',
48
- },
49
- },
50
- {
51
- filterKey: 'postalcode',
52
- queryMethod: 'contains',
53
- queryField: 'addresses.postalcode',
54
- condition: value => value && value.trim() !== '',
55
- },
56
- {
57
- filterKey: 'state',
58
- queryMethod: 'hasSome',
59
- queryField: 'addresses.state',
60
- condition: value => value && value.length > 0,
61
- fallback: {
62
- filterKey: 'stateSearch',
63
- queryMethod: 'contains',
64
- queryField: 'addresses.state',
65
- condition: value => value && value.trim() !== '',
66
- },
67
- },
68
- {
69
- filterKey: 'city',
70
- queryMethod: 'hasSome',
71
- queryField: 'addresses.city',
72
- condition: value => value && value.length > 0,
73
- fallback: {
74
- filterKey: 'citySearch',
75
- queryMethod: 'contains',
76
- queryField: 'addresses.city',
77
- condition: value => value && value.trim() !== '',
78
- },
79
- },
80
- ];
81
- //Ignore state, city and postal code when isSearchingNearby is true
82
- if (isSearchingNearby) {
83
- filterConfig = filterConfig.filter(
84
- config => !['state', 'city', 'postalcode'].includes(config.filterKey)
85
- );
86
- }
87
- const applyFilterToQuery = (query, config, filter) => {
88
- const filterValue = filter[config.filterKey];
89
- if (config.condition(filterValue)) {
90
- return query[config.queryMethod](config.queryField, filterValue);
91
- } else if (config.fallback) {
92
- return applyFilterToQuery(query, config.fallback, filter);
93
- }
94
- return query;
95
- };
96
- // Apply filters using the configuration
97
- filterConfig.forEach(config => {
98
- query = applyFilterToQuery(query, config, filter);
99
- });
100
- if (isUserLocationEnabled && isSearchingNearby) {
101
- const userGeohash = geohash.encode(filter.latitude, filter.longitude, GEO_HASH_PRECISION);
102
- const neighborGeohashes = geohash.neighbors(userGeohash);
103
- const geohashList = [userGeohash, ...neighborGeohashes];
104
- query = query.hasSome('locHash', geohashList);
105
- }
106
- if (filter.searchText.trim() !== '') {
107
- query = query.contains('fullName', filter.searchText);
108
- }
109
- if (!includeStudents) {
110
- query = query.ne('memberships.membertype', MEMBERSHIPS_TYPES.STUDENT);
111
- }
112
- return query;
113
- },
114
- run: async query => {
115
- const baseQuery = query.ascending('firstName').fields(...Object.values(MEMBERS_FIELDS));
116
- const getRandomSkip = totalCount => {
117
- let randomSkip = 0;
118
- if (totalCount > MAX__MEMBERS_SEARCH_RESULTS) {
119
- const maxSkip = totalCount - MAX__MEMBERS_SEARCH_RESULTS;
120
- randomSkip = Math.floor(Math.random() * (maxSkip + 1));
121
- }
122
- return randomSkip;
123
- };
124
- const getResult = async query => {
125
- if (isSearchingNearby) {
126
- return fetchAllItemsInParallel(baseQuery);
127
- }
128
- const totalCount = await query.count();
129
- const randomSkip = getRandomSkip(totalCount);
130
- const result = await query
131
- .skip(randomSkip)
132
- .limit(MAX__MEMBERS_SEARCH_RESULTS)
133
- .find({ omitTotalCount: true });
134
-
135
- // Shuffle the result items for additional randomization
136
- return {
137
- ...result,
138
- items: shuffleArray(result.items),
139
- };
140
- };
141
-
142
- const result = await getResult(baseQuery);
143
- if (isUserLocationEnabled) {
144
- const withDistances = result.items.map(item => ({
145
- ...item,
146
- distance: calculateDistance(
147
- {
148
- latitude: filter.latitude,
149
- longitude: filter.longitude,
150
- },
151
- findMainAddress(item.addressDisplayOption, item.addresses, {
152
- requireValidCoordinates: true,
153
- })
154
- ),
155
- }));
156
- const resultWithDistances = {
157
- ...result,
158
- items: withDistances,
159
- };
160
- if (isSearchingNearby) {
161
- return {
162
- ...resultWithDistances,
163
- items: withDistances
164
- .filter(item => item.distance !== null)
165
- .sort((a, b) => a.distance - b.distance)
166
- .slice(0, MAX__MEMBERS_SEARCH_RESULTS),
167
- };
168
- }
169
- return resultWithDistances;
170
- }
171
- return result;
172
- },
173
- };
174
- }
175
-
176
- // Generic parallel fetch function for large datasets
177
- async function fetchAllItemsInParallel(query) {
178
- const batchSize = WIX_QUERY_MAX_LIMIT;
179
- const allItems = [];
180
-
181
- const firstResult = await query.skip(0).limit(batchSize).find();
182
-
183
- const totalBatches = firstResult.totalPages;
184
- allItems.push(...firstResult.items);
185
-
186
- if (totalBatches > 1) {
187
- // Create parallel promises for all remaining batches
188
- const batchPromises = [];
189
- for (let i = 1; i < totalBatches; i++) {
190
- const skip = i * batchSize;
191
- const promise = query
192
- .skip(skip)
193
- .limit(batchSize)
194
- .find()
195
- .then(result => result.items);
196
- batchPromises.push(promise);
197
- }
198
-
199
- // Execute all batches in parallel
200
- const batchResults = await Promise.all(batchPromises);
201
- for (const items of batchResults) {
202
- if (items.length > 0) {
203
- allItems.push(...items);
204
- }
205
- }
206
- }
207
-
208
- return {
209
- ...firstResult,
210
- items: allItems,
211
- };
212
- }
213
-
214
- /**
215
- * Get all interests from the database
216
- * @returns {Promise<Array<string>>} Array of interest titles sorted alphabetically
217
- */
218
- async function getInterestAll() {
219
- try {
220
- let res = await wixData.query(COLLECTIONS.INTERESTS).limit(1000).find();
221
-
222
- let interests = res.items.map(x => x.title);
223
-
224
- while (res.hasNext()) {
225
- res = await res.next();
226
- interests.push(...res.items.map(x => x.title));
227
- }
228
-
229
- // Sort the interests alphabetically (case-insensitive)
230
- interests = interests.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
231
-
232
- return interests;
233
- } catch (e) {
234
- console.error('Error in getInterestAll:', e);
235
- throw e;
236
- }
237
- }
238
- async function clearCollection(collectionName) {
239
- try {
240
- await wixData.truncate(collectionName);
241
- } catch (err) {
242
- throw new Error(`Failed to clearCollection ${collectionName} with error: ${err.message}`);
243
- }
244
- }
245
-
246
- module.exports = {
247
- buildMembersSearchQuery,
248
- fetchAllItemsInParallel,
249
- getInterestAll,
250
- clearCollection,
251
- };
@@ -1,58 +0,0 @@
1
- const PAC_API_URL = 'https://members.abmp.com/eweb/api/Wix';
2
- const TEST_PAC_API_URL = 'https://members-test.abmp.com/eweb/api/Wix';
3
- const BACKUP_API_URL = 'https://psdevteamenterpris.wixstudio.com/abmp-backup/_functions';
4
- const SSO_TOKEN_AUTH_API_URL = 'https://members.professionalassistcorp.com/';
5
-
6
- /**
7
- * Valid configuration keys for getSiteConfigs function
8
- * @readonly
9
- * @enum {string}
10
- */
11
- const CONFIG_KEYS = {
12
- AUTOMATION_EMAIL_TRIGGER_ID: 'AUTOMATION_EMAIL_TRIGGER_ID',
13
- SITE_ASSOCIATION: 'SITE_ASSOCIATION',
14
- DEFAULT_PROFILE_SEO_DESCRIPTION: 'DEFAULT_PROFILE_SEO_DESCRIPTION',
15
- INTERESTS_API_URL: 'INTERESTS_API_URL',
16
- SITE_LOGO_URL: 'SITE_LOGO_URL',
17
- MEMBERS_EXTERNAL_PORTAL_URL: 'MEMBERS_EXTERNAL_PORTAL_URL',
18
- DEFAULT_PROFILE_IMAGE: 'DEFAULT_PROFILE_IMAGE',
19
- QA_ALLOW_ANY_MEMBER: 'QA_ALLOW_ANY_MEMBER',
20
- };
21
-
22
- const MAX__MEMBERS_SEARCH_RESULTS = 120;
23
- const WIX_QUERY_MAX_LIMIT = 1000;
24
-
25
- const GEO_HASH_PRECISION = 3;
26
-
27
- const COMPILED_FILTERS_FIELDS = {
28
- COMPILED_STATE_LIST: 'COMPILED_STATE_LIST',
29
- COMPILED_AREAS_OF_PRACTICES: 'COMPILED_AREAS_OF_PRACTICES',
30
- COMPILED_STATE_CITY_MAP: 'COMPILED_STATE_CITY_MAP',
31
- };
32
- const MEMBERSHIPS_TYPES = {
33
- STUDENT: 'Student',
34
- PAC_STAFF: 'PAC STAFF',
35
- };
36
-
37
- /**
38
- * Possible outcomes of attempting to change a Wix member's login email during the sync.
39
- */
40
- const LOGIN_EMAIL_SYNC_STATUS = {
41
- UPDATED: 'updated', // Wix login email successfully changed to the desired email
42
- FAILED: 'failed', // change failed -> keep the CMS login email unchanged and report for manual handling
43
- SKIPPED: 'skipped', // member has no wixMemberId, nothing to change
44
- };
45
-
46
- module.exports = {
47
- CONFIG_KEYS,
48
- MAX__MEMBERS_SEARCH_RESULTS,
49
- WIX_QUERY_MAX_LIMIT,
50
- GEO_HASH_PRECISION,
51
- PAC_API_URL,
52
- TEST_PAC_API_URL,
53
- COMPILED_FILTERS_FIELDS,
54
- MEMBERSHIPS_TYPES,
55
- SSO_TOKEN_AUTH_API_URL,
56
- BACKUP_API_URL,
57
- LOGIN_EMAIL_SYNC_STATUS,
58
- };
@@ -1,67 +0,0 @@
1
- const { contacts } = require('@wix/crm');
2
- const { auth } = require('@wix/essentials');
3
-
4
- const elevatedGetContact = auth.elevate(contacts.getContact);
5
- const elevatedUpdateContact = auth.elevate(contacts.updateContact);
6
- const elevatedCreateContact = auth.elevate(contacts.createContact);
7
- const elevatedDeleteContact = auth.elevate(contacts.deleteContact);
8
-
9
- const deleteSiteContact = contactId => elevatedDeleteContact(contactId);
10
-
11
- /**
12
- * Create a contact in Wix CRM
13
- * @param {Object} contactData - Contact data
14
- * @returns {Promise<Object>} - Contact data
15
- */
16
- async function createSiteContact(contactData) {
17
- console.log('[createSiteContact]contactData', JSON.stringify(contactData, null, 2));
18
- if (!contactData || !contactData.contactFormEmail) {
19
- throw new Error('Contact data is required');
20
- }
21
- const contactInfo = {
22
- name: {
23
- first: contactData.firstName,
24
- last: contactData.lastName,
25
- },
26
- emails: {
27
- items: [{ email: contactData.contactFormEmail, primary: true }],
28
- },
29
- };
30
- console.log('[createSiteContact]contactInfo', JSON.stringify(contactInfo, null, 2));
31
- // Intentionally NOT passing allowDuplicates: Wix CRM enforces email uniqueness
32
- // case-insensitively, which is the guard we want against two different members sharing an
33
- // email. Same-member cases never reach here — they collapse to a single entity upstream.
34
- const createContactResponse = await elevatedCreateContact(contactInfo);
35
- console.log(
36
- '[createSiteContact]createContactResponse',
37
- JSON.stringify(createContactResponse, null, 2)
38
- );
39
- return createContactResponse.contact._id;
40
- }
41
- /**
42
- * Generic contact update helper function
43
- * @param {string} contactId - The contact ID in Wix CRM
44
- * @param {function} updateInfoCallback - Function that returns the updated info object
45
- * @param {string} operationName - Name of the operation for logging
46
- */
47
- async function updateContactInfo(contactId, updateInfoCallback, operationName) {
48
- if (!contactId) {
49
- throw new Error('Contact ID is required');
50
- }
51
-
52
- try {
53
- const contact = await elevatedGetContact(contactId);
54
- const currentInfo = contact.info;
55
- const updatedInfo = updateInfoCallback(currentInfo);
56
- await elevatedUpdateContact(contactId, updatedInfo, contact.revision);
57
- } catch (error) {
58
- console.error(`Error in ${operationName}:`, error);
59
- throw new Error(`Failed to ${operationName}: ${error.message}`);
60
- }
61
- }
62
-
63
- module.exports = {
64
- createSiteContact,
65
- updateContactInfo,
66
- deleteSiteContact,
67
- };
@@ -1,222 +0,0 @@
1
- const { bulkSaveMembers, getMemberBySlug, findMembersByIds } = require('../members-data-methods');
2
- const { extractUrlCounter } = require('../utils');
3
-
4
- const { generateUpdatedMemberData } = require('./process-member-methods');
5
- const {
6
- changeWixMembersEmails,
7
- summarizeLoginEmailOutcomes,
8
- incrementUrlCounter,
9
- extractBaseUrl,
10
- } = require('./utils');
11
-
12
- /**
13
- * Ensures unique URLs within a batch of members by deduplicating URLs
14
- * Groups members by their base URL (normalized) and assigns unique counters
15
- * Also checks database to handle cross-page conflicts
16
- * @param {Array} memberDataList - Array of processed member data
17
- * @returns {Promise<Array>} - Array of members with unique URLs assigned
18
- */
19
- async function ensureUniqueUrlsInBatch(memberDataList) {
20
- if (!Array.isArray(memberDataList) || memberDataList.length === 0) {
21
- return memberDataList;
22
- }
23
-
24
- // Group members by their normalized base URL (case-insensitive to avoid
25
- // "John-12" and "john-12" being treated as different when slugs are matched case-insensitively)
26
- const urlGroups = new Map();
27
-
28
- memberDataList.forEach(member => {
29
- if (!member || !member.url) {
30
- return;
31
- }
32
-
33
- const baseUrl = extractBaseUrl(member.url);
34
- const groupKey = baseUrl.toLowerCase();
35
- if (!urlGroups.has(groupKey)) {
36
- urlGroups.set(groupKey, { members: [], baseUrl });
37
- }
38
- urlGroups.get(groupKey).members.push(member);
39
- });
40
-
41
- // For each group, check database and assign unique URLs sequentially
42
- for (const [, { members, baseUrl }] of urlGroups.entries()) {
43
- if (members.length <= 1) {
44
- // Single member - still check DB to ensure it doesn't conflict with other pages
45
- const member = members[0];
46
- if (member) {
47
- const dbMember = await getMemberBySlug({
48
- slug: baseUrl,
49
- excludeDropped: false,
50
- normalizeSlugForComparison: true,
51
- });
52
-
53
- if (dbMember && dbMember.url) {
54
- // Conflict found in DB, need to add counter
55
- member.url = incrementUrlCounter(dbMember.url, baseUrl);
56
- console.log(
57
- `Found DB conflict for single member with base URL "${baseUrl}", assigned: ${member.url}`
58
- );
59
- }
60
- }
61
- continue;
62
- }
63
-
64
- // Sort members to ensure consistent ordering
65
- members.sort((a, b) => {
66
- if (a.url && b.url) {
67
- return String(a.url).localeCompare(String(b.url));
68
- }
69
- return 0;
70
- });
71
-
72
- // Check database for existing members with this base URL to find highest counter
73
- const dbMember = await getMemberBySlug({
74
- slug: baseUrl,
75
- excludeDropped: false,
76
- normalizeSlugForComparison: true,
77
- });
78
-
79
- const dbMaxCounter = extractUrlCounter(dbMember?.url);
80
-
81
- // Find the highest existing counter among all members in this batch group
82
- let batchMaxCounter = -1;
83
- members.forEach(member => {
84
- const originalUrl = member.url;
85
- const urlCounter = extractUrlCounter(originalUrl);
86
- if (urlCounter > batchMaxCounter) {
87
- batchMaxCounter = urlCounter;
88
- }
89
- });
90
-
91
- // Start index from the maximum of DB counter and batch counter + 1
92
- const maxCounter = Math.max(dbMaxCounter, batchMaxCounter);
93
- const startIndex = maxCounter + 1;
94
-
95
- // Assign unique URLs: start from the appropriate index
96
- members.forEach((member, index) => {
97
- const assignedIndex = startIndex + index;
98
- if (assignedIndex === 0) {
99
- // Index 0 means no counter, use baseUrl
100
- member.url = baseUrl;
101
- } else {
102
- // Index > 0 means add counter
103
- member.url = `${baseUrl}-${assignedIndex}`;
104
- }
105
- });
106
-
107
- console.log(
108
- `Deduplicated ${
109
- members.length
110
- } members with base URL "${baseUrl}" (DB max: ${dbMaxCounter}, batch max: ${batchMaxCounter}, start: ${startIndex}): ${members
111
- .map(m => m.url)
112
- .join(', ')}`
113
- );
114
- }
115
-
116
- return memberDataList;
117
- }
118
-
119
- /**
120
- * Processes and saves multiple member records in bulk
121
- * @param {Object} options - The options object
122
- * @param {Array} options.memberDataList - Array of member data from API
123
- * @param {number} options.currentPageNumber - Current page number being processed
124
- * @param {Array} memberDataList - Array of member data from API
125
- * @returns {Promise<Object>} - Bulk save operation result with statistics
126
- */
127
- const bulkProcessAndSaveMemberData = async ({ memberDataList, currentPageNumber }) => {
128
- if (!Array.isArray(memberDataList) || memberDataList.length === 0) {
129
- throw new Error('Invalid member data list provided');
130
- }
131
-
132
- const startTime = Date.now();
133
-
134
- try {
135
- // Prefetch all existing members for this page in a few chunked queries instead of
136
- // one query per member — thousands of parallel lookups made the whole page fail
137
- // whenever a single one hit a transient network error.
138
- const existingMembersById = await findMembersByIds(
139
- memberDataList.map(memberData => memberData?.memberid)
140
- );
141
-
142
- const processedMemberDataPromises = memberDataList.map(memberData =>
143
- generateUpdatedMemberData({
144
- inputMemberData: memberData,
145
- currentPageNumber,
146
- existingDbMember: existingMembersById.get(String(memberData?.memberid)) ?? null,
147
- })
148
- );
149
-
150
- const processedMemberDataList = await Promise.all(processedMemberDataPromises);
151
- const validMemberData = processedMemberDataList.filter(
152
- data => data !== null && data !== undefined
153
- );
154
- if (validMemberData.length === 0) {
155
- return {
156
- totalProcessed: memberDataList.length,
157
- totalSaved: 0,
158
- totalFailed: memberDataList.length,
159
- processingTime: Date.now() - startTime,
160
- };
161
- }
162
- const newMembers = validMemberData.filter(data => data.isNewToDb);
163
- const existingMembers = validMemberData.filter(data => !data.isNewToDb);
164
- // Ensure unique URLs within the batch to prevent duplicates (also checks DB for cross-page conflicts)
165
- const uniqueUrlsNewToDBMembersList = await ensureUniqueUrlsInBatch(newMembers);
166
- const uniqueUrlsMembersData = [...uniqueUrlsNewToDBMembersList, ...existingMembers];
167
-
168
- // Change Wix login emails FIRST so we know which succeeded before saving the CMS records.
169
- const toChangeWixMembersEmails = uniqueUrlsMembersData.filter(
170
- member => member.wixMemberId && member.isLoginEmailChanged
171
- );
172
- let failedLoginEmailIds = new Set();
173
- let loginEmailFailures = [];
174
- if (toChangeWixMembersEmails.length > 0) {
175
- const outcomes = await changeWixMembersEmails(toChangeWixMembersEmails);
176
- ({ failedMemberIds: failedLoginEmailIds, failures: loginEmailFailures } =
177
- summarizeLoginEmailOutcomes(outcomes));
178
- }
179
-
180
- const toSaveMembersData = uniqueUrlsMembersData.map(member => {
181
- // Transient flags only drive the sync above; never persist them.
182
- const {
183
- isLoginEmailChanged: _isLoginEmailChanged,
184
- isNewToDb: _isNewToDb,
185
- previousLoginEmail,
186
- ...restMemberData
187
- } = member;
188
- // If the Wix login-email change failed, keep the CMS login email unchanged (revert to the
189
- // previous value) so the CMS stays consistent with Wix; the failure is reported below for
190
- // manual handling.
191
- if (failedLoginEmailIds.has(member.memberId) && previousLoginEmail !== undefined) {
192
- return { ...restMemberData, email: previousLoginEmail };
193
- }
194
- return restMemberData;
195
- });
196
- const saveResult = await bulkSaveMembers(toSaveMembersData);
197
- const totalFailed = memberDataList.length - validMemberData.length;
198
- const processingTime = Date.now() - startTime;
199
-
200
- if (loginEmailFailures.length > 0) {
201
- console.error(
202
- `[loginEmailSync] ${loginEmailFailures.length} login-email change(s) failed on page ${currentPageNumber}; CMS login email left unchanged for memberIds: [${loginEmailFailures
203
- .map(failure => failure.memberId)
204
- .join(', ')}]`
205
- );
206
- }
207
-
208
- return {
209
- ...saveResult,
210
- totalProcessed: memberDataList.length,
211
- totalSaved: validMemberData.length,
212
- totalFailed: totalFailed,
213
- processingTime: processingTime,
214
- loginEmailFailedCount: loginEmailFailures.length,
215
- loginEmailFailures,
216
- };
217
- } catch (error) {
218
- throw new Error(`Bulk operation failed: ${error.message}`);
219
- }
220
- };
221
-
222
- module.exports = { bulkProcessAndSaveMemberData, ensureUniqueUrlsInBatch };
@@ -1,39 +0,0 @@
1
- const MEMBER_ACTIONS = {
2
- UPDATE: 'update',
3
- NEW: 'new',
4
- DROP: 'drop',
5
- NONE: 'none',
6
- };
7
-
8
- /**
9
- * Address visibility configuration options
10
- */
11
- const ADDRESS_VISIBILITY_OPTIONS = {
12
- ALL: 'all',
13
- NONE: 'none',
14
- };
15
-
16
- /**
17
- * Default display settings for member profiles
18
- */
19
- const DEFAULT_MEMBER_DISPLAY_SETTINGS = {
20
- showLicenseNo: true,
21
- showName: true,
22
- showBookingUrl: false,
23
- showWebsite: false,
24
- showWixUrl: true,
25
- };
26
-
27
- const PAC_ASSOCIATIONS = {
28
- ABMP: 'ABMP',
29
- ASCP: 'ASCP',
30
- ANP: 'ANP',
31
- AHP: 'AHP',
32
- };
33
-
34
- module.exports = {
35
- MEMBER_ACTIONS,
36
- ADDRESS_VISIBILITY_OPTIONS,
37
- DEFAULT_MEMBER_DISPLAY_SETTINGS,
38
- PAC_ASSOCIATIONS,
39
- };
@@ -1,5 +0,0 @@
1
- module.exports = {
2
- ...require('./sync-to-cms-methods'),
3
- ...require('./consts'), //TODO: remove it once we finish NPM movement
4
- ...require('./bulk-process-methods'),
5
- };