abmp-npm 1.1.63 → 1.1.64

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.
@@ -231,9 +231,17 @@ async function getInterestAll() {
231
231
  throw e;
232
232
  }
233
233
  }
234
+ async function clearCollection(collectionName) {
235
+ try {
236
+ await wixData.truncate(collectionName);
237
+ } catch (err) {
238
+ throw new Error(`Failed to clearCollection ${collectionName} with error: ${err.message}`);
239
+ }
240
+ }
234
241
 
235
242
  module.exports = {
236
243
  buildMembersSearchQuery,
237
244
  fetchAllItemsInParallel,
238
245
  getInterestAll,
246
+ clearCollection,
239
247
  };
@@ -10,6 +10,7 @@ const wixData = {
10
10
  save: auth.elevate(items.save),
11
11
  remove: auth.elevate(items.remove),
12
12
  get: auth.elevate(items.get),
13
+ truncate: auth.elevate(items.truncate),
13
14
  //TODO: add other methods here as needed
14
15
  };
15
16
 
@@ -0,0 +1,72 @@
1
+ const { COLLECTIONS } = require('../../public/consts');
2
+ const { clearCollection } = require('../cms-data-methods');
3
+ const { getSecret } = require('../utils');
4
+
5
+ const { migrateInterests } = require('./interests');
6
+
7
+ const createHTTPFunctionsHelpers = wixHTTPFunctionsMethods => {
8
+ const { created, serverError, forbidden, ok, badRequest } = wixHTTPFunctionsMethods;
9
+ const responseOptions = {
10
+ headers: {
11
+ 'Content-Type': 'application/json',
12
+ },
13
+ };
14
+
15
+ const isRequestAuthenticated = async request => {
16
+ const AUTH_TOKEN = await getSecret('migrate-api-key');
17
+ return request.headers.authorization === 'Bearer ' + AUTH_TOKEN;
18
+ };
19
+
20
+ const withAuth = handler => async request => {
21
+ if (!(await isRequestAuthenticated(request))) {
22
+ return forbidden({
23
+ ...responseOptions,
24
+ body: { error: 'Unauthorized' },
25
+ });
26
+ }
27
+ return handler(request);
28
+ };
29
+
30
+ const migrateInterestsHandler = async _request => {
31
+ try {
32
+ const result = await migrateInterests();
33
+ return created({
34
+ ...responseOptions,
35
+ body: {
36
+ result,
37
+ },
38
+ });
39
+ } catch (error) {
40
+ console.error('Error migrating interests:', error);
41
+ return serverError(error);
42
+ }
43
+ };
44
+
45
+ const clearCollectionHandler = async request => {
46
+ try {
47
+ const collectionName = request.query.collectionName;
48
+ if (!collectionName || !Object.values(COLLECTIONS).includes(collectionName)) {
49
+ return badRequest({
50
+ ...responseOptions,
51
+ body: { error: 'Invalid collection name' },
52
+ });
53
+ }
54
+ const result = await clearCollection(collectionName);
55
+ return ok({
56
+ ...responseOptions,
57
+ body: {
58
+ result,
59
+ },
60
+ });
61
+ } catch (error) {
62
+ return serverError(error);
63
+ }
64
+ };
65
+
66
+ return {
67
+ post_migrateInterests: withAuth(migrateInterestsHandler),
68
+ delete_clearCollection: withAuth(clearCollectionHandler),
69
+ };
70
+ };
71
+
72
+ module.exports = { createHTTPFunctionsHelpers };
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ ...require('./httpFunctions'),
3
+ };
@@ -0,0 +1,37 @@
1
+ const { COLLECTIONS } = require('../../public/consts');
2
+ const { clearCollection } = require('../cms-data-methods');
3
+ const { CONFIG_KEYS } = require('../consts');
4
+ const { wixData } = require('../elevated-modules');
5
+ const { getHeaders } = require('../pac-api-methods');
6
+ const { getSiteConfigs } = require('../utils');
7
+
8
+ const getInterests = async () => {
9
+ const [url, headers] = await Promise.all([
10
+ getSiteConfigs(CONFIG_KEYS.INTERESTS_API_URL),
11
+ getHeaders(),
12
+ ]);
13
+ const fetchOptions = {
14
+ method: 'get',
15
+ headers: headers,
16
+ };
17
+ try {
18
+ const response = await fetch(url, fetchOptions);
19
+ return await response.json();
20
+ } catch (e) {
21
+ console.error('Error getting interests:', e);
22
+ throw e;
23
+ }
24
+ };
25
+
26
+ async function migrateInterests() {
27
+ const [interests, _] = await Promise.all([
28
+ getInterests(),
29
+ clearCollection(COLLECTIONS.INTERESTS),
30
+ ]);
31
+ const interestData = interests.map(val => ({ title: val.interest }));
32
+ return await wixData.bulkInsert(COLLECTIONS.INTERESTS, interestData);
33
+ }
34
+
35
+ module.exports = {
36
+ migrateInterests,
37
+ };
package/backend/index.js CHANGED
@@ -9,7 +9,7 @@ module.exports = {
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
11
  ...require('./cms-data-methods'), //TODO: remove it once we finish NPM movement
12
- ...require('./routers'),
13
12
  ...require('./sso-methods'),
14
13
  ...require('./data-hooks'),
14
+ ...require('./http-functions'),
15
15
  };
