abmp-npm 1.1.52 → 1.1.53

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.
@@ -207,7 +207,33 @@ async function fetchAllItemsInParallel(query) {
207
207
  };
208
208
  }
209
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
+
210
235
  module.exports = {
211
236
  buildMembersSearchQuery,
212
237
  fetchAllItemsInParallel,
238
+ getInterestAll,
213
239
  };
package/backend/consts.js CHANGED
@@ -12,6 +12,7 @@ const CONFIG_KEYS = {
12
12
  INTERESTS_API_URL: 'INTERESTS_API_URL',
13
13
  SITE_LOGO_URL: 'SITE_LOGO_URL',
14
14
  MEMBERS_EXTERNAL_PORTAL_URL: 'MEMBERS_EXTERNAL_PORTAL_URL',
15
+ DEFAULT_PROFILE_IMAGE: 'DEFAULT_PROFILE_IMAGE',
15
16
  };
16
17
 
17
18
  const MAX__MEMBERS_SEARCH_RESULTS = 120;
@@ -0,0 +1,128 @@
1
+ const { contacts } = require('@wix/crm');
2
+ const { auth } = require('@wix/essentials');
3
+
4
+ const elevatedGetContact = auth.elevate(contacts.getContact);
5
+ const elevatedUpdateContact = auth.elevate(contacts.updateContact);
6
+
7
+ /**
8
+ * Generic contact update helper function
9
+ * @param {string} contactId - The contact ID in Wix CRM
10
+ * @param {function} updateInfoCallback - Function that returns the updated info object
11
+ * @param {string} operationName - Name of the operation for logging
12
+ */
13
+ async function updateContactInfo(contactId, updateInfoCallback, operationName) {
14
+ if (!contactId) {
15
+ throw new Error('Contact ID is required');
16
+ }
17
+
18
+ try {
19
+ const contact = await elevatedGetContact(contactId);
20
+ const currentInfo = contact.info;
21
+ const updatedInfo = updateInfoCallback(currentInfo);
22
+
23
+ await elevatedUpdateContact(contactId, { info: updatedInfo }, contact.revision);
24
+ } catch (error) {
25
+ console.error(`Error in ${operationName}:`, error);
26
+ throw new Error(`Failed to ${operationName}: ${error.message}`);
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Updates contact email in Wix CRM
32
+ * @param {string} contactId - The contact ID in Wix CRM
33
+ * @param {string} newEmail - The new email address
34
+ */
35
+ async function updateContactEmail(contactId, newEmail) {
36
+ if (!newEmail) {
37
+ throw new Error('New email is required');
38
+ }
39
+
40
+ return await updateContactInfo(
41
+ contactId,
42
+ currentInfo => ({
43
+ ...currentInfo,
44
+ emails: {
45
+ items: [
46
+ {
47
+ email: newEmail,
48
+ primary: true,
49
+ },
50
+ ],
51
+ },
52
+ }),
53
+ 'update contact email'
54
+ );
55
+ }
56
+
57
+ /**
58
+ * Updates contact names in Wix CRM
59
+ * @param {string} contactId - The contact ID in Wix CRM
60
+ * @param {string} firstName - The new first name
61
+ * @param {string} lastName - The new last name
62
+ */
63
+ async function updateContactNames(contactId, firstName, lastName) {
64
+ if (!firstName && !lastName) {
65
+ throw new Error('At least one name field is required');
66
+ }
67
+
68
+ return await updateContactInfo(
69
+ contactId,
70
+ currentInfo => ({
71
+ ...currentInfo,
72
+ name: {
73
+ first: firstName || currentInfo?.name?.first || '',
74
+ last: lastName || currentInfo?.name?.last || '',
75
+ },
76
+ }),
77
+ 'update contact names'
78
+ );
79
+ }
80
+
81
+ /**
82
+ * Update fields if they have changed
83
+ * @param {Array} existingValues - Current values for comparison
84
+ * @param {Array} newValues - New values to compare against
85
+ * @param {Function} updater - Function to call if values changed
86
+ * @param {Function} argsBuilder - Function to build arguments for updater
87
+ */
88
+ const updateIfChanged = (existingValues, newValues, updater, argsBuilder) => {
89
+ const hasChanged = existingValues.some((val, idx) => val !== newValues[idx]);
90
+ if (!hasChanged) return null;
91
+ return updater(...argsBuilder(newValues));
92
+ };
93
+
94
+ /**
95
+ * Updates member contact information in CRM if fields have changed
96
+ * @param {Object} data - New member data
97
+ * @param {Object} existingMemberData - Existing member data
98
+ */
99
+ const updateMemberContactInfo = async (data, existingMemberData) => {
100
+ const { contactId } = existingMemberData;
101
+
102
+ const updateConfig = [
103
+ {
104
+ fields: ['contactFormEmail'],
105
+ updater: updateContactEmail,
106
+ args: ([email]) => [contactId, email],
107
+ },
108
+ {
109
+ fields: ['firstName', 'lastName'],
110
+ updater: updateContactNames,
111
+ args: ([firstName, lastName]) => [contactId, firstName, lastName],
112
+ },
113
+ ];
114
+
115
+ const updatePromises = updateConfig
116
+ .map(({ fields, updater, args }) => {
117
+ const existingValues = fields.map(field => existingMemberData[field]);
118
+ const newValues = fields.map(field => data[field]);
119
+ return updateIfChanged(existingValues, newValues, updater, args);
120
+ })
121
+ .filter(Boolean);
122
+
123
+ await Promise.all(updatePromises);
124
+ };
125
+
126
+ module.exports = {
127
+ updateMemberContactInfo,
128
+ };
package/backend/index.js CHANGED
@@ -8,4 +8,6 @@ module.exports = {
8
8
  ...require('./pac-api-methods'), //TODO: remove it once we finish NPM movement
9
9
  ...require('./members-area-methods'), //TODO: remove it once we finish NPM movement
10
10
  ...require('./members-data-methods'), //TODO: remove it once we finish NPM movement
11
+ ...require('./cms-data-methods'), //TODO: remove it once we finish NPM movement
12
+ ...require('./sso-methods'),
11
13
  };
@@ -31,6 +31,11 @@ const createSiteMember = async memberDetails => {
31
31
  }
32
32
  };
33
33
 
34
+ const getCurrentMember = async () => {
35
+ const member = await members.getCurrentMember();
36
+ return member.member;
37
+ };
38
+
34
39
  /**
35
40
  * Updates Wix member login email if the member has a contactId (registered Wix member)
36
41
  * @param {Object} member - Member object with contactId and email
@@ -79,5 +84,6 @@ async function updateWixMemberLoginEmail(member, result = {}) {
79
84
 
80
85
  module.exports = {
81
86
  createSiteMember,
87
+ getCurrentMember,
82
88
  updateWixMemberLoginEmail,
83
89
  };
@@ -1,8 +1,15 @@
1
1
  const { COLLECTIONS } = require('../public/consts');
2
2
 
3
+ const { updateMemberContactInfo } = require('./contacts-methods');
4
+ const { MEMBER_ACTIONS } = require('./daily-pull');
3
5
  const { wixData } = require('./elevated-modules');
4
6
  const { createSiteMember } = require('./members-area-methods');
5
- const { createBatches, normalizeUrlForComparison, queryAllItems } = require('./utils');
7
+ const {
8
+ createBatches,
9
+ normalizeUrlForComparison,
10
+ queryAllItems,
11
+ generateGeoHash,
12
+ } = require('./utils');
6
13
 
7
14
  /**
8
15
  * Retrieves member data by member ID
@@ -50,10 +57,9 @@ async function createContactAndMemberIfNew(memberData) {
50
57
  }
51
58
  }
52
59
 
53
- /**
54
- * Performs bulk save operation for member data
55
- * @param {Array} memberDataList - Array of member data objects to save
56
- * @returns {Promise<Object>} - Bulk save operation result
60
+ /** Performs bulk save operation for member data
61
+ * @param { Array } memberDataList - Array of member data objects to save
62
+ * @returns { Promise < Object >} - Bulk save operation result
57
63
  */
58
64
  async function bulkSaveMembers(memberDataList) {
59
65
  if (!Array.isArray(memberDataList) || memberDataList.length === 0) {
@@ -71,6 +77,7 @@ async function bulkSaveMembers(memberDataList) {
71
77
  throw new Error(`Bulk save failed: ${error.message}`);
72
78
  }
73
79
  }
80
+
74
81
  /**
75
82
  * Retrieves member data by member ID
76
83
  * @param {string} memberId - The member ID to search for
@@ -181,9 +188,88 @@ async function getMemberByContactId(contactId) {
181
188
  }
182
189
  }
183
190
 
191
+ /**
192
+ * Saves member registration data
193
+ * @param {Object} data - Member data to save
194
+ * @param {string} id - Member ID
195
+ * @returns {Promise<Object>} Result object with type and data/error
196
+ */
197
+ async function saveRegistrationData(data, id) {
198
+ try {
199
+ console.log(' saveRegistrationData data._id', data._id);
200
+ console.log(' saveRegistrationData id', id);
201
+ if (data._id !== id) return { type: 'notAuthorized' };
202
+
203
+ if (data.url) {
204
+ const isDuplicate = await urlExists(data.url, data.memberId);
205
+
206
+ if (isDuplicate) {
207
+ return {
208
+ type: 'error',
209
+ error: 'URL slug is already taken. Please choose a different one.',
210
+ };
211
+ }
212
+ }
213
+
214
+ if (data.addresses && Array.isArray(data.addresses)) {
215
+ data.locHash = generateGeoHash(data.addresses);
216
+ }
217
+
218
+ const existingMemberData = await findMemberByWixDataId(id);
219
+
220
+ await updateMemberContactInfo(data, existingMemberData);
221
+
222
+ const saveData = await wixData.update(COLLECTIONS.MEMBERS_DATA, data);
223
+ return {
224
+ type: 'success',
225
+ saveData,
226
+ };
227
+ } catch (error) {
228
+ console.error(error);
229
+ return {
230
+ type: 'error',
231
+ error,
232
+ };
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Checks if a URL already exists in the database for a different member (case-insensitive)
238
+ * @param {string} url - The URL to check
239
+ * @param {string|number} excludeMemberId - Member ID to exclude from the check
240
+ * @returns {Promise<boolean>} - True if URL exists for another member
241
+ */
242
+ async function urlExists(url, excludeMemberId) {
243
+ if (!url) return false;
244
+
245
+ try {
246
+ let query = wixData
247
+ .query(COLLECTIONS.MEMBERS_DATA)
248
+ .contains('url', url)
249
+ .ne('action', MEMBER_ACTIONS.DROP);
250
+
251
+ if (excludeMemberId) {
252
+ query = query.ne('memberId', excludeMemberId);
253
+ }
254
+
255
+ const { items } = await query.find();
256
+
257
+ // Case-insensitive comparison
258
+ const matchingMembers = items.filter(
259
+ item => item.url && item.url.toLowerCase() === url.toLowerCase()
260
+ );
261
+
262
+ return matchingMembers.length > 0;
263
+ } catch (error) {
264
+ console.error('Error checking URL existence:', error);
265
+ return false;
266
+ }
267
+ }
268
+
184
269
  module.exports = {
185
270
  findMemberByWixDataId,
186
271
  createContactAndMemberIfNew,
272
+ saveRegistrationData,
187
273
  bulkSaveMembers,
188
274
  findMemberById,
189
275
  getMemberBySlug,
@@ -0,0 +1,88 @@
1
+ const { CONFIG_KEYS } = require('./consts');
2
+ const { MEMBER_ACTIONS } = require('./daily-pull');
3
+ const { getCurrentMember } = require('./members-area-methods');
4
+ const { getMemberByContactId } = require('./members-data-methods');
5
+ const {
6
+ formatDateToMonthYear,
7
+ getAddressDisplayOptions,
8
+ isStudent,
9
+ getSiteConfigs,
10
+ } = require('./utils');
11
+
12
+ /**
13
+ * Validates member token and retrieves member data
14
+ * @param {string} memberIdInput - The member ID to validate
15
+ * @returns {Promise<{memberData: Object|null, isValid: boolean}>} Validation result with member data
16
+ */
17
+ async function validateMemberToken(memberIdInput) {
18
+ const invalidTokenResponse = { memberData: null, isValid: false };
19
+
20
+ if (!memberIdInput) {
21
+ return invalidTokenResponse;
22
+ }
23
+
24
+ try {
25
+ const member = await getCurrentMember();
26
+ if (!member || !member._id) {
27
+ console.log(
28
+ 'member not found from members.getCurrentMember() for memberIdInput',
29
+ memberIdInput
30
+ );
31
+ return invalidTokenResponse;
32
+ }
33
+
34
+ const [dbMember, siteConfigs] = await Promise.all([
35
+ getMemberByContactId(member._id),
36
+ getSiteConfigs(),
37
+ ]);
38
+ const siteAssociation = siteConfigs[CONFIG_KEYS.SITE_ASSOCIATION];
39
+ const membersExternalPortalUrl = siteConfigs[CONFIG_KEYS.MEMBERS_EXTERNAL_PORTAL_URL];
40
+ console.log('dbMember by contact id is:', dbMember);
41
+ console.log('member._id', member._id);
42
+
43
+ if (!dbMember?._id) {
44
+ const errorMessage = `No record found in DB for logged in Member [Corrupted Data - Duplicate Members? ] - There is no match in DB for currentMember: ${JSON.stringify(
45
+ { memberIdInput, currentMemberId: member._id }
46
+ )}`;
47
+ console.error(errorMessage);
48
+ throw new Error('CORRUPTED_MEMBER_DATA');
49
+ }
50
+
51
+ console.log(`Id found in DB for memberIdInput :${memberIdInput} is ${dbMember?._id}`);
52
+
53
+ const memberData = dbMember;
54
+
55
+ // Format membership dates
56
+ memberData.memberships = memberData.memberships.map(membership => ({
57
+ ...membership,
58
+ membersince: formatDateToMonthYear(membership.membersince),
59
+ isSiteAssociation: membership.association === siteAssociation,
60
+ }));
61
+
62
+ const savedMemberId = memberData?._id;
63
+ const isValid = savedMemberId === memberIdInput;
64
+
65
+ if (!savedMemberId || !isValid) {
66
+ return invalidTokenResponse;
67
+ }
68
+
69
+ // Check if member is dropped
70
+ if (memberData.action === MEMBER_ACTIONS.DROP) {
71
+ return invalidTokenResponse;
72
+ }
73
+
74
+ // Add computed properties
75
+ memberData.addressDisplayOption = getAddressDisplayOptions(memberData);
76
+ console.log('memberData', memberData);
77
+ memberData.isStudent = isStudent(memberData);
78
+
79
+ return { memberData, isValid, membersExternalPortalUrl };
80
+ } catch (error) {
81
+ console.error('Error in validateMemberToken:', error);
82
+ throw error;
83
+ }
84
+ }
85
+
86
+ module.exports = {
87
+ validateMemberToken,
88
+ };
package/backend/utils.js CHANGED
@@ -4,6 +4,7 @@ const { COLLECTIONS } = require('../public/consts');
4
4
 
5
5
  const { CONFIG_KEYS, GEO_HASH_PRECISION } = require('./consts');
6
6
  const { wixData } = require('./elevated-modules');
7
+ const { urlExists } = require('./members-data-methods');
7
8
 
8
9
  /**
9
10
  * Retrieves site configuration values from the database
@@ -41,6 +42,46 @@ const retrieveAllItems = async collectionName => {
41
42
  return allItems;
42
43
  };
43
44
 
45
+ /**
46
+ * Format date to Month Year string
47
+ * @param {string} dateString - The date string to format
48
+ * @returns {string} Formatted date (e.g., "January 2024")
49
+ */
50
+ function formatDateToMonthYear(dateString) {
51
+ if (!dateString) return '';
52
+
53
+ const date = new Date(dateString);
54
+ if (isNaN(date.getTime())) return '';
55
+ const options = { year: 'numeric', month: 'long' };
56
+ return date.toLocaleDateString('en-US', options);
57
+ }
58
+
59
+ /**
60
+ * Check if member is a student
61
+ * @param {Object} member - The member object
62
+ * @returns {boolean} True if member has student membership
63
+ */
64
+ function isStudent(member) {
65
+ const memberships = member?.memberships;
66
+ if (!Array.isArray(memberships)) return false;
67
+
68
+ return memberships.some(membership => membership.membertype === 'student');
69
+ }
70
+
71
+ /**
72
+ * Get address display options for member
73
+ * @param {Object} member - The member object
74
+ * @returns {Array} Address display options
75
+ */
76
+ function getAddressDisplayOptions(member) {
77
+ const addresses = member.addresses || [];
78
+ const displayOptions = member.addressDisplayOption || [];
79
+ if (addresses.length === 1 && addresses[0].key) {
80
+ return [{ key: addresses[0].key, isMain: true }];
81
+ }
82
+ return displayOptions;
83
+ }
84
+
44
85
  const queryAllItems = async query => {
45
86
  console.log('start query');
46
87
  let oldResults = await query.find();
@@ -87,6 +128,28 @@ const normalizeUrlForComparison = url => {
87
128
  return url.toLowerCase().replace(/-\d+$/, '');
88
129
  };
89
130
 
131
+ /**
132
+ * Checks URL uniqueness for a member
133
+ * @param {string} url - The URL to check
134
+ * @param {string} memberId - The member ID to exclude from the check
135
+ * @returns {Promise<Object>} Result object with isUnique boolean
136
+ */
137
+ async function checkUrlUniqueness(url, memberId) {
138
+ if (!url || !memberId) {
139
+ throw new Error('Missing required parameters: url and memberId are required');
140
+ }
141
+
142
+ try {
143
+ const trimmedUrl = url.trim();
144
+ const exists = await urlExists(trimmedUrl, memberId);
145
+
146
+ return { isUnique: !exists };
147
+ } catch (error) {
148
+ console.error('Error checking URL uniqueness:', error);
149
+ throw new Error(`Failed to check URL uniqueness: ${error.message}`);
150
+ }
151
+ }
152
+
90
153
  module.exports = {
91
154
  getSiteConfigs,
92
155
  retrieveAllItems,
@@ -95,4 +158,8 @@ module.exports = {
95
158
  isValidArray,
96
159
  normalizeUrlForComparison,
97
160
  queryAllItems,
161
+ checkUrlUniqueness,
162
+ formatDateToMonthYear,
163
+ isStudent,
164
+ getAddressDisplayOptions,
98
165
  };
package/eslint.config.js CHANGED
@@ -31,7 +31,7 @@ module.exports = [
31
31
  // Error prevention
32
32
  'no-var': 'error',
33
33
  'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
34
- 'no-console': ['warn', { allow: ['warn', 'error', 'log', 'info'] }],
34
+ 'no-console': ['warn', { allow: ['warn', 'error', 'log', 'info', 'group', 'groupEnd'] }],
35
35
  'no-debugger': 'warn',
36
36
  'no-duplicate-imports': 'error',
37
37
  'no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abmp-npm",
3
- "version": "1.1.52",
3
+ "version": "1.1.53",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -34,6 +34,7 @@
34
34
  "@wix/secrets": "^1.0.62",
35
35
  "@wix/site-location": "^1.31.0",
36
36
  "@wix/site-window": "^1.44.0",
37
+ "lodash": "^4.17.21",
37
38
  "ngeohash": "^0.6.3",
38
39
  "phone": "^3.1.67",
39
40
  "psdev-task-manager": "1.1.7",