abmp-npm 1.8.31 → 1.8.33
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 +32 -2
- package/backend/consts.js +8 -13
- package/backend/contacts-methods.js +130 -0
- package/backend/daily-pull/bulk-process-methods.js +65 -0
- package/backend/daily-pull/consts.js +34 -0
- package/backend/daily-pull/index.js +4 -0
- package/backend/daily-pull/process-member-methods.js +290 -0
- package/backend/daily-pull/sync-to-cms-methods.js +114 -0
- package/backend/daily-pull/utils.js +78 -0
- package/backend/index.js +7 -2
- package/backend/jobs.js +30 -0
- package/backend/members-area-methods.js +48 -1
- package/backend/members-data-methods.js +123 -126
- package/backend/pac-api-methods.js +35 -0
- package/backend/tasks.js +37 -0
- package/backend/utils.js +58 -53
- package/eslint.config.js +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,114 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
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/index.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
...require('./forms-methods'),
|
|
3
3
|
...require('./search-filters-methods'),
|
|
4
|
-
...require('./
|
|
5
|
-
...require('./utils'),
|
|
4
|
+
...require('./jobs'),
|
|
5
|
+
...require('./utils'), //TODO: remove it once we finish NPM movement
|
|
6
|
+
...require('./daily-pull'), //TODO: remove it once we finish NPM movement
|
|
7
|
+
...require('./pac-api-methods'), //TODO: remove it once we finish NPM movement
|
|
8
|
+
...require('./members-area-methods'), //TODO: remove it once we finish NPM movement
|
|
9
|
+
...require('./members-data-methods'), //TODO: remove it once we finish NPM movement
|
|
10
|
+
...require('./cms-data-methods'), //TODO: remove it once we finish NPM movement
|
|
6
11
|
};
|
package/backend/jobs.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
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,5 +1,5 @@
|
|
|
1
1
|
const { auth } = require('@wix/essentials');
|
|
2
|
-
const { members } = require('@wix/members');
|
|
2
|
+
const { members, authentication } = require('@wix/members');
|
|
3
3
|
const elevatedCreateMember = auth.elevate(members.createMember);
|
|
4
4
|
|
|
5
5
|
function prepareContactData(partner) {
|
|
@@ -36,7 +36,54 @@ const getCurrentMember = async () => {
|
|
|
36
36
|
return member.member;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Updates Wix member login email if the member has a contactId (registered Wix member)
|
|
41
|
+
* @param {Object} member - Member object with contactId and email
|
|
42
|
+
* @param {Object} result - Result object to track Wix member updates
|
|
43
|
+
*/
|
|
44
|
+
async function updateWixMemberLoginEmail(member, result = {}) {
|
|
45
|
+
if (!member.contactId) {
|
|
46
|
+
console.log(`Member ${member.memberId} has no contactId - skipping Wix login email update`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
console.log(
|
|
52
|
+
`Updating Wix login email for member ${member.memberId} (contactId: ${member.contactId})`
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const updatedWixMember = await authentication.changeLoginEmail(member.contactId, member.email);
|
|
56
|
+
|
|
57
|
+
console.log(
|
|
58
|
+
`✅ Successfully updated Wix login email for member ${member.memberId}: ${updatedWixMember.loginEmail}`
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (!result.wixMemberUpdates) {
|
|
62
|
+
result.wixMemberUpdates = { successful: 0, failed: 0 };
|
|
63
|
+
}
|
|
64
|
+
result.wixMemberUpdates.successful++;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`❌ Failed to update Wix login email for member ${member.memberId}:`, error);
|
|
67
|
+
|
|
68
|
+
if (!result.wixMemberUpdates) {
|
|
69
|
+
result.wixMemberUpdates = { successful: 0, failed: 0 };
|
|
70
|
+
}
|
|
71
|
+
result.wixMemberUpdates.failed++;
|
|
72
|
+
|
|
73
|
+
if (!result.wixMemberErrors) {
|
|
74
|
+
result.wixMemberErrors = [];
|
|
75
|
+
}
|
|
76
|
+
result.wixMemberErrors.push({
|
|
77
|
+
memberId: member.memberId,
|
|
78
|
+
contactId: member.contactId,
|
|
79
|
+
email: member.email,
|
|
80
|
+
error: error.message,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
39
85
|
module.exports = {
|
|
40
86
|
createSiteMember,
|
|
41
87
|
getCurrentMember,
|
|
88
|
+
updateWixMemberLoginEmail,
|
|
42
89
|
};
|
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
const { contacts } = require('@wix/crm');
|
|
2
|
-
const { auth } = require('@wix/essentials');
|
|
3
|
-
|
|
4
1
|
const { COLLECTIONS } = require('../public/consts');
|
|
5
2
|
|
|
6
|
-
const {
|
|
3
|
+
const { updateMemberContactInfo } = require('./contacts-methods');
|
|
4
|
+
const { MEMBER_ACTIONS } = require('./daily-pull');
|
|
7
5
|
const { wixData } = require('./elevated-modules');
|
|
8
6
|
const { createSiteMember, getCurrentMember } = require('./members-area-methods');
|
|
9
7
|
const {
|
|
8
|
+
createBatches,
|
|
9
|
+
normalizeUrlForComparison,
|
|
10
|
+
queryAllItems,
|
|
10
11
|
formatDateToMonthYear,
|
|
11
12
|
getAddressDisplayOptions,
|
|
12
13
|
isStudent,
|
|
13
14
|
generateGeoHash,
|
|
14
|
-
urlExists,
|
|
15
15
|
} = require('./utils');
|
|
16
16
|
|
|
17
|
-
const elevatedGetContact = auth.elevate(contacts.getContact);
|
|
18
|
-
const elevatedUpdateContact = auth.elevate(contacts.updateContact);
|
|
19
|
-
|
|
20
17
|
/**
|
|
21
18
|
* Retrieves member data by member ID
|
|
22
19
|
* @param {string} memberId - The member ID to search for
|
|
@@ -136,145 +133,110 @@ async function validateMemberToken(memberIdInput) {
|
|
|
136
133
|
}
|
|
137
134
|
}
|
|
138
135
|
|
|
139
|
-
/**
|
|
140
|
-
*
|
|
141
|
-
* @
|
|
142
|
-
* @param {function} updateInfoCallback - Function that returns the updated info object
|
|
143
|
-
* @param {string} operationName - Name of the operation for logging
|
|
136
|
+
/** Performs bulk save operation for member data
|
|
137
|
+
* @param { Array } memberDataList - Array of member data objects to save
|
|
138
|
+
* @returns { Promise < Object >} - Bulk save operation result
|
|
144
139
|
*/
|
|
145
|
-
async function
|
|
146
|
-
if (!
|
|
147
|
-
throw new Error('
|
|
140
|
+
async function bulkSaveMembers(memberDataList) {
|
|
141
|
+
if (!Array.isArray(memberDataList) || memberDataList.length === 0) {
|
|
142
|
+
throw new Error('Invalid member data list provided for bulk save');
|
|
148
143
|
}
|
|
149
144
|
|
|
150
145
|
try {
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
146
|
+
// bulkSave all with batches of 1000 items as this is the Velo limit for bulkSave
|
|
147
|
+
const batches = createBatches(memberDataList, 1000);
|
|
148
|
+
return await Promise.all(
|
|
149
|
+
batches.map(batch => wixData.bulkSave(COLLECTIONS.MEMBERS_DATA, batch))
|
|
150
|
+
);
|
|
156
151
|
} catch (error) {
|
|
157
|
-
console.error(
|
|
158
|
-
throw new Error(`
|
|
152
|
+
console.error('Error bulk saving members:', error);
|
|
153
|
+
throw new Error(`Bulk save failed: ${error.message}`);
|
|
159
154
|
}
|
|
160
155
|
}
|
|
161
156
|
|
|
162
157
|
/**
|
|
163
|
-
*
|
|
164
|
-
* @param {string}
|
|
165
|
-
* @
|
|
158
|
+
* Retrieves member data by member ID
|
|
159
|
+
* @param {string} memberId - The member ID to search for
|
|
160
|
+
* @returns {Promise<Object|null>} - Member data or null if not found
|
|
166
161
|
*/
|
|
167
|
-
async function
|
|
168
|
-
if (!
|
|
169
|
-
throw new Error('
|
|
162
|
+
async function findMemberById(memberId) {
|
|
163
|
+
if (!memberId) {
|
|
164
|
+
throw new Error('Member ID is required');
|
|
170
165
|
}
|
|
171
166
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
items: [
|
|
178
|
-
{
|
|
179
|
-
email: newEmail,
|
|
180
|
-
primary: true,
|
|
181
|
-
},
|
|
182
|
-
],
|
|
183
|
-
},
|
|
184
|
-
}),
|
|
185
|
-
'update contact email'
|
|
186
|
-
);
|
|
187
|
-
}
|
|
167
|
+
try {
|
|
168
|
+
const queryResult = await wixData
|
|
169
|
+
.query(COLLECTIONS.MEMBERS_DATA)
|
|
170
|
+
.eq('memberId', memberId)
|
|
171
|
+
.find();
|
|
188
172
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
* @param {string} firstName - The new first name
|
|
193
|
-
* @param {string} lastName - The new last name
|
|
194
|
-
*/
|
|
195
|
-
async function updateContactNames(contactId, firstName, lastName) {
|
|
196
|
-
if (!firstName && !lastName) {
|
|
197
|
-
throw new Error('At least one name field is required');
|
|
173
|
+
return queryResult.items.length > 0 ? queryResult.items[0] : null;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
throw new Error(`Failed to retrieve member data: ${error.message}`);
|
|
198
176
|
}
|
|
199
|
-
|
|
200
|
-
return await updateContactInfo(
|
|
201
|
-
contactId,
|
|
202
|
-
currentInfo => ({
|
|
203
|
-
...currentInfo,
|
|
204
|
-
name: {
|
|
205
|
-
first: firstName || currentInfo?.name?.first || '',
|
|
206
|
-
last: lastName || currentInfo?.name?.last || '',
|
|
207
|
-
},
|
|
208
|
-
}),
|
|
209
|
-
'update contact names'
|
|
210
|
-
);
|
|
211
177
|
}
|
|
212
178
|
|
|
213
179
|
/**
|
|
214
|
-
*
|
|
215
|
-
* @param {
|
|
216
|
-
* @param {
|
|
217
|
-
* @param {
|
|
218
|
-
* @param {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (!hasChanged) return null;
|
|
223
|
-
return updater(...argsBuilder(newValues));
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Updates member contact information in CRM if fields have changed
|
|
228
|
-
* @param {string} id - Member ID
|
|
229
|
-
* @param {Object} data - New member data
|
|
230
|
-
*/
|
|
231
|
-
const updateMemberContactInfo = async (id, data) => {
|
|
232
|
-
const existing = await findMemberByWixDataId(id);
|
|
233
|
-
const { contactId } = existing;
|
|
234
|
-
|
|
235
|
-
const updateConfig = [
|
|
236
|
-
{
|
|
237
|
-
fields: ['contactFormEmail'],
|
|
238
|
-
updater: updateContactEmail,
|
|
239
|
-
args: ([email]) => [contactId, email],
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
fields: ['firstName', 'lastName'],
|
|
243
|
-
updater: updateContactNames,
|
|
244
|
-
args: ([firstName, lastName]) => [contactId, firstName, lastName],
|
|
245
|
-
},
|
|
246
|
-
];
|
|
247
|
-
|
|
248
|
-
const updatePromises = updateConfig
|
|
249
|
-
.map(({ fields, updater, args }) => {
|
|
250
|
-
const existingValues = fields.map(field => existing[field]);
|
|
251
|
-
const newValues = fields.map(field => data[field]);
|
|
252
|
-
return updateIfChanged(existingValues, newValues, updater, args);
|
|
253
|
-
})
|
|
254
|
-
.filter(Boolean);
|
|
255
|
-
|
|
256
|
-
await Promise.all(updatePromises);
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Checks URL uniqueness for a member
|
|
261
|
-
* @param {string} url - The URL to check
|
|
262
|
-
* @param {string} memberId - The member ID to exclude from the check
|
|
263
|
-
* @returns {Promise<Object>} Result object with isUnique boolean
|
|
180
|
+
* Method to get member by slug with flexible filtering options
|
|
181
|
+
* @param {Object} options - Query options
|
|
182
|
+
* @param {string} options.slug - The slug to search for
|
|
183
|
+
* @param {boolean} options.excludeDropped - Whether to exclude dropped members (default: true)
|
|
184
|
+
* @param {boolean} options.excludeSearchedMember - Whether to exclude a specific member (default: false)
|
|
185
|
+
* @param {string|number} [options.memberId] - Member ID to exclude when excludeSearchedMember is true (optional)
|
|
186
|
+
* @param {boolean} [options.queryAllMatches=false] - Whether to query all matches or just the first one (default: false)
|
|
187
|
+
* @returns {Promise<Object|null>} - Member data or null if not found
|
|
264
188
|
*/
|
|
265
|
-
async function
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
189
|
+
async function getMemberBySlug({
|
|
190
|
+
slug,
|
|
191
|
+
excludeDropped = true,
|
|
192
|
+
excludeSearchedMember = false,
|
|
193
|
+
memberId = null,
|
|
194
|
+
queryAllMatches = false,
|
|
195
|
+
}) {
|
|
196
|
+
if (!slug) return null;
|
|
269
197
|
|
|
270
198
|
try {
|
|
271
|
-
|
|
272
|
-
const exists = await urlExists(trimmedUrl, memberId);
|
|
199
|
+
let query = wixData.query(COLLECTIONS.MEMBERS_DATA).contains('url', slug);
|
|
273
200
|
|
|
274
|
-
|
|
201
|
+
if (excludeDropped) {
|
|
202
|
+
query = query.ne('action', 'drop');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (excludeSearchedMember && memberId) {
|
|
206
|
+
query = query.ne('memberId', memberId);
|
|
207
|
+
}
|
|
208
|
+
let membersList;
|
|
209
|
+
if (queryAllMatches) {
|
|
210
|
+
query = query.limit(1000);
|
|
211
|
+
membersList = await queryAllItems(query);
|
|
212
|
+
} else {
|
|
213
|
+
membersList = await query.find().then(res => res.items);
|
|
214
|
+
}
|
|
215
|
+
let matchingMembers = membersList.filter(
|
|
216
|
+
item => item.url && item.url.toLowerCase() === slug.toLowerCase()
|
|
217
|
+
);
|
|
218
|
+
if (queryAllMatches) {
|
|
219
|
+
matchingMembers = membersList
|
|
220
|
+
.filter(
|
|
221
|
+
//remove trailing "-1", "-2", etc.
|
|
222
|
+
item => item.url && normalizeUrlForComparison(item.url) === slug.toLowerCase()
|
|
223
|
+
)
|
|
224
|
+
.sort((a, b) => b.url.toLowerCase().localeCompare(a.url.toLowerCase()));
|
|
225
|
+
}
|
|
226
|
+
if (matchingMembers.length > 1) {
|
|
227
|
+
const queryResultMsg = `Multiple members found with same slug ${slug} membersIds are : [${matchingMembers
|
|
228
|
+
.map(member => member.memberId)
|
|
229
|
+
.join(', ')}]`;
|
|
230
|
+
if (!queryAllMatches) {
|
|
231
|
+
throw new Error(queryResultMsg);
|
|
232
|
+
} else {
|
|
233
|
+
console.log(queryResultMsg);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return matchingMembers[0] || null;
|
|
275
237
|
} catch (error) {
|
|
276
|
-
console.error('Error
|
|
277
|
-
throw
|
|
238
|
+
console.error('Error getting member by slug:', error);
|
|
239
|
+
throw error;
|
|
278
240
|
}
|
|
279
241
|
}
|
|
280
242
|
|
|
@@ -321,10 +283,45 @@ async function saveRegistrationData(data, id) {
|
|
|
321
283
|
}
|
|
322
284
|
}
|
|
323
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Checks if a URL already exists in the database for a different member (case-insensitive)
|
|
288
|
+
* @param {string} url - The URL to check
|
|
289
|
+
* @param {string|number} excludeMemberId - Member ID to exclude from the check
|
|
290
|
+
* @returns {Promise<boolean>} - True if URL exists for another member
|
|
291
|
+
*/
|
|
292
|
+
async function urlExists(url, excludeMemberId) {
|
|
293
|
+
if (!url) return false;
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
let query = wixData
|
|
297
|
+
.query(COLLECTIONS.MEMBERS_DATA)
|
|
298
|
+
.contains('url', url)
|
|
299
|
+
.ne('action', MEMBER_ACTIONS.DROP);
|
|
300
|
+
|
|
301
|
+
if (excludeMemberId) {
|
|
302
|
+
query = query.ne('memberId', excludeMemberId);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const { items } = await query.find();
|
|
306
|
+
|
|
307
|
+
// Case-insensitive comparison
|
|
308
|
+
const matchingMembers = items.filter(
|
|
309
|
+
item => item.url && item.url.toLowerCase() === url.toLowerCase()
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
return matchingMembers.length > 0;
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.error('Error checking URL existence:', error);
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
324
319
|
module.exports = {
|
|
325
320
|
findMemberByWixDataId,
|
|
326
321
|
createContactAndMemberIfNew,
|
|
327
322
|
validateMemberToken,
|
|
328
|
-
checkUrlUniqueness,
|
|
329
323
|
saveRegistrationData,
|
|
324
|
+
bulkSaveMembers,
|
|
325
|
+
findMemberById,
|
|
326
|
+
getMemberBySlug,
|
|
330
327
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
package/backend/tasks.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const { TASKS_NAMES } = require('./consts');
|
|
2
|
+
const { MEMBER_ACTIONS, synchronizeSinglePage, syncMembersDataPerAction } = require('./daily-pull');
|
|
3
|
+
|
|
4
|
+
const getDailyMembersDataSyncChildTasks = () => {
|
|
5
|
+
// we don't want to sync none action as it means this members data hasn't changed and we don't need to sync it
|
|
6
|
+
const MEMBER_ACTIONS_EXCEPT_NONE = Object.values(MEMBER_ACTIONS).filter(
|
|
7
|
+
action => action !== MEMBER_ACTIONS.NONE
|
|
8
|
+
);
|
|
9
|
+
return MEMBER_ACTIONS_EXCEPT_NONE.map(action => ({
|
|
10
|
+
name: TASKS_NAMES.ScheduleMembersDataPerAction,
|
|
11
|
+
data: { action },
|
|
12
|
+
}));
|
|
13
|
+
};
|
|
14
|
+
const TASKS = {
|
|
15
|
+
[TASKS_NAMES.ScheduleDailyMembersDataSync]: {
|
|
16
|
+
name: TASKS_NAMES.ScheduleDailyMembersDataSync,
|
|
17
|
+
scheduleChildrenSequentially: false,
|
|
18
|
+
estimatedDurationSec: 60,
|
|
19
|
+
childTasks: getDailyMembersDataSyncChildTasks(),
|
|
20
|
+
},
|
|
21
|
+
[TASKS_NAMES.ScheduleMembersDataPerAction]: {
|
|
22
|
+
name: TASKS_NAMES.ScheduleMembersDataPerAction,
|
|
23
|
+
getIdentifier: task => task.data.action,
|
|
24
|
+
process: syncMembersDataPerAction,
|
|
25
|
+
shouldSkipCheck: () => false,
|
|
26
|
+
estimatedDurationSec: 6,
|
|
27
|
+
},
|
|
28
|
+
[TASKS_NAMES.SyncMembers]: {
|
|
29
|
+
name: TASKS_NAMES.SyncMembers,
|
|
30
|
+
getIdentifier: task => task,
|
|
31
|
+
process: synchronizeSinglePage,
|
|
32
|
+
shouldSkipCheck: () => false,
|
|
33
|
+
estimatedDurationSec: 6,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
module.exports = { TASKS };
|