package/backend/utils.js CHANGED
@@ -3,17 +3,18 @@ const { secrets } = require('@wix/secrets');
3
3
  const { site } = require('@wix/urls');
4
4
  const { encode } = require('ngeohash');
5
5
 
6
- const { COLLECTIONS, ADDRESS_STATUS_TYPES } = require('../public/consts');
7
- const { formatAddress, generateId } = require('../public/Utils/sharedUtils');
6
+ const { COLLECTIONS } = require('../public/consts');
8
7
 
9
- const { CONFIG_KEYS, GEO_HASH_PRECISION, MEMBERSHIPS_TYPES } = require('./consts');
8
+ const { CONFIG_KEYS, GEO_HASH_PRECISION } = require('./consts');
10
9
  const { wixData } = require('./elevated-modules');
11
10
  const { urlExists } = require('./members-data-methods');
12
11
  const elevatedGetSecretValue = elevate(secrets.getSecretValue);
13
12
 
14
13
  /**
15
14
  * Retrieves site configuration values from the database
16
- * @param {keyof typeof CONFIG_KEYS} [configKey] - The configuration key to retrieve
15
+ * @param {string} [configKey] - The configuration key to retrieve. Must be one of:
16
+ * - 'AUTOMATION_EMAIL_TRIGGER_ID' - Email template ID for triggered emails
17
+ * - 'SITE_ASSOCIATION' - Site association configuration
17
18
  * @returns {Promise<any>} The configuration value for the specified key, or all configs if no key provided
18
19
  * @example
19
20
  * // Get specific config
@@ -59,26 +60,18 @@ function formatDateToMonthYear(dateString) {
59
60
  return date.toLocaleDateString('en-US', options);
60
61
  }
61
62
 
62
- function hasStudentMembership({ member, checkAssociation = false, siteAssociation = null }) {
63
+ /**
64
+ * Check if member is a student
65
+ * @param {Object} member - The member object
66
+ * @returns {boolean} True if member has student membership
67
+ */
68
+ function isStudent(member) {
63
69
  const memberships = member?.memberships;
64
70
  if (!Array.isArray(memberships)) return false;
65
71
 
66
- return memberships.some(membership => {
67
- const isStudent = membership.membertype === MEMBERSHIPS_TYPES.STUDENT;
68
- const hasCorrectAssociation = !checkAssociation || membership.association === siteAssociation;
69
- return isStudent && hasCorrectAssociation;
70
- });
71
- }
72
-
73
- function isStudent(member) {
74
- return hasStudentMembership({ member, checkAssociation: false });
72
+ return memberships.some(membership => membership.membertype === 'student');
75
73
  }
76
74
 
