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.
- package/package.json +8 -1
- package/.claude/skills/wix-data-query/SKILL.md +0 -167
- package/.claude/skills/wix-data-query/references/backup-archive.md +0 -136
- package/.claude/skills/wix-data-query/references/members-data-latest.md +0 -125
- package/.claude/skills/wix-data-query/references/recipes.md +0 -264
- 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__/map-link.test.js +0 -115
- package/backend/__tests__/transient-retry-and-bulk-lookup.test.js +0 -129
- package/backend/__tests__/url-uniqueness.test.js +0 -194
- package/backend/__tests__/url-validation.test.js +0 -69
- 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/.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 };
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
const { taskManager } = require('psdev-task-manager');
|
|
2
|
-
|
|
3
|
-
const { CONFIG_KEYS } = require('../consts');
|
|
4
|
-
const { fetchPACMembers } = require('../pac-api-methods');
|
|
5
|
-
const { TASKS_NAMES } = require('../tasks/consts');
|
|
6
|
-
const { getSiteConfigs } = require('../utils');
|
|
7
|
-
|
|
8
|
-
const { bulkProcessAndSaveMemberData } = require('./bulk-process-methods');
|
|
9
|
-
const { MEMBER_ACTIONS } = require('./consts');
|
|
10
|
-
const { isUpdatedMember, isSiteAssociatedMember } = require('./utils');
|
|
11
|
-
|
|
12
|
-
const filterLicensesByAssociation = (member, siteAssociation) => {
|
|
13
|
-
if (!member?.licenses || !Array.isArray(member.licenses)) {
|
|
14
|
-
return member;
|
|
15
|
-
}
|
|
16
|
-
const filteredLicenses = member.licenses.filter(license => {
|
|
17
|
-
if (!license || !license.association) {
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
return license.association === siteAssociation;
|
|
21
|
-
});
|
|
22
|
-
return {
|
|
23
|
-
...member,
|
|
24
|
-
licenses: filteredLicenses,
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
async function syncMembersDataPerAction(taskData) {
|
|
29
|
-
const { action, backupDate, isTestEnvironment, includeNone } = taskData;
|
|
30
|
-
try {
|
|
31
|
-
const firstPageResponse = await fetchPACMembers({
|
|
32
|
-
page: 1,
|
|
33
|
-
action,
|
|
34
|
-
backupDate,
|
|
35
|
-
isTestEnvironment,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
!firstPageResponse ||
|
|
40
|
-
!firstPageResponse.results ||
|
|
41
|
-
firstPageResponse.results.length === 0
|
|
42
|
-
) {
|
|
43
|
-
return {
|
|
44
|
-
success: true,
|
|
45
|
-
totalPagesProcessed: 0,
|
|
46
|
-
lastPageProcessed: 0,
|
|
47
|
-
completedAt: new Date().toISOString(),
|
|
48
|
-
message: 'No data found',
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Calculate total pages from API response
|
|
53
|
-
const totalResults = firstPageResponse.total_results || 0;
|
|
54
|
-
const perPage = firstPageResponse.results.length;
|
|
55
|
-
const totalPages = firstPageResponse.total_pages || 0;
|
|
56
|
-
|
|
57
|
-
// Cap at 1000 pages as safety measure
|
|
58
|
-
const pagesToProcess = Math.min(totalPages, 1000);
|
|
59
|
-
|
|
60
|
-
console.log(
|
|
61
|
-
`Scheduling ${pagesToProcess} pages for processing (${totalResults} total records, ${perPage} per page)`
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
// Schedule tasks for all pages at once
|
|
65
|
-
const toScheduleTasks = Array.from(
|
|
66
|
-
{ length: pagesToProcess },
|
|
67
|
-
(_, i) => i + 1 // API expects page number to start from 1
|
|
68
|
-
).map(pageNumber => ({
|
|
69
|
-
name: TASKS_NAMES.SyncMembers,
|
|
70
|
-
data: {
|
|
71
|
-
pageNumber,
|
|
72
|
-
action,
|
|
73
|
-
...(backupDate ? { backupDate } : {}),
|
|
74
|
-
...(isTestEnvironment ? { isTestEnvironment } : {}),
|
|
75
|
-
...(includeNone ? { includeNone } : {}),
|
|
76
|
-
},
|
|
77
|
-
type: 'scheduled',
|
|
78
|
-
}));
|
|
79
|
-
|
|
80
|
-
// Wait for all scheduling to complete
|
|
81
|
-
await taskManager().scheduleInBulk(toScheduleTasks);
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
success: true,
|
|
85
|
-
totalPagesProcessed: pagesToProcess,
|
|
86
|
-
lastPageProcessed: pagesToProcess,
|
|
87
|
-
totalRecords: totalResults,
|
|
88
|
-
recordsPerPage: perPage,
|
|
89
|
-
completedAt: new Date().toISOString(),
|
|
90
|
-
};
|
|
91
|
-
} catch (error) {
|
|
92
|
-
throw new Error(`Synchronization failed: ${error.message}`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Synchronizes a single page of member data
|
|
98
|
-
* @param {Object} taskObject - Task object containing page data
|
|
99
|
-
* @returns {Promise<Object>} - Page synchronization result
|
|
100
|
-
*/
|
|
101
|
-
async function synchronizeSinglePage(taskObject) {
|
|
102
|
-
const { pageNumber, action, backupDate, isTestEnvironment, includeNone } = taskObject.data;
|
|
103
|
-
try {
|
|
104
|
-
const [siteAssociation, memberDataResponse] = await Promise.all([
|
|
105
|
-
getSiteConfigs(CONFIG_KEYS.SITE_ASSOCIATION),
|
|
106
|
-
fetchPACMembers({
|
|
107
|
-
page: pageNumber,
|
|
108
|
-
action,
|
|
109
|
-
backupDate,
|
|
110
|
-
isTestEnvironment,
|
|
111
|
-
}),
|
|
112
|
-
]);
|
|
113
|
-
if (
|
|
114
|
-
!memberDataResponse ||
|
|
115
|
-
!memberDataResponse.results ||
|
|
116
|
-
memberDataResponse.results.length === 0
|
|
117
|
-
) {
|
|
118
|
-
throw new Error(`No data found for page ${pageNumber}`);
|
|
119
|
-
}
|
|
120
|
-
const toSyncMembers = memberDataResponse.results.filter(member => {
|
|
121
|
-
if (!isSiteAssociatedMember(member, siteAssociation)) {
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
if (action === MEMBER_ACTIONS.NONE && includeNone) {
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
return isUpdatedMember(member);
|
|
128
|
-
});
|
|
129
|
-
const toSyncMembersWithFilteredLicenses = toSyncMembers.map(member =>
|
|
130
|
-
filterLicensesByAssociation(member, siteAssociation)
|
|
131
|
-
);
|
|
132
|
-
if (toSyncMembers.length === 0) {
|
|
133
|
-
return {
|
|
134
|
-
success: true,
|
|
135
|
-
pageNumber,
|
|
136
|
-
totalPageSize: memberDataResponse.results.length,
|
|
137
|
-
filteredPageSize: toSyncMembers.length,
|
|
138
|
-
message: `No to be updated, or members of association: '${siteAssociation}' found`,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
const result = await bulkProcessAndSaveMemberData({
|
|
142
|
-
memberDataList: toSyncMembersWithFilteredLicenses,
|
|
143
|
-
currentPageNumber: pageNumber,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
success: true,
|
|
148
|
-
pageNumber,
|
|
149
|
-
totalPageSize: memberDataResponse.results.length,
|
|
150
|
-
filteredPageSize: toSyncMembers.length,
|
|
151
|
-
...result,
|
|
152
|
-
};
|
|
153
|
-
} catch (error) {
|
|
154
|
-
throw new Error(`Page ${pageNumber} synchronization failed: ${error.message}`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
module.exports = {
|
|
159
|
-
syncMembersDataPerAction,
|
|
160
|
-
synchronizeSinglePage,
|
|
161
|
-
};
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
const { LOGIN_EMAIL_SYNC_STATUS } = require('../consts');
|
|
2
|
-
const { updateWixMemberLoginEmail } = require('../members-area-methods');
|
|
3
|
-
const { extractUrlCounter } = require('../utils');
|
|
4
|
-
|
|
5
|
-
const { MEMBER_ACTIONS } = require('./consts');
|
|
6
|
-
|
|
7
|
-
const isUpdatedMember = member => member.action !== MEMBER_ACTIONS.NONE;
|
|
8
|
-
const isSiteAssociatedMember = (member, siteAssociation) =>
|
|
9
|
-
member.memberships.some(membership => membership.association === siteAssociation);
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Attempts to change Wix login emails for the given members and returns one structured
|
|
13
|
-
* outcome per member (see updateWixMemberLoginEmail). Never throws for an individual member.
|
|
14
|
-
* @param {Array} toChangeWixMembersEmails
|
|
15
|
-
* @returns {Promise<Array>} outcomes
|
|
16
|
-
*/
|
|
17
|
-
const changeWixMembersEmails = async toChangeWixMembersEmails => {
|
|
18
|
-
console.log(
|
|
19
|
-
`[loginEmailSync] changing login emails for ${toChangeWixMembersEmails.length} members with ids: [${toChangeWixMembersEmails.map(member => member.memberId).join(', ')}]`
|
|
20
|
-
);
|
|
21
|
-
const outcomes = await Promise.all(
|
|
22
|
-
toChangeWixMembersEmails.map(member => updateWixMemberLoginEmail(member))
|
|
23
|
-
);
|
|
24
|
-
const summary = outcomes.reduce((acc, outcome) => {
|
|
25
|
-
acc[outcome.status] = (acc[outcome.status] || 0) + 1;
|
|
26
|
-
return acc;
|
|
27
|
-
}, {});
|
|
28
|
-
console.log(`[loginEmailSync] results summary: ${JSON.stringify(summary)}`);
|
|
29
|
-
return outcomes;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Summarizes login-email sync outcomes for manual handling via the task result.
|
|
34
|
-
* Pure function so it can be unit-tested without Wix.
|
|
35
|
-
* @param {Array} outcomes - from updateWixMemberLoginEmail / changeWixMembersEmails
|
|
36
|
-
* @returns {{ failedMemberIds: Set, failures: Array }} set of failed memberIds (whose CMS login
|
|
37
|
-
* email must be left unchanged) and the failure records to surface in the task result
|
|
38
|
-
*/
|
|
39
|
-
const summarizeLoginEmailOutcomes = (outcomes = []) => {
|
|
40
|
-
const failedMemberIds = new Set();
|
|
41
|
-
const failures = [];
|
|
42
|
-
outcomes.forEach(outcome => {
|
|
43
|
-
if (outcome.status === LOGIN_EMAIL_SYNC_STATUS.FAILED) {
|
|
44
|
-
failedMemberIds.add(outcome.memberId);
|
|
45
|
-
failures.push({
|
|
46
|
-
memberId: outcome.memberId,
|
|
47
|
-
wixMemberId: outcome.wixMemberId,
|
|
48
|
-
desiredEmail: outcome.desiredEmail,
|
|
49
|
-
error: outcome.error || 'unknown error',
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
return { failedMemberIds, failures };
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const extractBaseUrl = url => {
|
|
57
|
-
if (!url) return url;
|
|
58
|
-
const lastCounter = extractUrlCounter(url);
|
|
59
|
-
if (lastCounter > 0) {
|
|
60
|
-
// Remove the numeric counter to get the base URL
|
|
61
|
-
return url.split('-').slice(0, -1).join('-');
|
|
62
|
-
}
|
|
63
|
-
// No counter found, return the URL as-is
|
|
64
|
-
return url;
|
|
65
|
-
};
|
|
66
|
-
const incrementUrlCounter = (existingUrl, baseUrl) => {
|
|
67
|
-
if (!existingUrl || !baseUrl) {
|
|
68
|
-
return baseUrl;
|
|
69
|
-
}
|
|
70
|
-
// Normalize for comparison (case-insensitive)
|
|
71
|
-
const normalizedExisting = existingUrl.toLowerCase();
|
|
72
|
-
const normalizedBase = baseUrl.toLowerCase();
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
normalizedExisting === normalizedBase ||
|
|
76
|
-
normalizedExisting.startsWith(`${normalizedBase}-`)
|
|
77
|
-
) {
|
|
78
|
-
console.log(
|
|
79
|
-
`Found member with same url ${existingUrl} for baseUrl ${baseUrl}, increasing counter by 1`
|
|
80
|
-
);
|
|
81
|
-
const lastCounter = Math.max(0, extractUrlCounter(existingUrl));
|
|
82
|
-
return `${baseUrl}-${lastCounter + 1}`;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// No conflict, return baseUrl with counter 1 to be safe
|
|
86
|
-
return `${baseUrl}-1`;
|
|
87
|
-
};
|
|
88
|
-
/**
|
|
89
|
-
* Validates core member data requirements
|
|
90
|
-
* @param {Object} inputMemberData - Raw member data from API to validate
|
|
91
|
-
* @returns {boolean} - True if all required fields are valid, false otherwise
|
|
92
|
-
*/
|
|
93
|
-
const validateCoreMemberData = inputMemberData => {
|
|
94
|
-
// Check memberid
|
|
95
|
-
if (!inputMemberData?.memberid) {
|
|
96
|
-
console.warn('validateCoreMemberData: Missing required field - memberid is mandatory');
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Check email
|
|
101
|
-
if (
|
|
102
|
-
!inputMemberData?.email ||
|
|
103
|
-
typeof inputMemberData.email !== 'string' ||
|
|
104
|
-
!inputMemberData.email.trim()
|
|
105
|
-
) {
|
|
106
|
-
console.warn(
|
|
107
|
-
'validateCoreMemberData: Missing required field - email (valid string) is mandatory'
|
|
108
|
-
);
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Check memberships
|
|
113
|
-
if (
|
|
114
|
-
!inputMemberData?.memberships ||
|
|
115
|
-
!Array.isArray(inputMemberData.memberships) ||
|
|
116
|
-
inputMemberData.memberships.length === 0
|
|
117
|
-
) {
|
|
118
|
-
console.warn(
|
|
119
|
-
'validateCoreMemberData: Missing required field - memberships (non-empty array) is mandatory'
|
|
120
|
-
);
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return true;
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const containsNonEnglish = str => /[^a-zA-Z0-9-]/.test(str); // if it contains any non-english characters or invalid URL chars, test1 is allowed, hyphens are allowed
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Creates a full name from first and last name components
|
|
131
|
-
* @param {string} firstName - First name
|
|
132
|
-
* @param {string} lastName - Last name
|
|
133
|
-
* @returns {string} - Combined full name
|
|
134
|
-
*/
|
|
135
|
-
const createFullName = (firstName, lastName) => {
|
|
136
|
-
const trimmedFirst = firstName?.trim() || '';
|
|
137
|
-
const trimmedLast = lastName?.trim() || '';
|
|
138
|
-
return `${trimmedFirst} ${trimmedLast}`.trim();
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
module.exports = {
|
|
142
|
-
isUpdatedMember,
|
|
143
|
-
isSiteAssociatedMember,
|
|
144
|
-
changeWixMembersEmails,
|
|
145
|
-
summarizeLoginEmailOutcomes,
|
|
146
|
-
validateCoreMemberData,
|
|
147
|
-
containsNonEnglish,
|
|
148
|
-
createFullName,
|
|
149
|
-
incrementUrlCounter,
|
|
150
|
-
extractBaseUrl,
|
|
151
|
-
};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
const { taskManager, TASK_TYPE } = require('psdev-task-manager');
|
|
2
|
-
|
|
3
|
-
const { COMPILED_FILTERS_FIELDS } = require('./consts');
|
|
4
|
-
const { TASKS_NAMES } = require('./tasks/consts');
|
|
5
|
-
|
|
6
|
-
const scheduleCompileFiltersTask = field =>
|
|
7
|
-
taskManager().schedule({
|
|
8
|
-
name: TASKS_NAMES.CompileFiltersOptions,
|
|
9
|
-
data: { field },
|
|
10
|
-
type: TASK_TYPE.SCHEDULED,
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
function scheduleCompileInterestsTask() {
|
|
14
|
-
return scheduleCompileFiltersTask(COMPILED_FILTERS_FIELDS.COMPILED_AREAS_OF_PRACTICES);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function scheduleCompileStatesTask() {
|
|
18
|
-
return scheduleCompileFiltersTask(COMPILED_FILTERS_FIELDS.COMPILED_STATE_LIST);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function scheduleCompileCitiesTask() {
|
|
22
|
-
return scheduleCompileFiltersTask(COMPILED_FILTERS_FIELDS.COMPILED_STATE_CITY_MAP);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
module.exports = {
|
|
26
|
-
scheduleCompileInterestsTask,
|
|
27
|
-
scheduleCompileStatesTask,
|
|
28
|
-
scheduleCompileCitiesTask,
|
|
29
|
-
};
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
const { COLLECTIONS } = require('../public/consts');
|
|
2
|
-
|
|
3
|
-
const { ensureUniqueUrlsInBatch } = require('./daily-pull/bulk-process-methods');
|
|
4
|
-
const { ensureUniqueUrl } = require('./daily-pull/process-member-methods');
|
|
5
|
-
const { wixData } = require('./elevated-modules');
|
|
6
|
-
const { bulkSaveMembers } = require('./members-data-methods');
|
|
7
|
-
const { queryAllItems } = require('./utils');
|
|
8
|
-
|
|
9
|
-
async function deduplicateURls(collectionName, duplicateUrlsList) {
|
|
10
|
-
const query = await wixData.query(collectionName).hasSome('url', duplicateUrlsList).limit(1000);
|
|
11
|
-
const membersWithSameUrl = await queryAllItems(query);
|
|
12
|
-
|
|
13
|
-
console.log({ membersWithSameUrl });
|
|
14
|
-
const membersWithUniqueUrls = await ensureUniqueUrlsInBatch(membersWithSameUrl);
|
|
15
|
-
console.log({ membersWithUniqueUrls });
|
|
16
|
-
const deduplicatedUrls = membersWithUniqueUrls.map(m => m.url);
|
|
17
|
-
console.log({ deduplicatedUrls });
|
|
18
|
-
return await bulkSaveMembers(membersWithUniqueUrls, collectionName);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function copyContactIdToWixMemberId() {
|
|
22
|
-
const query = wixData.query(COLLECTIONS.MEMBERS_DATA).isNotEmpty('contactId');
|
|
23
|
-
const members = await queryAllItems(query);
|
|
24
|
-
const updatedMembers = members.map(member => ({
|
|
25
|
-
...member,
|
|
26
|
-
wixMemberId: member.contactId,
|
|
27
|
-
}));
|
|
28
|
-
return await bulkSaveMembers(updatedMembers, COLLECTIONS.MEMBERS_DATA);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function createMissingUrls() {
|
|
32
|
-
const query = wixData.query(COLLECTIONS.MEMBERS_DATA).isEmpty('url');
|
|
33
|
-
const members = await queryAllItems(query);
|
|
34
|
-
console.log(
|
|
35
|
-
'membersWithoutUrls info',
|
|
36
|
-
JSON.stringify({
|
|
37
|
-
count: members.length,
|
|
38
|
-
membersIds: members.map(m => m.memberId),
|
|
39
|
-
})
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
const membersWithGeneratedUrlsPromises = members.map(async member => ({
|
|
43
|
-
...member,
|
|
44
|
-
url: await ensureUniqueUrl({
|
|
45
|
-
url: member.url,
|
|
46
|
-
memberId: member.memberId,
|
|
47
|
-
fullName: member.fullName,
|
|
48
|
-
}),
|
|
49
|
-
}));
|
|
50
|
-
const membersWithGeneratedUrls = await Promise.all(membersWithGeneratedUrlsPromises);
|
|
51
|
-
//recheck urls in same batch to avoid duplicates
|
|
52
|
-
const uniqueUrlsUpdatedMembers = await ensureUniqueUrlsInBatch(membersWithGeneratedUrls);
|
|
53
|
-
const urls = uniqueUrlsUpdatedMembers.map(m => m.url).filter(Boolean);
|
|
54
|
-
console.log('unique urls', [...new Set(urls)]);
|
|
55
|
-
return await bulkSaveMembers(uniqueUrlsUpdatedMembers, COLLECTIONS.MEMBERS_DATA);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
module.exports = { deduplicateURls, copyContactIdToWixMemberId, createMissingUrls };
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
const { items } = require('@wix/data');
|
|
2
|
-
const { auth } = require('@wix/essentials');
|
|
3
|
-
|
|
4
|
-
// @wix/data does not support suppressAuth currently, so we need to elevate it
|
|
5
|
-
const wixData = {
|
|
6
|
-
insert: auth.elevate(items.insert),
|
|
7
|
-
update: auth.elevate(items.update),
|
|
8
|
-
bulkInsert: auth.elevate(items.bulkInsert),
|
|
9
|
-
query: auth.elevate(items.query),
|
|
10
|
-
save: auth.elevate(items.save),
|
|
11
|
-
remove: auth.elevate(items.remove),
|
|
12
|
-
get: auth.elevate(items.get),
|
|
13
|
-
truncate: auth.elevate(items.truncate),
|
|
14
|
-
bulkSave: auth.elevate(items.bulkSave),
|
|
15
|
-
search: auth.elevate(items.search),
|
|
16
|
-
//TODO: add other methods here as needed
|
|
17
|
-
};
|
|
18
|
-
module.exports = { wixData };
|