abmp-npm 1.1.135 → 1.1.136
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/__tests__/url-uniqueness.test.js +194 -0
- package/backend/daily-pull/bulk-process-methods.js +5 -14
- package/backend/daily-pull/process-member-methods.js +18 -4
- package/backend/daily-pull/utils.js +5 -16
- package/backend/jobs.js +15 -0
- package/backend/members-data-methods.js +7 -3
- package/backend/routers/methods.js +4 -2
- package/backend/routers/utils.js +6 -1
- package/backend/tasks/address-primary-methods.js +256 -0
- package/backend/tasks/consts.js +2 -0
- package/backend/tasks/index.js +1 -0
- package/backend/tasks/tasks-configs.js +18 -0
- package/backend/tasks/tasks-helpers-methods.js +1 -1
- package/backend/utils.js +10 -0
- package/dev-only-scripts/extract-duplicate-url-groups.js +201 -0
- package/dev-only-scripts/find-duplicate-ids.js +159 -0
- package/package.json +7 -4
- package/pages/Home.js +1 -1
- package/pages/LoadingPage.js +12 -3
- package/pages/personalDetails.js +69 -12
- package/public/Utils/homePage.js +90 -31
- package/public/Utils/sharedUtils.js +8 -3
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
const { incrementUrlCounter, extractBaseUrl } = require('../daily-pull/utils');
|
|
2
|
+
const {
|
|
3
|
+
normalizeUrlForComparison,
|
|
4
|
+
sortByUrlCounterDescending,
|
|
5
|
+
extractUrlCounter,
|
|
6
|
+
} = require('../utils');
|
|
7
|
+
|
|
8
|
+
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Simulates getMemberBySlug's normalized-comparison branch using
|
|
12
|
+
* the ACTUAL production sort comparator imported from utils.js.
|
|
13
|
+
*/
|
|
14
|
+
function simulateGetHighestMember(allMembers, slug) {
|
|
15
|
+
const matching = allMembers.filter(
|
|
16
|
+
m => m.url && normalizeUrlForComparison(m.url) === slug.toLowerCase()
|
|
17
|
+
);
|
|
18
|
+
matching.sort(sortByUrlCounterDescending);
|
|
19
|
+
return matching[0] || null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Simulates ensureUniqueUrl's counter-increment logic given the
|
|
24
|
+
* "highest" member returned by getMemberBySlug.
|
|
25
|
+
*/
|
|
26
|
+
function simulateEnsureUniqueUrl(baseSlug, highestMember) {
|
|
27
|
+
if (!highestMember || !highestMember.url) return baseSlug;
|
|
28
|
+
return incrementUrlCounter(highestMember?.url, baseSlug);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─── Test data ───────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
function buildMembersInDb() {
|
|
34
|
+
return [
|
|
35
|
+
{ memberId: 1, url: 'firstNameLastName' },
|
|
36
|
+
{ memberId: 2, url: 'firstNameLastName-1' },
|
|
37
|
+
{ memberId: 3, url: 'firstNameLastName-2' },
|
|
38
|
+
{ memberId: 4, url: 'firstNameLastName-3' },
|
|
39
|
+
{ memberId: 5, url: 'firstNameLastName-4' },
|
|
40
|
+
{ memberId: 6, url: 'firstNameLastName-5' },
|
|
41
|
+
{ memberId: 7, url: 'firstNameLastName-6' },
|
|
42
|
+
{ memberId: 8, url: 'firstNameLastName-7' },
|
|
43
|
+
{ memberId: 9, url: 'firstNameLastName-8' },
|
|
44
|
+
{ memberId: 10, url: 'firstNameLastName-9' },
|
|
45
|
+
{ memberId: 11, url: 'firstNameLastName-10' },
|
|
46
|
+
{ memberId: 12, url: 'firstNameLastName-11' },
|
|
47
|
+
{ memberId: 13, url: 'firstNameLastName-12' },
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildLargeCounterMembers() {
|
|
52
|
+
const members = [];
|
|
53
|
+
for (let i = 0; i <= 105; i++) {
|
|
54
|
+
members.push({ memberId: i, url: i === 0 ? 'testUser' : `testUser-${i}` });
|
|
55
|
+
}
|
|
56
|
+
return members;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─── Tests ───────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
describe('Production sort: getMemberBySlug must return the HIGHEST counter', () => {
|
|
62
|
+
test('should return -12 as the highest URL (not -9)', () => {
|
|
63
|
+
const members = buildMembersInDb();
|
|
64
|
+
const highest = simulateGetHighestMember(members, 'firstnamelastname');
|
|
65
|
+
|
|
66
|
+
expect(highest.url).toBe('firstNameLastName-12');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('should sort -12 above -9 in descending order', () => {
|
|
70
|
+
const members = buildMembersInDb();
|
|
71
|
+
const sorted = [...members]
|
|
72
|
+
.filter(m => normalizeUrlForComparison(m.url) === 'firstnamelastname')
|
|
73
|
+
.sort(sortByUrlCounterDescending);
|
|
74
|
+
const sortedUrls = sorted.map(m => m.url);
|
|
75
|
+
|
|
76
|
+
const indexOf9 = sortedUrls.indexOf('firstNameLastName-9');
|
|
77
|
+
const indexOf12 = sortedUrls.indexOf('firstNameLastName-12');
|
|
78
|
+
|
|
79
|
+
expect(indexOf12).toBeLessThan(indexOf9);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('should handle large counters (100+) correctly', () => {
|
|
83
|
+
const members = buildLargeCounterMembers();
|
|
84
|
+
const highest = simulateGetHighestMember(members, 'testuser');
|
|
85
|
+
|
|
86
|
+
expect(highest.url).toBe('testUser-105');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('should handle hyphenated names (mary-jane) correctly', () => {
|
|
90
|
+
const members = [
|
|
91
|
+
{ memberId: 1, url: 'mary-jane' },
|
|
92
|
+
{ memberId: 2, url: 'mary-jane-1' },
|
|
93
|
+
{ memberId: 3, url: 'mary-jane-2' },
|
|
94
|
+
{ memberId: 4, url: 'mary-jane-10' },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const highest = simulateGetHighestMember(members, 'mary-jane');
|
|
98
|
+
expect(highest.url).toBe('mary-jane-10');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('Production sort: ensureUniqueUrl must generate a truly unique URL', () => {
|
|
103
|
+
test('should generate -13 (not -10) when DB has URLs up to -12', () => {
|
|
104
|
+
const members = buildMembersInDb();
|
|
105
|
+
const highest = simulateGetHighestMember(members, 'firstnamelastname');
|
|
106
|
+
const newUrl = simulateEnsureUniqueUrl('firstNameLastName', highest);
|
|
107
|
+
|
|
108
|
+
expect(newUrl).toBe('firstNameLastName-13');
|
|
109
|
+
|
|
110
|
+
const existingUrls = members.map(m => m.url);
|
|
111
|
+
expect(existingUrls).not.toContain(newUrl);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('repeated daily pulls should produce sequential unique URLs', () => {
|
|
115
|
+
const members = buildMembersInDb();
|
|
116
|
+
const generatedUrls = [];
|
|
117
|
+
|
|
118
|
+
for (let day = 0; day < 5; day++) {
|
|
119
|
+
const highest = simulateGetHighestMember(members, 'firstnamelastname');
|
|
120
|
+
const newUrl = simulateEnsureUniqueUrl('firstNameLastName', highest);
|
|
121
|
+
generatedUrls.push(newUrl);
|
|
122
|
+
members.push({ memberId: 100 + day, url: newUrl });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
expect(new Set(generatedUrls).size).toBe(5);
|
|
126
|
+
expect(generatedUrls).toEqual([
|
|
127
|
+
'firstNameLastName-13',
|
|
128
|
+
'firstNameLastName-14',
|
|
129
|
+
'firstNameLastName-15',
|
|
130
|
+
'firstNameLastName-16',
|
|
131
|
+
'firstNameLastName-17',
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('should NOT produce duplicates (the -10 bug)', () => {
|
|
136
|
+
const members = buildMembersInDb();
|
|
137
|
+
const highest = simulateGetHighestMember(members, 'firstnamelastname');
|
|
138
|
+
const newUrl = simulateEnsureUniqueUrl('firstNameLastName', highest);
|
|
139
|
+
|
|
140
|
+
expect(newUrl).not.toBe('firstNameLastName-10');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('Production sort: ensureUniqueUrlsInBatch single-member path', () => {
|
|
145
|
+
test('incrementUrlCounter should produce a unique URL from highest DB member', () => {
|
|
146
|
+
const members = buildMembersInDb();
|
|
147
|
+
const dbMember = simulateGetHighestMember(members, 'firstnamelastname');
|
|
148
|
+
const result = incrementUrlCounter(dbMember.url, 'firstNameLastName');
|
|
149
|
+
|
|
150
|
+
expect(result).toBe('firstNameLastName-13');
|
|
151
|
+
|
|
152
|
+
const existingUrls = members.map(m => m.url);
|
|
153
|
+
expect(existingUrls).not.toContain(result);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('Utility functions', () => {
|
|
158
|
+
test('normalizeUrlForComparison strips trailing counter', () => {
|
|
159
|
+
expect(normalizeUrlForComparison('firstNameLastName')).toBe('firstnamelastname');
|
|
160
|
+
expect(normalizeUrlForComparison('firstNameLastName-1')).toBe('firstnamelastname');
|
|
161
|
+
expect(normalizeUrlForComparison('firstNameLastName-9')).toBe('firstnamelastname');
|
|
162
|
+
expect(normalizeUrlForComparison('firstNameLastName-10')).toBe('firstnamelastname');
|
|
163
|
+
expect(normalizeUrlForComparison('firstNameLastName-100')).toBe('firstnamelastname');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('extractUrlCounter returns numeric counter', () => {
|
|
167
|
+
expect(extractUrlCounter('firstNameLastName')).toBe(-1);
|
|
168
|
+
expect(extractUrlCounter('firstNameLastName-1')).toBe(1);
|
|
169
|
+
expect(extractUrlCounter('firstNameLastName-9')).toBe(9);
|
|
170
|
+
expect(extractUrlCounter('firstNameLastName-10')).toBe(10);
|
|
171
|
+
expect(extractUrlCounter('firstNameLastName-100')).toBe(100);
|
|
172
|
+
expect(extractUrlCounter(null)).toBe(-1);
|
|
173
|
+
expect(extractUrlCounter('')).toBe(-1);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('extractBaseUrl strips numeric counter', () => {
|
|
177
|
+
expect(extractBaseUrl('firstNameLastName-10')).toBe('firstNameLastName');
|
|
178
|
+
expect(extractBaseUrl('firstNameLastName-1')).toBe('firstNameLastName');
|
|
179
|
+
expect(extractBaseUrl('firstNameLastName')).toBe('firstNameLastName');
|
|
180
|
+
expect(extractBaseUrl('john-doe-3')).toBe('john-doe');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('incrementUrlCounter increments correctly', () => {
|
|
184
|
+
expect(incrementUrlCounter('firstNameLastName-9', 'firstNameLastName')).toBe(
|
|
185
|
+
'firstNameLastName-10'
|
|
186
|
+
);
|
|
187
|
+
expect(incrementUrlCounter('firstNameLastName-10', 'firstNameLastName')).toBe(
|
|
188
|
+
'firstNameLastName-11'
|
|
189
|
+
);
|
|
190
|
+
expect(incrementUrlCounter('firstNameLastName', 'firstNameLastName')).toBe(
|
|
191
|
+
'firstNameLastName-1'
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
const { bulkSaveMembers, getMemberBySlug } = require('../members-data-methods');
|
|
2
|
+
const { extractUrlCounter } = require('../utils');
|
|
2
3
|
|
|
3
4
|
const { generateUpdatedMemberData } = require('./process-member-methods');
|
|
4
|
-
const {
|
|
5
|
-
changeWixMembersEmails,
|
|
6
|
-
extractUrlCounter,
|
|
7
|
-
incrementUrlCounter,
|
|
8
|
-
extractBaseUrl,
|
|
9
|
-
} = require('./utils');
|
|
5
|
+
const { changeWixMembersEmails, incrementUrlCounter, extractBaseUrl } = require('./utils');
|
|
10
6
|
|
|
11
7
|
/**
|
|
12
8
|
* Ensures unique URLs within a batch of members by deduplicating URLs
|
|
@@ -80,14 +76,9 @@ async function ensureUniqueUrlsInBatch(memberDataList) {
|
|
|
80
76
|
let batchMaxCounter = -1;
|
|
81
77
|
members.forEach(member => {
|
|
82
78
|
const originalUrl = member.url;
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (isNumeric) {
|
|
87
|
-
const counter = parseInt(lastSegment, 10);
|
|
88
|
-
if (counter > batchMaxCounter) {
|
|
89
|
-
batchMaxCounter = counter;
|
|
90
|
-
}
|
|
79
|
+
const urlCounter = extractUrlCounter(originalUrl);
|
|
80
|
+
if (urlCounter > batchMaxCounter) {
|
|
81
|
+
batchMaxCounter = urlCounter;
|
|
91
82
|
}
|
|
92
83
|
});
|
|
93
84
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
const { ADDRESS_STATUS_TYPES } = require('../../public/consts');
|
|
1
2
|
const { findMemberById, getMemberBySlug } = require('../members-data-methods');
|
|
2
3
|
const { isValidArray, generateGeoHash } = require('../utils');
|
|
3
4
|
|
|
4
5
|
const { MEMBER_ACTIONS, DEFAULT_MEMBER_DISPLAY_SETTINGS } = require('./consts');
|
|
6
|
+
const { incrementUrlCounter } = require('./utils');
|
|
5
7
|
const { validateCoreMemberData, containsNonEnglish, createFullName } = require('./utils');
|
|
6
8
|
|
|
7
9
|
/**
|
|
@@ -43,9 +45,7 @@ const ensureUniqueUrl = async ({ url, memberId, fullName }) => {
|
|
|
43
45
|
console.log(
|
|
44
46
|
`Found member with same url ${existingMember.url} for memberId ${memberId} and URL ${uniqueUrl}, increasing counter by 1`
|
|
45
47
|
);
|
|
46
|
-
|
|
47
|
-
const lastCounter = parseInt(lastSegment, 10) || 0;
|
|
48
|
-
uniqueUrl = `${uniqueUrl}-${lastCounter + 1}`;
|
|
48
|
+
uniqueUrl = incrementUrlCounter(existingMember.url, uniqueUrl);
|
|
49
49
|
}
|
|
50
50
|
if (uniqueUrl !== baseUrl) {
|
|
51
51
|
console.log(`URL conflict resolved: ${baseUrl} -> ${uniqueUrl} for member ${memberId}`);
|
|
@@ -82,7 +82,21 @@ async function generateUpdatedMemberData({ inputMemberData, currentPageNumber })
|
|
|
82
82
|
|
|
83
83
|
// Only add address data for new members
|
|
84
84
|
if (!existingDbMember && isValidArray(inputMemberData.addresses)) {
|
|
85
|
-
|
|
85
|
+
const normalizedAddresses = inputMemberData.addresses.map((address, index) => {
|
|
86
|
+
const key = address?.key || `address_${index}`;
|
|
87
|
+
const rawStatus = address?.addressStatus;
|
|
88
|
+
const resolvedStatus =
|
|
89
|
+
index === 0 && rawStatus === ADDRESS_STATUS_TYPES.DONT_SHOW
|
|
90
|
+
? ADDRESS_STATUS_TYPES.STATE_CITY_ZIP
|
|
91
|
+
: rawStatus || ADDRESS_STATUS_TYPES.STATE_CITY_ZIP;
|
|
92
|
+
return {
|
|
93
|
+
...address,
|
|
94
|
+
key,
|
|
95
|
+
addressStatus: resolvedStatus,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
updatedMemberData.addresses = normalizedAddresses;
|
|
99
|
+
updatedMemberData.addressDisplayOption = [{ key: normalizedAddresses[0].key, isMain: true }];
|
|
86
100
|
}
|
|
87
101
|
|
|
88
102
|
return { ...updatedMemberData, isNewToDb: !existingDbMember };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { updateWixMemberLoginEmail } = require('../members-area-methods');
|
|
2
|
+
const { extractUrlCounter } = require('../utils');
|
|
2
3
|
|
|
3
4
|
const { MEMBER_ACTIONS } = require('./consts');
|
|
4
5
|
|
|
@@ -15,21 +16,12 @@ const changeWixMembersEmails = async toChangeWixMembersEmails => {
|
|
|
15
16
|
);
|
|
16
17
|
};
|
|
17
18
|
|
|
18
|
-
const extractUrlCounter = url => {
|
|
19
|
-
if (!url) return -1;
|
|
20
|
-
const lastSegment = url.split('-').pop() || '0';
|
|
21
|
-
const isNumeric = /^\d+$/.test(lastSegment);
|
|
22
|
-
return isNumeric ? parseInt(lastSegment, 10) : -1;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
19
|
const extractBaseUrl = url => {
|
|
26
20
|
if (!url) return url;
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const isNumeric = /^\d+$/.test(lastSegment);
|
|
30
|
-
if (isNumeric && urlParts.length > 1) {
|
|
21
|
+
const lastCounter = extractUrlCounter(url);
|
|
22
|
+
if (lastCounter > 0) {
|
|
31
23
|
// Remove the numeric counter to get the base URL
|
|
32
|
-
return
|
|
24
|
+
return url.split('-').slice(0, -1).join('-');
|
|
33
25
|
}
|
|
34
26
|
// No counter found, return the URL as-is
|
|
35
27
|
return url;
|
|
@@ -49,9 +41,7 @@ const incrementUrlCounter = (existingUrl, baseUrl) => {
|
|
|
49
41
|
console.log(
|
|
50
42
|
`Found member with same url ${existingUrl} for baseUrl ${baseUrl}, increasing counter by 1`
|
|
51
43
|
);
|
|
52
|
-
const
|
|
53
|
-
const isNumeric = /^\d+$/.test(lastSegment);
|
|
54
|
-
const lastCounter = isNumeric ? parseInt(lastSegment, 10) : 0;
|
|
44
|
+
const lastCounter = Math.max(0, extractUrlCounter(existingUrl));
|
|
55
45
|
return `${baseUrl}-${lastCounter + 1}`;
|
|
56
46
|
}
|
|
57
47
|
|
|
@@ -118,7 +108,6 @@ module.exports = {
|
|
|
118
108
|
validateCoreMemberData,
|
|
119
109
|
containsNonEnglish,
|
|
120
110
|
createFullName,
|
|
121
|
-
extractUrlCounter,
|
|
122
111
|
incrementUrlCounter,
|
|
123
112
|
extractBaseUrl,
|
|
124
113
|
};
|
package/backend/jobs.js
CHANGED
|
@@ -68,6 +68,20 @@ async function scheduleCreateContactsFromMembersTask() {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
async function scheduleFixPrimaryAddressForMembersTask() {
|
|
72
|
+
try {
|
|
73
|
+
console.log('scheduleFixPrimaryAddressForMembers started!');
|
|
74
|
+
return await taskManager().schedule({
|
|
75
|
+
name: TASKS_NAMES.scheduleFixPrimaryAddressForMembers,
|
|
76
|
+
data: {},
|
|
77
|
+
type: 'scheduled',
|
|
78
|
+
});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(`Failed to scheduleFixPrimaryAddressForMembers: ${error.message}`);
|
|
81
|
+
throw new Error(`Failed to scheduleFixPrimaryAddressForMembers: ${error.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
71
85
|
async function updateSiteMapS3() {
|
|
72
86
|
try {
|
|
73
87
|
return await taskManager().schedule({
|
|
@@ -85,4 +99,5 @@ module.exports = {
|
|
|
85
99
|
scheduleDailyPullTask,
|
|
86
100
|
updateSiteMapS3,
|
|
87
101
|
scheduleCreateContactsFromMembersTask,
|
|
102
|
+
scheduleFixPrimaryAddressForMembersTask,
|
|
88
103
|
};
|
|
@@ -10,6 +10,7 @@ const { createSiteMember, getCurrentMember } = require('./members-area-methods')
|
|
|
10
10
|
const {
|
|
11
11
|
chunkArray,
|
|
12
12
|
normalizeUrlForComparison,
|
|
13
|
+
sortByUrlCounterDescending,
|
|
13
14
|
queryAllItems,
|
|
14
15
|
generateGeoHash,
|
|
15
16
|
searchAllItems,
|
|
@@ -176,7 +177,7 @@ async function getMemberBySlug({
|
|
|
176
177
|
//remove trailing "-1", "-2", etc.
|
|
177
178
|
item => item.url && normalizeUrlForComparison(item.url) === slug.toLowerCase()
|
|
178
179
|
)
|
|
179
|
-
.sort(
|
|
180
|
+
.sort(sortByUrlCounterDescending);
|
|
180
181
|
}
|
|
181
182
|
if (matchingMembers.length > 1) {
|
|
182
183
|
const queryResultMsg = `Multiple members found with same slug ${slug} membersIds are : [${matchingMembers
|
|
@@ -410,7 +411,8 @@ async function getMembersWithWixUrl() {
|
|
|
410
411
|
.eq('showWixUrl', true)
|
|
411
412
|
.ne('action', MEMBER_ACTIONS.DROP)
|
|
412
413
|
.ne('memberships.membertype', MEMBERSHIPS_TYPES.PAC_STAFF)
|
|
413
|
-
.
|
|
414
|
+
.ne('memberships.membertype', MEMBERSHIPS_TYPES.STUDENT)
|
|
415
|
+
//.isNotEmpty('url') - not used because it's not working as expected
|
|
414
416
|
.limit(1000);
|
|
415
417
|
let currentResults = await membersQuery.find();
|
|
416
418
|
let i = 0;
|
|
@@ -422,7 +424,9 @@ async function getMembersWithWixUrl() {
|
|
|
422
424
|
i++;
|
|
423
425
|
}
|
|
424
426
|
console.log('i is ', i);
|
|
425
|
-
const filtered = allItems.filter(
|
|
427
|
+
const filtered = allItems.filter(
|
|
428
|
+
item => typeof item.url === 'string' && !item.url.includes('/') && item.url !== ''
|
|
429
|
+
);
|
|
426
430
|
console.log('filtered is ', filtered.length);
|
|
427
431
|
return filtered;
|
|
428
432
|
}
|
|
@@ -47,10 +47,12 @@ const createRoutersHandlers = wixRouterMethods => {
|
|
|
47
47
|
}
|
|
48
48
|
const profileUrl = `${request.baseUrl}/${PAGES_PATHS.PROFILE}/${profileData.url}`;
|
|
49
49
|
const isPrivateMember = profileData.isPrivateMember;
|
|
50
|
+
const isStudent = profileData.shouldHaveStudentBadge;
|
|
51
|
+
const shouldNoIndex = isPrivateMember || isStudent;
|
|
50
52
|
const seoData = {
|
|
51
53
|
title: seoTitle,
|
|
52
54
|
description: description,
|
|
53
|
-
noIndex:
|
|
55
|
+
noIndex: shouldNoIndex,
|
|
54
56
|
metaTags: [
|
|
55
57
|
{
|
|
56
58
|
name: 'description',
|
|
@@ -69,7 +71,7 @@ const createRoutersHandlers = wixRouterMethods => {
|
|
|
69
71
|
},
|
|
70
72
|
{
|
|
71
73
|
name: 'robots',
|
|
72
|
-
content:
|
|
74
|
+
content: shouldNoIndex ? 'noindex, nofollow' : 'index, follow',
|
|
73
75
|
},
|
|
74
76
|
// Open Graph tags
|
|
75
77
|
{
|
package/backend/routers/utils.js
CHANGED
|
@@ -66,6 +66,11 @@ function transformMemberToProfileData(member, siteAssociation) {
|
|
|
66
66
|
numeric: true,
|
|
67
67
|
})
|
|
68
68
|
) || [];
|
|
69
|
+
|
|
70
|
+
const phones = Array.isArray(member.phones) ? member.phones : [];
|
|
71
|
+
const toShowPhone = member.toShowPhone || '';
|
|
72
|
+
const phone = toShowPhone && phones.includes(toShowPhone) ? toShowPhone : '';
|
|
73
|
+
|
|
69
74
|
return {
|
|
70
75
|
mainAddress,
|
|
71
76
|
testimonials: member.testimonial || [],
|
|
@@ -84,7 +89,7 @@ function transformMemberToProfileData(member, siteAssociation) {
|
|
|
84
89
|
bookingUrl: member.bookingUrl,
|
|
85
90
|
aboutService: member.aboutService,
|
|
86
91
|
businessName: (member.showBusinessName && member.businessName) || '',
|
|
87
|
-
phone
|
|
92
|
+
phone,
|
|
88
93
|
areasOfPractices,
|
|
89
94
|
gallery: member.gallery,
|
|
90
95
|
bannerImages: member.bannerImages,
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
const { taskManager } = require('psdev-task-manager');
|
|
2
|
+
|
|
3
|
+
const { COLLECTIONS } = require('../../public/consts');
|
|
4
|
+
const { wixData } = require('../elevated-modules');
|
|
5
|
+
const { bulkSaveMembers, getMembersByIds } = require('../members-data-methods');
|
|
6
|
+
const { chunkArray, queryAllItems } = require('../utils');
|
|
7
|
+
|
|
8
|
+
const { TASKS_NAMES } = require('./consts');
|
|
9
|
+
|
|
10
|
+
const CHUNK_SIZE = 1000;
|
|
11
|
+
|
|
12
|
+
const getAddressKey = (address, index) =>
|
|
13
|
+
address?.key || address?.addressid || address?.addressId || `address_${index}`;
|
|
14
|
+
|
|
15
|
+
const hasPrimaryAddress = addressDisplayOption =>
|
|
16
|
+
Array.isArray(addressDisplayOption) &&
|
|
17
|
+
addressDisplayOption.some(option => option?.isMain === true);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Schedules tasks to fix members with multiple addresses and no primary address.
|
|
21
|
+
*/
|
|
22
|
+
async function scheduleFixPrimaryAddressForMembers() {
|
|
23
|
+
console.log('=== Scheduling Fix Primary Address Tasks ===');
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const membersQuery = await wixData
|
|
27
|
+
.query(COLLECTIONS.MEMBERS_DATA)
|
|
28
|
+
.isNotEmpty('addresses')
|
|
29
|
+
.limit(1000);
|
|
30
|
+
const members = await queryAllItems(membersQuery);
|
|
31
|
+
console.log(`Fetched ${members.length} members with addresses`);
|
|
32
|
+
|
|
33
|
+
const membersToFix = members.filter(member => {
|
|
34
|
+
const addresses = Array.isArray(member.addresses) ? member.addresses : [];
|
|
35
|
+
if (addresses.length === 0) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return !hasPrimaryAddress(member.addressDisplayOption);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const memberIds = [
|
|
42
|
+
...new Set(
|
|
43
|
+
membersToFix
|
|
44
|
+
.map(member => Number(member.memberId))
|
|
45
|
+
.filter(memberId => Number.isFinite(memberId) && memberId > 0)
|
|
46
|
+
),
|
|
47
|
+
];
|
|
48
|
+
console.log(`Members with addresses and no primary: ${memberIds.length}`);
|
|
49
|
+
|
|
50
|
+
if (memberIds.length === 0) {
|
|
51
|
+
console.log('No members need primary address fixes');
|
|
52
|
+
return {
|
|
53
|
+
success: true,
|
|
54
|
+
message: 'No members need primary address fixes',
|
|
55
|
+
totalMembers: 0,
|
|
56
|
+
tasksScheduled: 0,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const chunks = chunkArray(memberIds, CHUNK_SIZE);
|
|
61
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
62
|
+
const chunk = chunks[i];
|
|
63
|
+
const task = {
|
|
64
|
+
name: TASKS_NAMES.fixPrimaryAddressChunk,
|
|
65
|
+
data: {
|
|
66
|
+
memberIds: chunk,
|
|
67
|
+
chunkIndex: i,
|
|
68
|
+
totalChunks: chunks.length,
|
|
69
|
+
},
|
|
70
|
+
type: 'scheduled',
|
|
71
|
+
};
|
|
72
|
+
await taskManager().schedule(task);
|
|
73
|
+
console.log(`Scheduled task ${i + 1}/${chunks.length} (${chunk.length} members)`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const result = {
|
|
77
|
+
success: true,
|
|
78
|
+
message: `Scheduled ${chunks.length} tasks for ${memberIds.length} members`,
|
|
79
|
+
totalMembers: memberIds.length,
|
|
80
|
+
tasksScheduled: chunks.length,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
console.log('=== Scheduling Complete ===');
|
|
84
|
+
console.log(`Sample memberIds: ${memberIds.slice(0, 10).join(', ')}`);
|
|
85
|
+
console.log(JSON.stringify(result, null, 2));
|
|
86
|
+
|
|
87
|
+
return result;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('Error scheduling primary address fix:', error);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Processes a chunk of members and sets the first address as primary
|
|
96
|
+
* when a member has multiple addresses and no primary address.
|
|
97
|
+
*/
|
|
98
|
+
async function fixPrimaryAddressChunk(data) {
|
|
99
|
+
const { memberIds, chunkIndex, totalChunks } = data;
|
|
100
|
+
console.log(
|
|
101
|
+
`Processing primary address fix chunk ${chunkIndex + 1}/${totalChunks} (${memberIds.length} members)`
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const result = {
|
|
105
|
+
successful: 0,
|
|
106
|
+
failed: 0,
|
|
107
|
+
skipped: 0,
|
|
108
|
+
errors: [],
|
|
109
|
+
skippedIds: [],
|
|
110
|
+
failedIds: [],
|
|
111
|
+
};
|
|
112
|
+
const skippedNoAddress = [];
|
|
113
|
+
const skippedHasPrimary = [];
|
|
114
|
+
const updatedIds = [];
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const members = await getMembersByIds(memberIds);
|
|
118
|
+
console.log(`Loaded ${members.length} members for this chunk`);
|
|
119
|
+
const membersToUpdate = [];
|
|
120
|
+
|
|
121
|
+
members.forEach(member => {
|
|
122
|
+
const addresses = Array.isArray(member.addresses) ? member.addresses : [];
|
|
123
|
+
if (addresses.length === 0) {
|
|
124
|
+
result.skipped++;
|
|
125
|
+
result.skippedIds.push(member.memberId);
|
|
126
|
+
if (skippedNoAddress.length < 20) {
|
|
127
|
+
skippedNoAddress.push(member.memberId);
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (hasPrimaryAddress(member.addressDisplayOption)) {
|
|
132
|
+
result.skipped++;
|
|
133
|
+
result.skippedIds.push(member.memberId);
|
|
134
|
+
if (skippedHasPrimary.length < 20) {
|
|
135
|
+
skippedHasPrimary.push(member.memberId);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const firstKey = getAddressKey(addresses[0], 0);
|
|
141
|
+
const normalizedAddresses = addresses.map((address, index) => {
|
|
142
|
+
if (index !== 0 || address?.key) {
|
|
143
|
+
return address;
|
|
144
|
+
}
|
|
145
|
+
return { ...address, key: firstKey };
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const updatedDisplayOptions = Array.isArray(member.addressDisplayOption)
|
|
149
|
+
? member.addressDisplayOption.map(option => ({
|
|
150
|
+
...option,
|
|
151
|
+
isMain: false,
|
|
152
|
+
}))
|
|
153
|
+
: [];
|
|
154
|
+
|
|
155
|
+
const existingOption = updatedDisplayOptions.find(option => option?.key === firstKey);
|
|
156
|
+
if (existingOption) {
|
|
157
|
+
existingOption.isMain = true;
|
|
158
|
+
} else {
|
|
159
|
+
updatedDisplayOptions.push({ key: firstKey, isMain: true });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
membersToUpdate.push({
|
|
163
|
+
...member,
|
|
164
|
+
addresses: normalizedAddresses,
|
|
165
|
+
addressDisplayOption: updatedDisplayOptions,
|
|
166
|
+
});
|
|
167
|
+
if (updatedIds.length < 20) {
|
|
168
|
+
updatedIds.push(member.memberId);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (membersToUpdate.length === 0) {
|
|
173
|
+
console.log('No members need updating in this batch');
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
await bulkSaveMembers(membersToUpdate);
|
|
179
|
+
result.successful += membersToUpdate.length;
|
|
180
|
+
console.log(`✅ Successfully updated ${membersToUpdate.length} members`);
|
|
181
|
+
if (updatedIds.length > 0) {
|
|
182
|
+
console.log(`Updated memberIds (sample): ${updatedIds.join(', ')}`);
|
|
183
|
+
}
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error('❌ Error bulk saving members:', error);
|
|
186
|
+
result.failed += membersToUpdate.length;
|
|
187
|
+
result.failedIds.push(...membersToUpdate.map(member => member.memberId));
|
|
188
|
+
result.errors.push({
|
|
189
|
+
error: error.message,
|
|
190
|
+
memberCount: membersToUpdate.length,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (skippedNoAddress.length > 0) {
|
|
195
|
+
console.log(`Skipped (no addresses) sample: ${skippedNoAddress.join(', ')}`);
|
|
196
|
+
}
|
|
197
|
+
if (skippedHasPrimary.length > 0) {
|
|
198
|
+
console.log(`Skipped (already has primary) sample: ${skippedHasPrimary.join(', ')}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return result;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error(`Error processing primary address fix chunk ${chunkIndex}:`, error);
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Returns count of members with addresses but no primary address.
|
|
210
|
+
*/
|
|
211
|
+
async function countMembersMissingPrimaryAddress() {
|
|
212
|
+
console.log('=== Counting Members Missing Primary Address ===');
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const membersQuery = await wixData
|
|
216
|
+
.query(COLLECTIONS.MEMBERS_DATA)
|
|
217
|
+
.isNotEmpty('addresses')
|
|
218
|
+
.limit(1000);
|
|
219
|
+
const members = await queryAllItems(membersQuery);
|
|
220
|
+
console.log(`Fetched ${members.length} members with addresses`);
|
|
221
|
+
|
|
222
|
+
const membersToFix = members.filter(member => {
|
|
223
|
+
const addresses = Array.isArray(member.addresses) ? member.addresses : [];
|
|
224
|
+
if (addresses.length === 0) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
return !hasPrimaryAddress(member.addressDisplayOption);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const memberIds = [
|
|
231
|
+
...new Set(
|
|
232
|
+
membersToFix
|
|
233
|
+
.map(member => Number(member.memberId))
|
|
234
|
+
.filter(memberId => Number.isFinite(memberId) && memberId > 0)
|
|
235
|
+
),
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
const result = {
|
|
239
|
+
success: true,
|
|
240
|
+
totalMembers: memberIds.length,
|
|
241
|
+
sampleMemberIds: memberIds.slice(0, 20),
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
console.log(JSON.stringify(result, null, 2));
|
|
245
|
+
return result;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.error('Error counting members missing primary address:', error);
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = {
|
|
253
|
+
scheduleFixPrimaryAddressForMembers,
|
|
254
|
+
fixPrimaryAddressChunk,
|
|
255
|
+
countMembersMissingPrimaryAddress,
|
|
256
|
+
};
|