77
- function isPAC_STAFF(member) {
78
- return Boolean(
79
- member?.memberships?.some(membership => membership.membertype === MEMBERSHIPS_TYPES.PAC_STAFF)
80
- );
81
- }
82
75
  /**
83
76
  * Get address display options for member
84
77
  * @param {Object} member - The member object
@@ -92,22 +85,7 @@ function getAddressDisplayOptions(member) {
92
85
  }
93
86
  return displayOptions;
94
87
  }
95
- function getAddressesByStatus(addresses = [], addressDisplayOption = []) {
96
- const visible = addresses.filter(addr => addr.addressStatus !== ADDRESS_STATUS_TYPES.DONT_SHOW);
97
- if (visible.length < 2) {
98
- return [];
99
- }
100
- const opts = Array.isArray(addressDisplayOption) ? addressDisplayOption : [];
101
- const mainOpt = opts.find(o => o.isMain);
102
- const mainKey = mainOpt ? mainOpt.key : visible[0].key; // fallback to the first visible if none marked
103
- return visible
104
- .filter(addr => addr?.key !== mainKey)
105
- .map(addr => {
106
- const addressString = formatAddress(addr);
107
- return addressString ? { _id: generateId(), address: addressString } : null;
108
- })
109
- .filter(Boolean);
110
- }
88
+
111
89
  const queryAllItems = async query => {
112
90
  console.log('start query');
113
91
  let oldResults = await query.find();
@@ -222,12 +200,9 @@ module.exports = {
222
200
  checkUrlUniqueness,
223
201
  formatDateToMonthYear,
224
202
  isStudent,
225
- hasStudentMembership,
226
203
  getAddressDisplayOptions,
227
204
  getSecret,
228
205
  getSiteBaseUrl,
229
206
  encodeXml,
230
207
  formatDateOnly,
231
- getAddressesByStatus,
232
- isPAC_STAFF,
233
208
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abmp-npm",
3
- "version": "1.1.63",
3
+ "version": "1.1.64",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -168,5 +168,4 @@ module.exports = {
168
168
  calculateDistance,
169
169
  toRadians,
170
170
  generateId,
171
- formatAddress,
172
171
  };
package/public/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  module.exports = {
2
2
  ...require('./consts'),
3
3
  ...require('./messages'),
4
- ...require('./Utils/sharedUtils'),
5
4
  };
@@ -1,3 +0,0 @@
1
- module.exports = {
2
- ...require('./methods'),
3
- };
@@ -1,177 +0,0 @@
1
- const { PAGES_PATHS } = require('../../public/consts');
2
- //const { fetchAllItemsInParallel } = require('../cms-data-methods'); unused at host site
3
- const { CONFIG_KEYS } = require('../consts');
4
- const { getSiteConfigs } = require('../utils');
5
-
6
- const { generateSEOTitle, stripHtmlTags, getMemberProfileData } = require('./utils');
7
-
8
- const createRoutersHandlers = wixRouterMethods => {
9
- const {
10
- redirect,
11
- ok,
12
- notFound,
13
- sendStatus,
14
- WixRouterSitemapEntry: _WixRouterSitemapEntry,
15
- } = wixRouterMethods;
16
-
17
- async function profileRouter(request) {
18
- const slug = request.path[0];
19
- if (!slug) {
20
- return redirect(request.baseUrl);
21
- }
22
- try {
23
- const siteConfigs = await getSiteConfigs();
24
- const siteAssociation = siteConfigs[CONFIG_KEYS.SITE_ASSOCIATION];
25
- const defaultSEODescription = siteConfigs[CONFIG_KEYS.DEFAULT_PROFILE_SEO_DESCRIPTION];
26
- const siteLogoUrl = siteConfigs[CONFIG_KEYS.SITE_LOGO_URL];
27
- const defaultProfileImage = siteConfigs[CONFIG_KEYS.DEFAULT_PROFILE_IMAGE];
28
- const profileData = await getMemberProfileData(slug, siteAssociation);
29
- if (profileData && profileData.showWixUrl) {
30
- const ogImage = profileData.profileImage || profileData.logoImage || siteLogoUrl;
31
- const seoTitle = generateSEOTitle({
32
- fullName: profileData.fullName,
33
- areasOfPractices: profileData.areasOfPractices,
34
- siteAssociation,
35
- });
36
- // Use stripped HTML from aboutService rich text content
37
- let description = stripHtmlTags(profileData.aboutService) || defaultSEODescription;
38
-
39
- // Limit description to 160 characters for optimal SEO
40
- if (description.length > 160) {
41
- description = description.substring(0, 157) + '...';
42
- }
43
- const profileUrl = `${request.baseUrl}/${PAGES_PATHS.PROFILE}/${profileData.url}`;
44
- const isPrivateMember = profileData.isPrivateMember;
45
- const seoData = {
46
- title: seoTitle,
47
- description: description,
48
- noIndex: isPrivateMember,
49
- metaTags: [
50
- {
51
- name: 'description',
52
- content: description,
53
- },
54
- {
55
- name: 'keywords',
56
- content:
57
- `${profileData.fullName}, ${profileData.areasOfPractices ? profileData.areasOfPractices.slice(0, 3).join(', ') : ''}, ${siteAssociation}, ${profileData.city || ''}, ${profileData.state || ''}`
58
- .replace(/,\s*,/g, ',')
59
- .replace(/^,|,$/g, ''),
60
- },
61
- {
62
- name: 'author',
63
- content: profileData.fullName,
64
- },
65
- {
66
- name: 'robots',
67
- content: isPrivateMember ? 'noindex, nofollow' : 'index, follow',
68
- },
69
- // Open Graph tags
70
- {
71
- property: 'og:type',
72
- content: 'profile',
73
- },
74
- {
75
- property: 'og:title',
76
- content: seoTitle,
77
- },
78
- {
79
- property: 'og:description',
80
- content: description,
81
- },
82
- {
83
- property: 'og:image',
84
- content: ogImage,
85
- },
86
- {
87
- property: 'og:url',
88
- content: profileUrl,
89
- },
90
- {
91
- property: 'og:site_name',
92
- content: `${siteAssociation} Members`,
93
- },
94
- // Twitter Card tags
95
- {
96
- name: 'twitter:card',
97
- content: 'summary_large_image',
98
- },
99
- {
100
- name: 'twitter:title',
101
- content: seoTitle,
102
- },
103
- {
104
- name: 'twitter:description',
105
- content: description,
106
- },
107
- {
108
- name: 'twitter:image',
109
- content: ogImage,
110
- },
111
- // Additional SEO tags
112
- {
113
- name: 'geo.region',
114
- content: profileData.state || '',
115
- },
116
- {
117
- name: 'geo.placename',
118
- content: profileData.city || '',
119
- },
120
- ].filter(tag => tag.content && tag.content.trim() !== ''), // Remove empty tags
121
- };
122
- return ok('profile', { ...profileData, defaultProfileImage }, seoData);
123
- }
124
- return notFound();
125
- } catch (error) {
126
- console.error(error);
127
- return sendStatus('500', 'Internal Server Error');
128
- }
129
- }
130
- function profileSiteMap(_sitemapRequest) {
131
- return [];
132
- // Commented out - currently disabled in host site
133
- // try {
134
- // const membersQuery = wixData
135
- // .query(COLLECTIONS.MEMBERS_DATA)
136
- // .eq('showWixUrl', true)
137
- // .isNotEmpty('url')
138
- // .ne('action', 'drop')
139
- // .fields('url', 'fullName');
140
-
141
- // const allMembers = await fetchAllItemsInParallel(membersQuery);
142
-
143
- // const batchSize = 1000;
144
- // const sitemapEntries = [];
145
- // const totalItems = allMembers.items.length;
146
-
147
- // for (let i = 0; i < totalItems; i += batchSize) {
148
- // const batch = allMembers.items.slice(i, i + batchSize);
149
- // const batchEntries = batch.map(member => {
150
- // const entry = new WixRouterSitemapEntry(member.fullName);
151
- // entry.pageName = 'profile';
152
- // entry.url = `${PAGES_PATHS.PROFILE}/${member.url}`;
153
- // entry.title = member.fullName;
154
- // entry.changeFrequency = 'monthly';
155
- // entry.priority = 1.0;
156
- // return entry;
157
- // });
158
- // sitemapEntries.push(...batchEntries);
159
- // }
160
-
161
- // return sitemapEntries;
162
- // } catch (error) {
163
- // console.error('Error generating profile sitemap:', error);
164
- // return [];
165
- // }
166
- }
167
- //Add other routers here
168
- return {
169
- profileRouter,
170
- profileSiteMap,
171
- //Add other routers here
172
- };
173
- };
174
-
175
- module.exports = {
176
- createRoutersHandlers,
177
- };
@@ -1,118 +0,0 @@
1
- const { getMainAddress } = require('../../public/Utils/sharedUtils');
2
- const { getMemberBySlug } = require('../members-data-methods');
3
- const {
4
- getAddressesByStatus,
5
- formatDateToMonthYear,
6
- hasStudentMembership,
7
- isPAC_STAFF,
8
- } = require('../utils');
9
-
10
- function generateSEOTitle({ fullName, areasOfPractices, siteAssociation }) {
11
- return `${fullName}${
12
- areasOfPractices && areasOfPractices.length > 0
13
- ? ` | ${areasOfPractices.slice(0, 3).join(', ')}`
14
- : ''
15
- } | ${siteAssociation} Member`;
16
- }
17
-
18
- function stripHtmlTags(html) {
19
- if (!html) return '';
20
- // Remove HTML tags and decode HTML entities
21
- return html
22
- .replace(/<[^>]*>/g, '') // Remove HTML tags
23
- .replace(/&nbsp;/g, ' ') // Replace non-breaking spaces
24
- .replace(/&amp;/g, '&') // Replace encoded ampersands
25
- .replace(/&lt;/g, '<') // Replace encoded less than
26
- .replace(/&gt;/g, '>') // Replace encoded greater than
27
- .replace(/&quot;/g, '"') // Replace encoded quotes
28
- .replace(/&#39;/g, "'") // Replace encoded apostrophes
29
- .replace(/\s+/g, ' ') // Replace multiple whitespace with single space
30
- .trim(); // Remove leading/trailing whitespace
31
- }
32
-
33
- function shouldHaveStudentBadge(member, siteAssociation) {
34
- return hasStudentMembership({
35
- member,
36
- checkAssociation: true,
37
- siteAssociation,
38
- });
39
- }
40
-
41
- function transformMemberToProfileData(member, siteAssociation) {
42
- if (!member) {
43
- throw new Error('member is required');
44
- }
45
- const addresses = member.addresses || [];
46
- const mainAddress = getMainAddress(member.addressDisplayOption, addresses);
47
- const licenceNo = member.licenses
48
- ?.map(val => val.license)
49
- .filter(Boolean)
50
- .join(', ');
51
- const processedAddresses = getAddressesByStatus(member.addresses, member.addressDisplayOption);
52
-
53
- const memberships = member.memberships || [];
54
- const siteAssociationMembership = memberships.find(m => m.association === siteAssociation);
55
-
56
- const areasOfPractices =
57
- member.areasOfPractices
58
- ?.filter(item => typeof item === 'string' && item.trim().length > 0)
59
- .map(item => item.trim())
60
- .sort((a, b) =>
61
- a.localeCompare(b, undefined, {
62
- sensitivity: 'base',
63
- numeric: true,
64
- })
65
- ) || [];
66
- return {
67
- mainAddress,
68
- testimonials: member.testimonial || [],
69
- licenceNo,
70
- processedAddresses,
71
- memberSince:
72
- (member.showABMP &&
73
- siteAssociationMembership &&
74
- formatDateToMonthYear(siteAssociationMembership?.membersince)) ||
75
- '',
76
- shouldHaveStudentBadge: shouldHaveStudentBadge(member, siteAssociation),
77
- logoImage: member.logoImage,
78
- fullName: member.fullName,
79
- profileImage: member.profileImage,
80
- showContactForm: member.showContactForm,
81
- bookingUrl: member.bookingUrl,
82
- aboutService: member.aboutService,
83
- businessName: (member.showBusinessName && member.businessName) || '',
84
- phone: member.toShowPhone || '',
85
- areasOfPractices,
86
- gallery: member.gallery,
87
- bannerImages: member.bannerImages,
88
- showWixUrl: member.showWixUrl,
89
- _id: member._id,
90
- url: member.url,
91
- isPrivateMember: isPAC_STAFF(member),
92
- };
93
- }
94
-
95
- const getMemberProfileData = async (slug, siteAssociation) => {
96
- try {
97
- const member = await getMemberBySlug({
98
- slug,
99
- excludeDropped: true,
100
- excludeSearchedMember: false,
101
- });
102
-
103
- if (!member) {
104
- return null;
105
- }
106
-
107
- return transformMemberToProfileData(member, siteAssociation);
108
- } catch (error) {
109
- console.error(error);
110
- throw error;
111
- }
112
- };
113
-
114
- module.exports = {
115
- generateSEOTitle,
116
- stripHtmlTags,
117
- getMemberProfileData,
118
- };