abmp-npm 1.1.51 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/cms-data-methods.js +2 -6
- package/backend/consts.js +2 -10
- package/backend/index.js +0 -6
- package/backend/members-area-methods.js +1 -48
- package/backend/members-data-methods.js +0 -111
- package/backend/utils.js +1 -54
- package/package.json +2 -4
- package/pages/PersonalDetailsForm.js +1989 -0
- package/pages/index.js +1 -0
- package/backend/daily-pull/bulk-process-methods.js +0 -65
- package/backend/daily-pull/consts.js +0 -34
- package/backend/daily-pull/index.js +0 -4
- package/backend/daily-pull/process-member-methods.js +0 -290
- package/backend/daily-pull/sync-to-cms-methods.js +0 -114
- package/backend/daily-pull/utils.js +0 -78
- package/backend/jobs.js +0 -30
- package/backend/pac-api-methods.js +0 -35
- package/backend/tasks.js +0 -37
package/pages/index.js
CHANGED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
const { bulkSaveMembers } = require('../members-data-methods');
|
|
2
|
-
|
|
3
|
-
const { generateUpdatedMemberData } = require('./process-member-methods');
|
|
4
|
-
const { changeWixMembersEmails } = require('./utils');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Processes and saves multiple member records in bulk
|
|
8
|
-
* @param {Array} memberDataList - Array of member data from API
|
|
9
|
-
* @param {number} currentPageNumber - Current page number being processed
|
|
10
|
-
* @returns {Promise<Object>} - Bulk save operation result with statistics
|
|
11
|
-
*/
|
|
12
|
-
const bulkProcessAndSaveMemberData = async (memberDataList, currentPageNumber) => {
|
|
13
|
-
if (!Array.isArray(memberDataList) || memberDataList.length === 0) {
|
|
14
|
-
throw new Error('Invalid member data list provided');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const startTime = Date.now();
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
const processedMemberDataPromises = memberDataList.map(memberData =>
|
|
21
|
-
generateUpdatedMemberData(memberData, currentPageNumber)
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
const processedMemberDataList = await Promise.all(processedMemberDataPromises);
|
|
25
|
-
const validMemberData = processedMemberDataList.filter(
|
|
26
|
-
data => data !== null && data !== undefined
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
if (validMemberData.length === 0) {
|
|
30
|
-
return {
|
|
31
|
-
totalProcessed: memberDataList.length,
|
|
32
|
-
totalSaved: 0,
|
|
33
|
-
totalFailed: memberDataList.length,
|
|
34
|
-
processingTime: Date.now() - startTime,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
const toChangeWixMembersEmails = [];
|
|
38
|
-
const toSaveMembersData = validMemberData.map(member => {
|
|
39
|
-
const { isLoginEmailChanged, ...restMemberData } = member;
|
|
40
|
-
if (member.contactId && isLoginEmailChanged) {
|
|
41
|
-
toChangeWixMembersEmails.push(member);
|
|
42
|
-
}
|
|
43
|
-
return restMemberData; //we don't want to store the isLoginEmailChanged in the database, it's just a flag to know if we need to change the login email in Members area
|
|
44
|
-
});
|
|
45
|
-
const saveResult = await bulkSaveMembers(toSaveMembersData);
|
|
46
|
-
// Change login emails for users who was dropped but now are back to system as new members and have different loginEmail for users with action DROP
|
|
47
|
-
if (toChangeWixMembersEmails.length > 0) {
|
|
48
|
-
await changeWixMembersEmails(toChangeWixMembersEmails);
|
|
49
|
-
}
|
|
50
|
-
const totalFailed = memberDataList.length - validMemberData.length;
|
|
51
|
-
const processingTime = Date.now() - startTime;
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
...saveResult,
|
|
55
|
-
totalProcessed: memberDataList.length,
|
|
56
|
-
totalSaved: validMemberData.length,
|
|
57
|
-
totalFailed: totalFailed,
|
|
58
|
-
processingTime: processingTime,
|
|
59
|
-
};
|
|
60
|
-
} catch (error) {
|
|
61
|
-
throw new Error(`Bulk operation failed: ${error.message}`);
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
module.exports = { bulkProcessAndSaveMemberData };
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
const PAC_API_URL = 'https://members.abmp.com/eweb/api/Wix';
|
|
2
|
-
|
|
3
|
-
const MEMBER_ACTIONS = {
|
|
4
|
-
UPDATE: 'update',
|
|
5
|
-
NEW: 'new',
|
|
6
|
-
DROP: 'drop',
|
|
7
|
-
NONE: 'none',
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Address visibility configuration options
|
|
12
|
-
*/
|
|
13
|
-
const ADDRESS_VISIBILITY_OPTIONS = {
|
|
14
|
-
ALL: 'all',
|
|
15
|
-
NONE: 'none',
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Default display settings for member profiles
|
|
20
|
-
*/
|
|
21
|
-
const DEFAULT_MEMBER_DISPLAY_SETTINGS = {
|
|
22
|
-
showLicenseNo: true,
|
|
23
|
-
showName: true,
|
|
24
|
-
showBookingUrl: false,
|
|
25
|
-
showWebsite: false,
|
|
26
|
-
showWixUrl: true,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
module.exports = {
|
|
30
|
-
MEMBER_ACTIONS,
|
|
31
|
-
ADDRESS_VISIBILITY_OPTIONS,
|
|
32
|
-
DEFAULT_MEMBER_DISPLAY_SETTINGS,
|
|
33
|
-
PAC_API_URL,
|
|
34
|
-
};
|
|
@@ -1,290 +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 {
|
|
6
|
-
MEMBER_ACTIONS,
|
|
7
|
-
ADDRESS_VISIBILITY_OPTIONS,
|
|
8
|
-
DEFAULT_MEMBER_DISPLAY_SETTINGS,
|
|
9
|
-
} = require('./consts');
|
|
10
|
-
const { validateCoreMemberData, containsNonEnglish, createFullName } = require('./utils');
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Ensures a URL is unique by appending a counter if necessary
|
|
14
|
-
* @param {Object} options - The options object
|
|
15
|
-
* @param {string} options.url - The base URL to make unique
|
|
16
|
-
* @param {string|number} options.memberId - The member ID requesting this URL
|
|
17
|
-
* @param {string} options.fullName - The full name of the member
|
|
18
|
-
* @returns {Promise<string>} - A unique URL
|
|
19
|
-
*/
|
|
20
|
-
const ensureUniqueUrl = async ({ url, memberId, fullName }) => {
|
|
21
|
-
const baseUrl = url;
|
|
22
|
-
let uniqueUrl = url;
|
|
23
|
-
if (!url) {
|
|
24
|
-
console.log(`member with id ${memberId} has no url, creating one`);
|
|
25
|
-
const fullNameWithoutSpace = fullName?.replace(/ /g, '');
|
|
26
|
-
if (!fullNameWithoutSpace || containsNonEnglish(fullNameWithoutSpace)) {
|
|
27
|
-
console.log(
|
|
28
|
-
`member with id ${memberId} has non-english full name, will use fallback url: 'firstNameLastName'`
|
|
29
|
-
);
|
|
30
|
-
uniqueUrl = 'firstNameLastName';
|
|
31
|
-
} else {
|
|
32
|
-
uniqueUrl = fullNameWithoutSpace; //fallback if there is no full name for this user
|
|
33
|
-
}
|
|
34
|
-
console.log(
|
|
35
|
-
`member with id ${memberId} and no API provided url, will have this initial url ${uniqueUrl}`
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
if (!memberId) throw new Error('Member ID is required');
|
|
39
|
-
|
|
40
|
-
const existingMember = await getMemberBySlug({
|
|
41
|
-
slug: uniqueUrl,
|
|
42
|
-
excludeDropped: true,
|
|
43
|
-
excludeSearchedMember: true,
|
|
44
|
-
memberId,
|
|
45
|
-
queryAllMatches: true,
|
|
46
|
-
});
|
|
47
|
-
if (existingMember && existingMember.url) {
|
|
48
|
-
console.log(
|
|
49
|
-
`Found member with same url ${existingMember.url} for memberId ${memberId} and URL ${uniqueUrl}, increasing counter by 1`
|
|
50
|
-
);
|
|
51
|
-
const lastSegment = existingMember.url.split('-').pop() || '0';
|
|
52
|
-
const lastCounter = parseInt(lastSegment, 10) || 0;
|
|
53
|
-
uniqueUrl = `${uniqueUrl}-${lastCounter + 1}`;
|
|
54
|
-
}
|
|
55
|
-
if (uniqueUrl !== baseUrl) {
|
|
56
|
-
console.log(`URL conflict resolved: ${baseUrl} -> ${uniqueUrl} for member ${memberId}`);
|
|
57
|
-
}
|
|
58
|
-
return uniqueUrl;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Generates complete updated member data by combining existing and migration data
|
|
63
|
-
* @param {Object} inputMemberData - Raw member data from API
|
|
64
|
-
* @param {number} currentPageNumber - Current page number being processed
|
|
65
|
-
* @returns {Promise<Object|null>} - Complete updated member data or null if validation fails
|
|
66
|
-
*/
|
|
67
|
-
async function generateUpdatedMemberData(inputMemberData, currentPageNumber) {
|
|
68
|
-
if (!validateCoreMemberData(inputMemberData)) {
|
|
69
|
-
throw new Error(
|
|
70
|
-
'Invalid member data: memberid, email (valid string), and memberships (array) are required'
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const existingDbMember = await findMemberById(inputMemberData.memberid);
|
|
75
|
-
|
|
76
|
-
const updatedMemberData = await createCoreMemberData(
|
|
77
|
-
inputMemberData,
|
|
78
|
-
existingDbMember,
|
|
79
|
-
currentPageNumber
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
// If createCoreMemberData returns null due to validation failure, return null
|
|
83
|
-
if (!updatedMemberData) {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Only enrich with migration and address data for new members
|
|
88
|
-
if (!existingDbMember) {
|
|
89
|
-
enrichWithMigrationData(updatedMemberData, inputMemberData.migrationData);
|
|
90
|
-
|
|
91
|
-
enrichWithAddressData(
|
|
92
|
-
updatedMemberData,
|
|
93
|
-
inputMemberData.addresses,
|
|
94
|
-
inputMemberData.migrationData?.addressinfo
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return updatedMemberData;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Processes and adds address data with proper status
|
|
103
|
-
* @param {Object} memberDataToUpdate - Member data object to enhance
|
|
104
|
-
* @param {Array} addressesList - Array of address objects
|
|
105
|
-
* @param {Object} addressDisplayInfo - Address visibility configuration
|
|
106
|
-
*/
|
|
107
|
-
function enrichWithAddressData(memberDataToUpdate, addressesList, addressDisplayInfo) {
|
|
108
|
-
if (isValidArray(addressesList)) {
|
|
109
|
-
memberDataToUpdate.addresses = processAddressesWithStatus(addressesList, addressDisplayInfo);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Processes multiple addresses with their display statuses
|
|
115
|
-
* @param {Array} addressesList - Array of address objects
|
|
116
|
-
* @param {Object} displayConfiguration - Address display configuration
|
|
117
|
-
* @returns {Array} - Processed addresses with status information
|
|
118
|
-
*/
|
|
119
|
-
function processAddressesWithStatus(addressesList, displayConfiguration = {}) {
|
|
120
|
-
if (!isValidArray(addressesList)) {
|
|
121
|
-
return [];
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return addressesList.map(address => {
|
|
125
|
-
const displayStatus = displayConfiguration[address.key]
|
|
126
|
-
? determineAddressDisplayStatus(displayConfiguration[address.key])
|
|
127
|
-
: ADDRESS_STATUS_TYPES.STATE_CITY_ZIP;
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
...address,
|
|
131
|
-
addressStatus: displayStatus,
|
|
132
|
-
};
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Determines address display status based on visibility settings
|
|
138
|
-
* @param {string} visibilityValue - The address visibility value from migration data
|
|
139
|
-
* @returns {string} - The corresponding address status
|
|
140
|
-
*/
|
|
141
|
-
function determineAddressDisplayStatus(visibilityValue) {
|
|
142
|
-
if (!visibilityValue) {
|
|
143
|
-
return ADDRESS_STATUS_TYPES.STATE_CITY_ZIP;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const normalizedValue = visibilityValue.trim().toLowerCase();
|
|
147
|
-
|
|
148
|
-
switch (normalizedValue) {
|
|
149
|
-
case ADDRESS_VISIBILITY_OPTIONS.ALL:
|
|
150
|
-
return ADDRESS_STATUS_TYPES.FULL_ADDRESS;
|
|
151
|
-
case ADDRESS_VISIBILITY_OPTIONS.NONE:
|
|
152
|
-
return ADDRESS_STATUS_TYPES.DONT_SHOW;
|
|
153
|
-
default:
|
|
154
|
-
return ADDRESS_STATUS_TYPES.STATE_CITY_ZIP;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Helper function to get fields that should only be set for new members
|
|
159
|
-
* @param {Object} inputMemberData - Raw member data from API
|
|
160
|
-
* @param {Object} existingDbMember - Existing member data from database
|
|
161
|
-
* @returns {Promise<Object>} - Object with fields that should only be set for new members
|
|
162
|
-
*/
|
|
163
|
-
async function getNewMemberOnlyFields(inputMemberData, existingDbMember) {
|
|
164
|
-
if (existingDbMember) {
|
|
165
|
-
return {};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Only set these fields for new members
|
|
169
|
-
const sanitizedFirstName = inputMemberData.firstname?.trim() || '';
|
|
170
|
-
const sanitizedLastName = inputMemberData.lastname?.trim() || '';
|
|
171
|
-
const fullName = createFullName(sanitizedFirstName, sanitizedLastName);
|
|
172
|
-
|
|
173
|
-
const uniqueUrl = await ensureUniqueUrl({
|
|
174
|
-
url: inputMemberData.url,
|
|
175
|
-
memberId: inputMemberData.memberid,
|
|
176
|
-
fullName,
|
|
177
|
-
});
|
|
178
|
-
return {
|
|
179
|
-
memberId: inputMemberData.memberid,
|
|
180
|
-
firstName: sanitizedFirstName,
|
|
181
|
-
lastName: sanitizedLastName,
|
|
182
|
-
fullName,
|
|
183
|
-
phones: inputMemberData.phones || [],
|
|
184
|
-
toShowPhone: inputMemberData.migrationData?.show_phone || '',
|
|
185
|
-
optOut: inputMemberData.migrationData?.opted_out || false,
|
|
186
|
-
url: uniqueUrl,
|
|
187
|
-
showContactForm: true,
|
|
188
|
-
bookingUrl: inputMemberData.migrationData?.schedule_code?.startsWith('http')
|
|
189
|
-
? inputMemberData.migrationData?.schedule_code
|
|
190
|
-
: '',
|
|
191
|
-
APIBookingUrl: inputMemberData.migrationData?.schedule_code,
|
|
192
|
-
showABMP: inputMemberData.migrationData?.show_member_since || false,
|
|
193
|
-
locHash: generateGeoHash(inputMemberData.addresses || []),
|
|
194
|
-
...DEFAULT_MEMBER_DISPLAY_SETTINGS,
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Enriches member data with optional migration properties
|
|
199
|
-
* @param {Object} memberDataToUpdate - Member data object to enhance
|
|
200
|
-
* @param {Object} migrationData - Migration data containing optional properties
|
|
201
|
-
*/
|
|
202
|
-
function enrichWithMigrationData(memberDataToUpdate, migrationData) {
|
|
203
|
-
if (!migrationData) return;
|
|
204
|
-
|
|
205
|
-
memberDataToUpdate.addressInfo = migrationData.addressinfo;
|
|
206
|
-
|
|
207
|
-
if (migrationData.website) {
|
|
208
|
-
memberDataToUpdate.website = migrationData.website;
|
|
209
|
-
memberDataToUpdate.showWebsite = true;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (migrationData.interests) {
|
|
213
|
-
memberDataToUpdate.areasOfPractices = processInterests(migrationData.interests);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Processes interests string into clean array
|
|
218
|
-
* @param {string} interestsString - Comma-separated interests string
|
|
219
|
-
* @returns {Array} - Array of trimmed, non-empty interests
|
|
220
|
-
*/
|
|
221
|
-
function processInterests(interestsString) {
|
|
222
|
-
if (!interestsString) return [];
|
|
223
|
-
|
|
224
|
-
return interestsString
|
|
225
|
-
.split(',')
|
|
226
|
-
.map(interest => interest.trim())
|
|
227
|
-
.filter(interest => interest.length > 0);
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* Creates base member data structure with core properties
|
|
231
|
-
* @param {Object} inputMemberData - Raw member data from API
|
|
232
|
-
* @param {Object} existingDbMember - Existing member data from database
|
|
233
|
-
* @param {number} currentPageNumber - Current page number being processed
|
|
234
|
-
* @returns {Promise<Object|null>} - Structured base member data or null if required fields are missing
|
|
235
|
-
*/
|
|
236
|
-
async function createCoreMemberData(inputMemberData, existingDbMember, currentPageNumber) {
|
|
237
|
-
if (!validateCoreMemberData(inputMemberData)) {
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const getMemberEmails = () => {
|
|
242
|
-
// Update both loginEmail & contactFormEmail only for new members who don't exist in DB
|
|
243
|
-
// 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
|
|
244
|
-
const newEmail = inputMemberData.email.trim();
|
|
245
|
-
const isMemberExistInDb = Boolean(existingDbMember?._id);
|
|
246
|
-
if (!isMemberExistInDb) {
|
|
247
|
-
return {
|
|
248
|
-
email: newEmail,
|
|
249
|
-
contactFormEmail: newEmail,
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
//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
|
|
253
|
-
const isMemberReinstatedWithNewEmail =
|
|
254
|
-
inputMemberData.action === MEMBER_ACTIONS.NEW &&
|
|
255
|
-
newEmail &&
|
|
256
|
-
existingDbMember.email !== newEmail;
|
|
257
|
-
if (isMemberReinstatedWithNewEmail) {
|
|
258
|
-
// 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
|
|
259
|
-
return {
|
|
260
|
-
email: newEmail,
|
|
261
|
-
isLoginEmailChanged: true,
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
//If exists in DB but not reinstated with new email, then don't update emails
|
|
265
|
-
return {};
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
const newMemberFields = await getNewMemberOnlyFields(inputMemberData, existingDbMember);
|
|
269
|
-
|
|
270
|
-
return {
|
|
271
|
-
...existingDbMember,
|
|
272
|
-
|
|
273
|
-
// Always update these core fields from API
|
|
274
|
-
action: inputMemberData.action,
|
|
275
|
-
licenses: inputMemberData.licenses || [],
|
|
276
|
-
memberships: inputMemberData.memberships,
|
|
277
|
-
pageNumber: currentPageNumber,
|
|
278
|
-
isVisible: inputMemberData.action !== MEMBER_ACTIONS.DROP,
|
|
279
|
-
|
|
280
|
-
// Handle Member emails
|
|
281
|
-
...getMemberEmails(),
|
|
282
|
-
|
|
283
|
-
// Handle fields that should only be set for new members
|
|
284
|
-
...newMemberFields,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
module.exports = {
|
|
289
|
-
generateUpdatedMemberData,
|
|
290
|
-
};
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
const { taskManager } = require('psdev-task-manager');
|
|
2
|
-
|
|
3
|
-
const { TASKS_NAMES } = require('../consts');
|
|
4
|
-
const { fetchPACMembers } = require('../pac-api-methods');
|
|
5
|
-
|
|
6
|
-
const { bulkProcessAndSaveMemberData } = require('./bulk-process-methods');
|
|
7
|
-
const { isUpdatedMember, isABMPMember } = require('./utils');
|
|
8
|
-
|
|
9
|
-
async function syncMembersDataPerAction(action) {
|
|
10
|
-
try {
|
|
11
|
-
const firstPageResponse = await fetchPACMembers(1, action);
|
|
12
|
-
|
|
13
|
-
if (
|
|
14
|
-
!firstPageResponse ||
|
|
15
|
-
!firstPageResponse.results ||
|
|
16
|
-
firstPageResponse.results.length === 0
|
|
17
|
-
) {
|
|
18
|
-
return {
|
|
19
|
-
success: true,
|
|
20
|
-
totalPagesProcessed: 0,
|
|
21
|
-
lastPageProcessed: 0,
|
|
22
|
-
completedAt: new Date().toISOString(),
|
|
23
|
-
message: 'No data found',
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Calculate total pages from API response
|
|
28
|
-
const totalResults = firstPageResponse.total_results || 0;
|
|
29
|
-
const perPage = firstPageResponse.results.length;
|
|
30
|
-
const totalPages = firstPageResponse.total_pages || 0;
|
|
31
|
-
|
|
32
|
-
// Cap at 1000 pages as safety measure
|
|
33
|
-
const pagesToProcess = Math.min(totalPages, 1000);
|
|
34
|
-
|
|
35
|
-
console.log(
|
|
36
|
-
`Scheduling ${pagesToProcess} pages for processing (${totalResults} total records, ${perPage} per page)`
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
// Schedule tasks for all pages at once
|
|
40
|
-
const toScheduleTasks = Array.from(
|
|
41
|
-
{ length: pagesToProcess },
|
|
42
|
-
(_, i) => i + 1 // API expects page number to start from 1
|
|
43
|
-
).map(pageNumber => ({
|
|
44
|
-
name: TASKS_NAMES.SyncMembers,
|
|
45
|
-
data: {
|
|
46
|
-
pageNumber,
|
|
47
|
-
action,
|
|
48
|
-
},
|
|
49
|
-
type: 'scheduled',
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
// Wait for all scheduling to complete
|
|
53
|
-
await taskManager().scheduleInBulk(toScheduleTasks);
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
success: true,
|
|
57
|
-
totalPagesProcessed: pagesToProcess,
|
|
58
|
-
lastPageProcessed: pagesToProcess,
|
|
59
|
-
totalRecords: totalResults,
|
|
60
|
-
recordsPerPage: perPage,
|
|
61
|
-
completedAt: new Date().toISOString(),
|
|
62
|
-
};
|
|
63
|
-
} catch (error) {
|
|
64
|
-
throw new Error(`Synchronization failed: ${error.message}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Synchronizes a single page of member data
|
|
70
|
-
* @param {Object} taskObject - Task object containing page data
|
|
71
|
-
* @returns {Promise<Object>} - Page synchronization result
|
|
72
|
-
*/
|
|
73
|
-
async function synchronizeSinglePage(taskObject) {
|
|
74
|
-
const { pageNumber, action } = taskObject.data;
|
|
75
|
-
try {
|
|
76
|
-
const memberDataResponse = await fetchPACMembers(pageNumber, action);
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
!memberDataResponse ||
|
|
80
|
-
!memberDataResponse.results ||
|
|
81
|
-
memberDataResponse.results.length === 0
|
|
82
|
-
) {
|
|
83
|
-
throw new Error(`No data found for page ${pageNumber}`);
|
|
84
|
-
}
|
|
85
|
-
const toSyncMembers = memberDataResponse.results.filter(
|
|
86
|
-
member => isUpdatedMember(member) && isABMPMember(member)
|
|
87
|
-
);
|
|
88
|
-
if (toSyncMembers.length === 0) {
|
|
89
|
-
return {
|
|
90
|
-
success: true,
|
|
91
|
-
pageNumber,
|
|
92
|
-
totalPageSize: memberDataResponse.results.length,
|
|
93
|
-
filteredPageSize: toSyncMembers.length,
|
|
94
|
-
message: 'No to be updated, or ABMP members found',
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
const result = await bulkProcessAndSaveMemberData(toSyncMembers, pageNumber);
|
|
98
|
-
|
|
99
|
-
return {
|
|
100
|
-
success: true,
|
|
101
|
-
pageNumber,
|
|
102
|
-
totalPageSize: memberDataResponse.results.length,
|
|
103
|
-
filteredPageSize: toSyncMembers.length,
|
|
104
|
-
...result,
|
|
105
|
-
};
|
|
106
|
-
} catch (error) {
|
|
107
|
-
throw new Error(`Page ${pageNumber} synchronization failed: ${error.message}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
module.exports = {
|
|
112
|
-
syncMembersDataPerAction,
|
|
113
|
-
synchronizeSinglePage,
|
|
114
|
-
};
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
const { updateWixMemberLoginEmail } = require('../members-area-methods');
|
|
2
|
-
|
|
3
|
-
const { MEMBER_ACTIONS } = require('./consts');
|
|
4
|
-
|
|
5
|
-
const isUpdatedMember = member => member.action !== MEMBER_ACTIONS.NONE;
|
|
6
|
-
const isABMPMember = member =>
|
|
7
|
-
member.memberships.some(membership => membership.association === 'ABMP');
|
|
8
|
-
|
|
9
|
-
const changeWixMembersEmails = async toChangeWixMembersEmails => {
|
|
10
|
-
console.log(
|
|
11
|
-
`Changing login emails for ${toChangeWixMembersEmails.length} members with ids: [${toChangeWixMembersEmails.map(member => member.memberId).join(', ')}]`
|
|
12
|
-
);
|
|
13
|
-
return await Promise.all(
|
|
14
|
-
toChangeWixMembersEmails.map(member => updateWixMemberLoginEmail(member, {}))
|
|
15
|
-
);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Validates core member data requirements
|
|
20
|
-
* @param {Object} inputMemberData - Raw member data from API to validate
|
|
21
|
-
* @returns {boolean} - True if all required fields are valid, false otherwise
|
|
22
|
-
*/
|
|
23
|
-
const validateCoreMemberData = inputMemberData => {
|
|
24
|
-
// Check memberid
|
|
25
|
-
if (!inputMemberData?.memberid) {
|
|
26
|
-
console.warn('validateCoreMemberData: Missing required field - memberid is mandatory');
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Check email
|
|
31
|
-
if (
|
|
32
|
-
!inputMemberData?.email ||
|
|
33
|
-
typeof inputMemberData.email !== 'string' ||
|
|
34
|
-
!inputMemberData.email.trim()
|
|
35
|
-
) {
|
|
36
|
-
console.warn(
|
|
37
|
-
'validateCoreMemberData: Missing required field - email (valid string) is mandatory'
|
|
38
|
-
);
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Check memberships
|
|
43
|
-
if (
|
|
44
|
-
!inputMemberData?.memberships ||
|
|
45
|
-
!Array.isArray(inputMemberData.memberships) ||
|
|
46
|
-
inputMemberData.memberships.length === 0
|
|
47
|
-
) {
|
|
48
|
-
console.warn(
|
|
49
|
-
'validateCoreMemberData: Missing required field - memberships (non-empty array) is mandatory'
|
|
50
|
-
);
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return true;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const containsNonEnglish = str => /[^a-zA-Z0-9]/.test(str); // if it contains any non-english characters, test1 is allowed, but any others are not
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Creates a full name from first and last name components
|
|
61
|
-
* @param {string} firstName - First name
|
|
62
|
-
* @param {string} lastName - Last name
|
|
63
|
-
* @returns {string} - Combined full name
|
|
64
|
-
*/
|
|
65
|
-
const createFullName = (firstName, lastName) => {
|
|
66
|
-
const trimmedFirst = firstName?.trim() || '';
|
|
67
|
-
const trimmedLast = lastName?.trim() || '';
|
|
68
|
-
return `${trimmedFirst} ${trimmedLast}`.trim();
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
module.exports = {
|
|
72
|
-
isUpdatedMember,
|
|
73
|
-
isABMPMember,
|
|
74
|
-
changeWixMembersEmails,
|
|
75
|
-
validateCoreMemberData,
|
|
76
|
-
containsNonEnglish,
|
|
77
|
-
createFullName,
|
|
78
|
-
};
|
package/backend/jobs.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
const { taskManager } = require('psdev-task-manager');
|
|
2
|
-
|
|
3
|
-
const { TASKS_NAMES } = require('./consts');
|
|
4
|
-
const { TASKS } = require('./tasks');
|
|
5
|
-
|
|
6
|
-
async function runScheduledTasks() {
|
|
7
|
-
try {
|
|
8
|
-
console.log('runScheduledTasks started');
|
|
9
|
-
return await taskManager().runScheduledTasks(TASKS);
|
|
10
|
-
} catch (error) {
|
|
11
|
-
console.error(`Failed to runScheduledTasks: ${error.message}`);
|
|
12
|
-
throw new Error(`Failed to runScheduledTasks: ${error.message}`);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function scheduleDailyPullTask() {
|
|
17
|
-
try {
|
|
18
|
-
console.log('scheduleDailyPullTask started!');
|
|
19
|
-
return await taskManager().schedule({
|
|
20
|
-
name: TASKS_NAMES.ScheduleDailyMembersDataSync,
|
|
21
|
-
data: {},
|
|
22
|
-
type: 'scheduled',
|
|
23
|
-
});
|
|
24
|
-
} catch (error) {
|
|
25
|
-
console.error(`Failed to scheduleDailyPullTask: ${error.message}`);
|
|
26
|
-
throw new Error(`Failed to scheduleDailyPullTask: ${error.message}`);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
module.exports = { runScheduledTasks, scheduleDailyPullTask };
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
const { secrets } = require('@wix/secrets');
|
|
2
|
-
|
|
3
|
-
const { PAC_API_URL } = require('./daily-pull/consts');
|
|
4
|
-
|
|
5
|
-
const getHeaders = async () => {
|
|
6
|
-
const AUTH_TOKEN = await secrets.getSecretValue('members-data-api-key');
|
|
7
|
-
const headers = {
|
|
8
|
-
Authorization: `Bearer ${AUTH_TOKEN}`,
|
|
9
|
-
};
|
|
10
|
-
return headers;
|
|
11
|
-
};
|
|
12
|
-
const fetchPACMembers = async (pageNum, actionFilter) => {
|
|
13
|
-
const url = `${PAC_API_URL}/Members?page=${pageNum}&actionFilter=${actionFilter}`;
|
|
14
|
-
const headers = await getHeaders();
|
|
15
|
-
const fetchOptions = {
|
|
16
|
-
method: 'get',
|
|
17
|
-
headers: headers,
|
|
18
|
-
};
|
|
19
|
-
const response = await fetch(url, fetchOptions);
|
|
20
|
-
const responseType = response.headers.get('content-type');
|
|
21
|
-
if (!responseType.includes('application/json')) {
|
|
22
|
-
const errorMessage = `[fetchPACMembers] got invalid responseType: ${responseType} for page ${pageNum} and actionFilter ${actionFilter}`;
|
|
23
|
-
console.error(errorMessage);
|
|
24
|
-
throw new Error(errorMessage);
|
|
25
|
-
}
|
|
26
|
-
if (response.ok) {
|
|
27
|
-
return response.json();
|
|
28
|
-
} else {
|
|
29
|
-
const errorMessage = `[fetchPACMembers] failed with status ${response.status} for page ${pageNum} and actionFilter ${actionFilter}`;
|
|
30
|
-
console.error(errorMessage);
|
|
31
|
-
throw new Error(errorMessage);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
module.exports = { fetchPACMembers, getHeaders }; //TODO: remove getHeaders from exported methods once npm movement finishes
|