abmp-npm 2.0.76 → 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.
- package/package.json +8 -1
- package/pages/Home.js +27 -55
- package/pages/index.js +0 -1
- package/public/Utils/personalDetailsUtils.js +5 -1
- package/public/Utils/sharedUtils.js +32 -0
- package/public/consts.js +0 -1
- package/.claude/worktrees/fix-save-button-disable/.husky/pre-commit +0 -19
- package/.claude/worktrees/fix-save-button-disable/.prettierignore +0 -7
- package/.claude/worktrees/fix-save-button-disable/.prettierrc.json +0 -16
- package/.claude/worktrees/fix-save-button-disable/README.md +0 -1
- package/.claude/worktrees/fix-save-button-disable/backend/README-member-contact-flows.md +0 -136
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/daily-pull-execution-check.test.js +0 -124
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/ensure-unique-urls-in-batch.test.js +0 -129
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/login-email-sync.test.js +0 -49
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/transient-retry-and-bulk-lookup.test.js +0 -129
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/url-uniqueness.test.js +0 -194
- package/.claude/worktrees/fix-save-button-disable/backend/automations-methods.js +0 -13
- package/.claude/worktrees/fix-save-button-disable/backend/cms-data-methods.js +0 -251
- package/.claude/worktrees/fix-save-button-disable/backend/consts.js +0 -58
- package/.claude/worktrees/fix-save-button-disable/backend/contacts-methods.js +0 -67
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/bulk-process-methods.js +0 -222
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/consts.js +0 -39
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/index.js +0 -5
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/process-member-methods.js +0 -213
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/schedule-methods.js +0 -41
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/sync-to-cms-methods.js +0 -161
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/utils.js +0 -151
- package/.claude/worktrees/fix-save-button-disable/backend/data-hooks.js +0 -29
- package/.claude/worktrees/fix-save-button-disable/backend/dev-only-methods.js +0 -58
- package/.claude/worktrees/fix-save-button-disable/backend/elevated-modules.js +0 -18
- package/.claude/worktrees/fix-save-button-disable/backend/forms-methods.js +0 -52
- package/.claude/worktrees/fix-save-button-disable/backend/http-functions/httpFunctions.js +0 -86
- package/.claude/worktrees/fix-save-button-disable/backend/http-functions/index.js +0 -3
- package/.claude/worktrees/fix-save-button-disable/backend/http-functions/interests.js +0 -35
- package/.claude/worktrees/fix-save-button-disable/backend/index.js +0 -19
- package/.claude/worktrees/fix-save-button-disable/backend/jobs.js +0 -158
- package/.claude/worktrees/fix-save-button-disable/backend/login/generate-member-session-token.js +0 -27
- package/.claude/worktrees/fix-save-button-disable/backend/login/index.js +0 -8
- package/.claude/worktrees/fix-save-button-disable/backend/login/qa-login-methods.js +0 -54
- package/.claude/worktrees/fix-save-button-disable/backend/login/sso-methods.js +0 -159
- package/.claude/worktrees/fix-save-button-disable/backend/member-contact-orchestration.js +0 -111
- package/.claude/worktrees/fix-save-button-disable/backend/members-area-methods.js +0 -88
- package/.claude/worktrees/fix-save-button-disable/backend/members-data-methods.js +0 -789
- package/.claude/worktrees/fix-save-button-disable/backend/pac-api-methods.js +0 -54
- package/.claude/worktrees/fix-save-button-disable/backend/routers/index.js +0 -3
- package/.claude/worktrees/fix-save-button-disable/backend/routers/methods.js +0 -191
- package/.claude/worktrees/fix-save-button-disable/backend/routers/utils.js +0 -128
- package/.claude/worktrees/fix-save-button-disable/backend/search-filters-methods.js +0 -124
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/address-primary-methods.js +0 -256
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/address-visibility-methods.js +0 -160
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/consts.js +0 -34
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/daily-pull-check-methods.js +0 -78
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/email-normalize-methods.js +0 -134
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/index.js +0 -10
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/migration-methods.js +0 -46
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-configs.js +0 -251
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-helpers-methods.js +0 -433
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-process-methods.js +0 -606
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/url-migration-methods.js +0 -378
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/url-space-fix-methods.js +0 -174
- package/.claude/worktrees/fix-save-button-disable/backend/utils.js +0 -277
- package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/extract-duplicate-url-groups.js +0 -201
- package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/find-duplicate-ids.js +0 -159
- package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/find-duplicate-urls.js +0 -201
- package/.claude/worktrees/fix-save-button-disable/eslint.config.js +0 -120
- package/.claude/worktrees/fix-save-button-disable/index.js +0 -5
- package/.claude/worktrees/fix-save-button-disable/package-lock.json +0 -12160
- package/.claude/worktrees/fix-save-button-disable/package.json +0 -60
- package/.claude/worktrees/fix-save-button-disable/pages/ContactUs.js +0 -129
- package/.claude/worktrees/fix-save-button-disable/pages/Home.js +0 -779
- package/.claude/worktrees/fix-save-button-disable/pages/LearnMore.js +0 -27
- package/.claude/worktrees/fix-save-button-disable/pages/LoadingPage.js +0 -29
- package/.claude/worktrees/fix-save-button-disable/pages/Profile.js +0 -354
- package/.claude/worktrees/fix-save-button-disable/pages/QAPage.js +0 -39
- package/.claude/worktrees/fix-save-button-disable/pages/SaveAlerts.js +0 -13
- package/.claude/worktrees/fix-save-button-disable/pages/SelectBannerImages.js +0 -46
- package/.claude/worktrees/fix-save-button-disable/pages/deleteConfirm.js +0 -19
- package/.claude/worktrees/fix-save-button-disable/pages/index.js +0 -12
- package/.claude/worktrees/fix-save-button-disable/pages/personalDetails.js +0 -2486
- package/.claude/worktrees/fix-save-button-disable/public/Utils/homePage.js +0 -798
- package/.claude/worktrees/fix-save-button-disable/public/Utils/personalDetailsUtils.js +0 -25
- package/.claude/worktrees/fix-save-button-disable/public/Utils/sharedUtils.js +0 -235
- package/.claude/worktrees/fix-save-button-disable/public/consts.js +0 -113
- package/.claude/worktrees/fix-save-button-disable/public/index.js +0 -5
- package/.claude/worktrees/fix-save-button-disable/public/messages.js +0 -16
- package/.claude/worktrees/fix-save-button-disable/public/sso-auth-methods.js +0 -43
- package/.husky/pre-commit +0 -19
- package/.prettierignore +0 -7
- package/.prettierrc.json +0 -16
- package/backend/__tests__/daily-pull-execution-check.test.js +0 -124
- package/backend/__tests__/ensure-unique-urls-in-batch.test.js +0 -129
- package/backend/__tests__/login-email-sync.test.js +0 -49
- package/backend/__tests__/transient-retry-and-bulk-lookup.test.js +0 -129
- package/backend/__tests__/url-uniqueness.test.js +0 -194
- package/dev-only-scripts/extract-duplicate-url-groups.js +0 -201
- package/dev-only-scripts/find-duplicate-ids.js +0 -159
- package/dev-only-scripts/find-duplicate-urls.js +0 -201
- package/eslint.config.js +0 -120
- package/pages/contactForLocation.js +0 -28
|
@@ -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
|
-
};
|
package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/bulk-process-methods.js
DELETED
|
@@ -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
|
-
};
|
package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/process-member-methods.js
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
const { ADDRESS_STATUS_TYPES } = require('../../public/consts');
|
|
2
|
-
const { findMemberById, getMemberBySlug } = require('../members-data-methods');
|
|
3
|
-
const { isValidArray, generateGeoHash } = require('../utils');
|
|
4
|
-
|
|
5
|
-
const { MEMBER_ACTIONS, DEFAULT_MEMBER_DISPLAY_SETTINGS } = require('./consts');
|
|
6
|
-
const { incrementUrlCounter } = require('./utils');
|
|
7
|
-
const { validateCoreMemberData, containsNonEnglish, createFullName } = require('./utils');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Ensures a URL is unique by appending a counter if necessary
|
|
11
|
-
* @param {Object} options - The options object
|
|
12
|
-
* @param {string} options.url - The base URL to make unique
|
|
13
|
-
* @param {string|number} options.memberId - The member ID requesting this URL
|
|
14
|
-
* @param {string} options.fullName - The full name of the member
|
|
15
|
-
* @returns {Promise<string>} - A unique URL
|
|
16
|
-
*/
|
|
17
|
-
const ensureUniqueUrl = async ({ url, memberId, fullName }) => {
|
|
18
|
-
const baseUrl = url;
|
|
19
|
-
let uniqueUrl = url;
|
|
20
|
-
if (!url) {
|
|
21
|
-
console.log(`member with id ${memberId} has no url, creating one`);
|
|
22
|
-
const fullNameWithoutSpace = fullName?.replace(/ /g, '');
|
|
23
|
-
if (!fullNameWithoutSpace || containsNonEnglish(fullNameWithoutSpace)) {
|
|
24
|
-
console.log(
|
|
25
|
-
`member with id ${memberId} has non-english full name, will use fallback url: 'firstNameLastName'`
|
|
26
|
-
);
|
|
27
|
-
uniqueUrl = 'firstNameLastName';
|
|
28
|
-
} else {
|
|
29
|
-
uniqueUrl = fullNameWithoutSpace; //fallback if there is no full name for this user
|
|
30
|
-
}
|
|
31
|
-
console.log(
|
|
32
|
-
`member with id ${memberId} and no API provided url, will have this initial url ${uniqueUrl}`
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
if (!memberId) throw new Error('Member ID is required');
|
|
36
|
-
|
|
37
|
-
const existingMember = await getMemberBySlug({
|
|
38
|
-
slug: uniqueUrl,
|
|
39
|
-
excludeDropped: false,
|
|
40
|
-
excludeSearchedMember: true,
|
|
41
|
-
memberId,
|
|
42
|
-
normalizeSlugForComparison: true,
|
|
43
|
-
});
|
|
44
|
-
if (existingMember && existingMember.url) {
|
|
45
|
-
console.log(
|
|
46
|
-
`Found member with same url ${existingMember.url} for memberId ${memberId} and URL ${uniqueUrl}, increasing counter by 1`
|
|
47
|
-
);
|
|
48
|
-
uniqueUrl = incrementUrlCounter(existingMember.url, uniqueUrl);
|
|
49
|
-
}
|
|
50
|
-
if (uniqueUrl !== baseUrl) {
|
|
51
|
-
console.log(`URL conflict resolved: ${baseUrl} -> ${uniqueUrl} for member ${memberId}`);
|
|
52
|
-
}
|
|
53
|
-
return uniqueUrl;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Generates complete updated member data by combining existing and migration data
|
|
58
|
-
* @param {Object} options - The options object
|
|
59
|
-
* @param {Object} options.inputMemberData - Raw member data from API
|
|
60
|
-
* @param {number} options.currentPageNumber - Current page number being processed
|
|
61
|
-
* @param {Object|null} [options.existingDbMember] - Prefetched DB member (null if none exists).
|
|
62
|
-
* Pass it when processing members in bulk to avoid one DB query per member; when omitted,
|
|
63
|
-
* the member is looked up individually.
|
|
64
|
-
* @returns {Promise<Object|null>} - Complete updated member data or null if validation fails
|
|
65
|
-
*/
|
|
66
|
-
async function generateUpdatedMemberData({
|
|
67
|
-
inputMemberData,
|
|
68
|
-
currentPageNumber,
|
|
69
|
-
existingDbMember: prefetchedDbMember,
|
|
70
|
-
}) {
|
|
71
|
-
if (!validateCoreMemberData(inputMemberData)) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
'Invalid member data: memberid, email (valid string), and memberships (array) are required'
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const existingDbMember =
|
|
78
|
-
prefetchedDbMember === undefined
|
|
79
|
-
? await findMemberById(inputMemberData.memberid)
|
|
80
|
-
: prefetchedDbMember;
|
|
81
|
-
|
|
82
|
-
const updatedMemberData = await createCoreMemberData(
|
|
83
|
-
inputMemberData,
|
|
84
|
-
existingDbMember,
|
|
85
|
-
currentPageNumber
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
// If createCoreMemberData returns null due to validation failure, return null
|
|
89
|
-
if (!updatedMemberData) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Only add address data for new members
|
|
94
|
-
if (!existingDbMember && isValidArray(inputMemberData.addresses)) {
|
|
95
|
-
const normalizedAddresses = inputMemberData.addresses.map((address, index) => {
|
|
96
|
-
const key = address?.key || `address_${index}`;
|
|
97
|
-
const rawStatus = address?.addressStatus;
|
|
98
|
-
const resolvedStatus =
|
|
99
|
-
index === 0 && rawStatus === ADDRESS_STATUS_TYPES.DONT_SHOW
|
|
100
|
-
? ADDRESS_STATUS_TYPES.STATE_CITY_ZIP
|
|
101
|
-
: rawStatus || ADDRESS_STATUS_TYPES.STATE_CITY_ZIP;
|
|
102
|
-
return {
|
|
103
|
-
...address,
|
|
104
|
-
key,
|
|
105
|
-
addressStatus: resolvedStatus,
|
|
106
|
-
};
|
|
107
|
-
});
|
|
108
|
-
updatedMemberData.addresses = normalizedAddresses;
|
|
109
|
-
updatedMemberData.addressDisplayOption = [{ key: normalizedAddresses[0].key, isMain: true }];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return { ...updatedMemberData, isNewToDb: !existingDbMember };
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Helper function to get fields that should only be set for new members
|
|
116
|
-
* @param {Object} inputMemberData - Raw member data from API
|
|
117
|
-
* @param {Object} existingDbMember - Existing member data from database
|
|
118
|
-
* @returns {Promise<Object>} - Object with fields that should only be set for new members
|
|
119
|
-
*/
|
|
120
|
-
async function getNewMemberOnlyFields(inputMemberData, existingDbMember) {
|
|
121
|
-
if (existingDbMember) {
|
|
122
|
-
return {};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Only set these fields for new members
|
|
126
|
-
const sanitizedFirstName = inputMemberData.firstname?.trim() || '';
|
|
127
|
-
const sanitizedLastName = inputMemberData.lastname?.trim() || '';
|
|
128
|
-
const fullName = createFullName(sanitizedFirstName, sanitizedLastName);
|
|
129
|
-
|
|
130
|
-
const uniqueUrl = await ensureUniqueUrl({
|
|
131
|
-
url: inputMemberData.url,
|
|
132
|
-
memberId: inputMemberData.memberid,
|
|
133
|
-
fullName,
|
|
134
|
-
});
|
|
135
|
-
return {
|
|
136
|
-
memberId: inputMemberData.memberid,
|
|
137
|
-
firstName: sanitizedFirstName,
|
|
138
|
-
lastName: sanitizedLastName,
|
|
139
|
-
fullName,
|
|
140
|
-
phones: inputMemberData.phones || [],
|
|
141
|
-
optOut: false,
|
|
142
|
-
url: uniqueUrl,
|
|
143
|
-
showContactForm: true,
|
|
144
|
-
showABMP: false,
|
|
145
|
-
locHash: generateGeoHash(inputMemberData.addresses || []),
|
|
146
|
-
...DEFAULT_MEMBER_DISPLAY_SETTINGS,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Creates base member data structure with core properties
|
|
151
|
-
* @param {Object} inputMemberData - Raw member data from API
|
|
152
|
-
* @param {Object} existingDbMember - Existing member data from database
|
|
153
|
-
* @param {number} currentPageNumber - Current page number being processed
|
|
154
|
-
* @returns {Promise<Object|null>} - Structured base member data or null if required fields are missing
|
|
155
|
-
*/
|
|
156
|
-
async function createCoreMemberData(inputMemberData, existingDbMember, currentPageNumber) {
|
|
157
|
-
if (!validateCoreMemberData(inputMemberData)) {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const getMemberEmails = () => {
|
|
162
|
-
// Update both loginEmail & contactFormEmail only for new members who don't exist in DB
|
|
163
|
-
// Note: PAC API member actions are not reliable, so we need to check if the member exists in DB to know if it's a new member or not
|
|
164
|
-
const newEmail = inputMemberData.email.trim();
|
|
165
|
-
const isMemberExistInDb = Boolean(existingDbMember?._id);
|
|
166
|
-
if (!isMemberExistInDb) {
|
|
167
|
-
return {
|
|
168
|
-
email: newEmail,
|
|
169
|
-
contactFormEmail: newEmail,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
//If exists in DB then only update loginEmail for those who came in with action new based on logic below, otherwise we don't update emails
|
|
173
|
-
const isMemberReinstatedWithNewEmail =
|
|
174
|
-
inputMemberData.action === MEMBER_ACTIONS.NEW &&
|
|
175
|
-
newEmail &&
|
|
176
|
-
existingDbMember.email !== newEmail;
|
|
177
|
-
if (isMemberReinstatedWithNewEmail) {
|
|
178
|
-
// If exists in DB, and email was changed means this user was dropped before that's why it exists in DB, then only update loginEmail not contactFormEmail.
|
|
179
|
-
// previousLoginEmail lets the caller keep the old email if the Wix login-email change fails.
|
|
180
|
-
return {
|
|
181
|
-
email: newEmail,
|
|
182
|
-
isLoginEmailChanged: true,
|
|
183
|
-
previousLoginEmail: existingDbMember.email,
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
//If exists in DB but not reinstated with new email, then don't update emails
|
|
187
|
-
return {};
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const newMemberFields = await getNewMemberOnlyFields(inputMemberData, existingDbMember);
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
...existingDbMember,
|
|
194
|
-
|
|
195
|
-
// Always update these core fields from API
|
|
196
|
-
action: inputMemberData.action,
|
|
197
|
-
licenses: inputMemberData.licenses || [],
|
|
198
|
-
memberships: inputMemberData.memberships,
|
|
199
|
-
pageNumber: currentPageNumber,
|
|
200
|
-
isVisible: inputMemberData.action !== MEMBER_ACTIONS.DROP,
|
|
201
|
-
|
|
202
|
-
// Handle Member emails
|
|
203
|
-
...getMemberEmails(),
|
|
204
|
-
|
|
205
|
-
// Handle fields that should only be set for new members
|
|
206
|
-
...newMemberFields,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
module.exports = {
|
|
211
|
-
generateUpdatedMemberData,
|
|
212
|
-
ensureUniqueUrl,
|
|
213
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
const { taskManager } = require('psdev-task-manager');
|
|
2
|
-
|
|
3
|
-
const { TASKS_NAMES } = require('../tasks/consts');
|
|
4
|
-
|
|
5
|
-
const { MEMBER_ACTIONS } = require('./consts');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Builds the per-action daily pull tasks. Single source of truth for what a
|
|
9
|
-
* "daily pull" schedules, shared by the cron job and the execution-check
|
|
10
|
-
* fallback so both produce identical tasks (same names, same environment flags).
|
|
11
|
-
* @param {Object} [options]
|
|
12
|
-
* @param {string} [options.backupDate] - Optional backup date (YYYY-MM-DD) to pull from the backup endpoint
|
|
13
|
-
* @param {boolean} [options.isTestEnvironment=false] - Whether to pull from the test PAC API
|
|
14
|
-
* @param {boolean} [options.includeNone=false] - Whether to also sync the NONE action
|
|
15
|
-
* @returns {Array<Object>} - Task definitions for taskManager().scheduleInBulk
|
|
16
|
-
*/
|
|
17
|
-
const buildDailyPullTasks = ({ backupDate, isTestEnvironment, includeNone } = {}) => {
|
|
18
|
-
const actionsToSync = includeNone
|
|
19
|
-
? Object.values(MEMBER_ACTIONS)
|
|
20
|
-
: Object.values(MEMBER_ACTIONS).filter(action => action !== MEMBER_ACTIONS.NONE);
|
|
21
|
-
return actionsToSync.map(action => ({
|
|
22
|
-
name: TASKS_NAMES.ScheduleMembersDataPerAction,
|
|
23
|
-
data: {
|
|
24
|
-
action,
|
|
25
|
-
...(backupDate ? { backupDate } : {}),
|
|
26
|
-
...(isTestEnvironment ? { isTestEnvironment } : {}),
|
|
27
|
-
...(includeNone ? { includeNone } : {}),
|
|
28
|
-
},
|
|
29
|
-
type: 'scheduled',
|
|
30
|
-
}));
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Schedules the daily pull (one ScheduleMembersDataPerAction task per action).
|
|
35
|
-
* @param {Object} [options] - Same options as buildDailyPullTasks
|
|
36
|
-
* @returns {Promise<any>}
|
|
37
|
-
*/
|
|
38
|
-
const scheduleDailyPullTasks = options =>
|
|
39
|
-
taskManager().scheduleInBulk(buildDailyPullTasks(options));
|
|
40
|
-
|
|
41
|
-
module.exports = { buildDailyPullTasks, scheduleDailyPullTasks };
|