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
|
@@ -4,7 +4,11 @@ const { COLLECTIONS, MEMBERS_FIELDS } = require('../public/consts.js');
|
|
|
4
4
|
const { findMainAddress } = require('../public/Utils/sharedUtils.js');
|
|
5
5
|
const { calculateDistance, shuffleArray } = require('../public/Utils/sharedUtils.js');
|
|
6
6
|
|
|
7
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
GEO_HASH_PRECISION,
|
|
9
|
+
MAX__MEMBERS_SEARCH_RESULTS,
|
|
10
|
+
WIX_QUERY_MAX_LIMIT,
|
|
11
|
+
} = require('./consts.js');
|
|
8
12
|
const { wixData } = require('./elevated-modules');
|
|
9
13
|
|
|
10
14
|
function buildMembersSearchQuery(data) {
|
|
@@ -92,7 +96,7 @@ function buildMembersSearchQuery(data) {
|
|
|
92
96
|
query = applyFilterToQuery(query, config, filter);
|
|
93
97
|
});
|
|
94
98
|
if (isUserLocationEnabled && isSearchingNearby) {
|
|
95
|
-
const userGeohash = geohash.encode(filter.latitude, filter.longitude,
|
|
99
|
+
const userGeohash = geohash.encode(filter.latitude, filter.longitude, GEO_HASH_PRECISION);
|
|
96
100
|
const neighborGeohashes = geohash.neighbors(userGeohash);
|
|
97
101
|
const geohashList = [userGeohash, ...neighborGeohashes];
|
|
98
102
|
query = query.hasSome('locHash', geohashList);
|
|
@@ -203,7 +207,33 @@ async function fetchAllItemsInParallel(query) {
|
|
|
203
207
|
};
|
|
204
208
|
}
|
|
205
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Get all interests from the database
|
|
212
|
+
* @returns {Promise<Array<string>>} Array of interest titles sorted alphabetically
|
|
213
|
+
*/
|
|
214
|
+
async function getInterestAll() {
|
|
215
|
+
try {
|
|
216
|
+
let res = await wixData.query(COLLECTIONS.INTERESTS).limit(1000).find();
|
|
217
|
+
|
|
218
|
+
let interests = res.items.map(x => x.title);
|
|
219
|
+
|
|
220
|
+
while (res.hasNext()) {
|
|
221
|
+
res = await res.next();
|
|
222
|
+
interests.push(...res.items.map(x => x.title));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Sort the interests alphabetically (case-insensitive)
|
|
226
|
+
interests = interests.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
227
|
+
|
|
228
|
+
return interests;
|
|
229
|
+
} catch (e) {
|
|
230
|
+
console.error('Error in getInterestAll:', e);
|
|
231
|
+
throw e;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
206
235
|
module.exports = {
|
|
207
236
|
buildMembersSearchQuery,
|
|
208
237
|
fetchAllItemsInParallel,
|
|
238
|
+
getInterestAll,
|
|
209
239
|
};
|
package/backend/consts.js
CHANGED
|
@@ -8,26 +8,21 @@ const CONFIG_KEYS = {
|
|
|
8
8
|
SITE_ASSOCIATION: 'SITE_ASSOCIATION',
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
const PRECISION = 3;
|
|
12
11
|
const MAX__MEMBERS_SEARCH_RESULTS = 120;
|
|
13
12
|
const WIX_QUERY_MAX_LIMIT = 1000;
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*/
|
|
20
|
-
const MEMBER_ACTIONS = {
|
|
21
|
-
UPDATE: 'update',
|
|
22
|
-
NEW: 'new',
|
|
23
|
-
DROP: 'drop',
|
|
24
|
-
NONE: 'none',
|
|
14
|
+
const TASKS_NAMES = {
|
|
15
|
+
ScheduleDailyMembersDataSync: 'ScheduleDailyMembersDataSync',
|
|
16
|
+
ScheduleMembersDataPerAction: 'ScheduleMembersDataPerAction',
|
|
17
|
+
SyncMembers: 'SyncMembers',
|
|
25
18
|
};
|
|
26
19
|
|
|
20
|
+
const GEO_HASH_PRECISION = 3;
|
|
21
|
+
|
|
27
22
|
module.exports = {
|
|
28
23
|
CONFIG_KEYS,
|
|
29
|
-
PRECISION,
|
|
30
24
|
MAX__MEMBERS_SEARCH_RESULTS,
|
|
31
25
|
WIX_QUERY_MAX_LIMIT,
|
|
32
|
-
|
|
26
|
+
TASKS_NAMES,
|
|
27
|
+
GEO_HASH_PRECISION,
|
|
33
28
|
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const { contacts } = require('@wix/crm');
|
|
2
|
+
const { auth } = require('@wix/essentials');
|
|
3
|
+
const elevatedGetContact = auth.elevate(contacts.getContact);
|
|
4
|
+
const elevatedUpdateContact = auth.elevate(contacts.updateContact);
|
|
5
|
+
|
|
6
|
+
const { findMemberByWixDataId } = require('./members-data-methods');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generic contact update helper function
|
|
10
|
+
* @param {string} contactId - The contact ID in Wix CRM
|
|
11
|
+
* @param {function} updateInfoCallback - Function that returns the updated info object
|
|
12
|
+
* @param {string} operationName - Name of the operation for logging
|
|
13
|
+
*/
|
|
14
|
+
async function updateContactInfo(contactId, updateInfoCallback, operationName) {
|
|
15
|
+
if (!contactId) {
|
|
16
|
+
throw new Error('Contact ID is required');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const contact = await elevatedGetContact(contactId);
|
|
21
|
+
const currentInfo = contact.info;
|
|
22
|
+
const updatedInfo = updateInfoCallback(currentInfo);
|
|
23
|
+
|
|
24
|
+
await elevatedUpdateContact(contactId, { info: updatedInfo }, contact.revision);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(`Error in ${operationName}:`, error);
|
|
27
|
+
throw new Error(`Failed to ${operationName}: ${error.message}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Updates contact email in Wix CRM
|
|
33
|
+
* @param {string} contactId - The contact ID in Wix CRM
|
|
34
|
+
* @param {string} newEmail - The new email address
|
|
35
|
+
*/
|
|
36
|
+
async function updateContactEmail(contactId, newEmail) {
|
|
37
|
+
if (!newEmail) {
|
|
38
|
+
throw new Error('New email is required');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return await updateContactInfo(
|
|
42
|
+
contactId,
|
|
43
|
+
currentInfo => ({
|
|
44
|
+
...currentInfo,
|
|
45
|
+
emails: {
|
|
46
|
+
items: [
|
|
47
|
+
{
|
|
48
|
+
email: newEmail,
|
|
49
|
+
primary: true,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
'update contact email'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Updates contact names in Wix CRM
|
|
60
|
+
* @param {string} contactId - The contact ID in Wix CRM
|
|
61
|
+
* @param {string} firstName - The new first name
|
|
62
|
+
* @param {string} lastName - The new last name
|
|
63
|
+
*/
|
|
64
|
+
async function updateContactNames(contactId, firstName, lastName) {
|
|
65
|
+
if (!firstName && !lastName) {
|
|
66
|
+
throw new Error('At least one name field is required');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return await updateContactInfo(
|
|
70
|
+
contactId,
|
|
71
|
+
currentInfo => ({
|
|
72
|
+
...currentInfo,
|
|
73
|
+
name: {
|
|
74
|
+
first: firstName || currentInfo?.name?.first || '',
|
|
75
|
+
last: lastName || currentInfo?.name?.last || '',
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
'update contact names'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Update fields if they have changed
|
|
84
|
+
* @param {Array} existingValues - Current values for comparison
|
|
85
|
+
* @param {Array} newValues - New values to compare against
|
|
86
|
+
* @param {Function} updater - Function to call if values changed
|
|
87
|
+
* @param {Function} argsBuilder - Function to build arguments for updater
|
|
88
|
+
*/
|
|
89
|
+
const updateIfChanged = (existingValues, newValues, updater, argsBuilder) => {
|
|
90
|
+
const hasChanged = existingValues.some((val, idx) => val !== newValues[idx]);
|
|
91
|
+
if (!hasChanged) return null;
|
|
92
|
+
return updater(...argsBuilder(newValues));
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Updates member contact information in CRM if fields have changed
|
|
97
|
+
* @param {string} id - Member ID
|
|
98
|
+
* @param {Object} data - New member data
|
|
99
|
+
*/
|
|
100
|
+
const updateMemberContactInfo = async (id, data) => {
|
|
101
|
+
const existing = await findMemberByWixDataId(id);
|
|
102
|
+
const { contactId } = existing;
|
|
103
|
+
|
|
104
|
+
const updateConfig = [
|
|
105
|
+
{
|
|
106
|
+
fields: ['contactFormEmail'],
|
|
107
|
+
updater: updateContactEmail,
|
|
108
|
+
args: ([email]) => [contactId, email],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
fields: ['firstName', 'lastName'],
|
|
112
|
+
updater: updateContactNames,
|
|
113
|
+
args: ([firstName, lastName]) => [contactId, firstName, lastName],
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
const updatePromises = updateConfig
|
|
118
|
+
.map(({ fields, updater, args }) => {
|
|
119
|
+
const existingValues = fields.map(field => existing[field]);
|
|
120
|
+
const newValues = fields.map(field => data[field]);
|
|
121
|
+
return updateIfChanged(existingValues, newValues, updater, args);
|
|
122
|
+
})
|
|
123
|
+
.filter(Boolean);
|
|
124
|
+
|
|
125
|
+
await Promise.all(updatePromises);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
updateMemberContactInfo,
|
|
130
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
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 };
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,290 @@
|
|
|
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
|
+
};
|