abmp-npm 2.0.49 → 2.0.52
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 +2 -3
- package/backend/daily-pull/utils.js +5 -16
- package/backend/members-data-methods.js +6 -3
- package/backend/tasks/address-primary-methods.js +53 -8
- 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/personalDetails.js +1 -1
|
@@ -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
|
|
|
@@ -3,6 +3,7 @@ const { findMemberById, getMemberBySlug } = require('../members-data-methods');
|
|
|
3
3
|
const { isValidArray, generateGeoHash } = require('../utils');
|
|
4
4
|
|
|
5
5
|
const { MEMBER_ACTIONS, DEFAULT_MEMBER_DISPLAY_SETTINGS } = require('./consts');
|
|
6
|
+
const { incrementUrlCounter } = require('./utils');
|
|
6
7
|
const { validateCoreMemberData, containsNonEnglish, createFullName } = require('./utils');
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -44,9 +45,7 @@ const ensureUniqueUrl = async ({ url, memberId, fullName }) => {
|
|
|
44
45
|
console.log(
|
|
45
46
|
`Found member with same url ${existingMember.url} for memberId ${memberId} and URL ${uniqueUrl}, increasing counter by 1`
|
|
46
47
|
);
|
|
47
|
-
|
|
48
|
-
const lastCounter = parseInt(lastSegment, 10) || 0;
|
|
49
|
-
uniqueUrl = `${uniqueUrl}-${lastCounter + 1}`;
|
|
48
|
+
uniqueUrl = incrementUrlCounter(existingMember.url, uniqueUrl);
|
|
50
49
|
}
|
|
51
50
|
if (uniqueUrl !== baseUrl) {
|
|
52
51
|
console.log(`URL conflict resolved: ${baseUrl} -> ${uniqueUrl} for member ${memberId}`);
|
|
@@ -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
|
};
|
|
@@ -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
|
|
@@ -411,7 +412,7 @@ async function getMembersWithWixUrl() {
|
|
|
411
412
|
.ne('action', MEMBER_ACTIONS.DROP)
|
|
412
413
|
.ne('memberships.membertype', MEMBERSHIPS_TYPES.PAC_STAFF)
|
|
413
414
|
.ne('memberships.membertype', MEMBERSHIPS_TYPES.STUDENT)
|
|
414
|
-
|
|
415
|
+
//.isNotEmpty('url') - not used because it's not working as expected
|
|
415
416
|
.limit(1000);
|
|
416
417
|
let currentResults = await membersQuery.find();
|
|
417
418
|
let i = 0;
|
|
@@ -423,7 +424,9 @@ async function getMembersWithWixUrl() {
|
|
|
423
424
|
i++;
|
|
424
425
|
}
|
|
425
426
|
console.log('i is ', i);
|
|
426
|
-
const filtered = allItems.filter(
|
|
427
|
+
const filtered = allItems.filter(
|
|
428
|
+
item => typeof item.url === 'string' && !item.url.includes('/') && item.url !== ''
|
|
429
|
+
);
|
|
427
430
|
console.log('filtered is ', filtered.length);
|
|
428
431
|
return filtered;
|
|
429
432
|
}
|
|
@@ -32,7 +32,7 @@ async function scheduleFixPrimaryAddressForMembers() {
|
|
|
32
32
|
|
|
33
33
|
const membersToFix = members.filter(member => {
|
|
34
34
|
const addresses = Array.isArray(member.addresses) ? member.addresses : [];
|
|
35
|
-
if (addresses.length
|
|
35
|
+
if (addresses.length === 0) {
|
|
36
36
|
return false;
|
|
37
37
|
}
|
|
38
38
|
return !hasPrimaryAddress(member.addressDisplayOption);
|
|
@@ -45,7 +45,7 @@ async function scheduleFixPrimaryAddressForMembers() {
|
|
|
45
45
|
.filter(memberId => Number.isFinite(memberId) && memberId > 0)
|
|
46
46
|
),
|
|
47
47
|
];
|
|
48
|
-
console.log(`Members with
|
|
48
|
+
console.log(`Members with addresses and no primary: ${memberIds.length}`);
|
|
49
49
|
|
|
50
50
|
if (memberIds.length === 0) {
|
|
51
51
|
console.log('No members need primary address fixes');
|
|
@@ -109,7 +109,7 @@ async function fixPrimaryAddressChunk(data) {
|
|
|
109
109
|
skippedIds: [],
|
|
110
110
|
failedIds: [],
|
|
111
111
|
};
|
|
112
|
-
const
|
|
112
|
+
const skippedNoAddress = [];
|
|
113
113
|
const skippedHasPrimary = [];
|
|
114
114
|
const updatedIds = [];
|
|
115
115
|
|
|
@@ -120,11 +120,11 @@ async function fixPrimaryAddressChunk(data) {
|
|
|
120
120
|
|
|
121
121
|
members.forEach(member => {
|
|
122
122
|
const addresses = Array.isArray(member.addresses) ? member.addresses : [];
|
|
123
|
-
if (addresses.length
|
|
123
|
+
if (addresses.length === 0) {
|
|
124
124
|
result.skipped++;
|
|
125
125
|
result.skippedIds.push(member.memberId);
|
|
126
|
-
if (
|
|
127
|
-
|
|
126
|
+
if (skippedNoAddress.length < 20) {
|
|
127
|
+
skippedNoAddress.push(member.memberId);
|
|
128
128
|
}
|
|
129
129
|
return;
|
|
130
130
|
}
|
|
@@ -191,8 +191,8 @@ async function fixPrimaryAddressChunk(data) {
|
|
|
191
191
|
});
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
if (
|
|
195
|
-
console.log(`Skipped (
|
|
194
|
+
if (skippedNoAddress.length > 0) {
|
|
195
|
+
console.log(`Skipped (no addresses) sample: ${skippedNoAddress.join(', ')}`);
|
|
196
196
|
}
|
|
197
197
|
if (skippedHasPrimary.length > 0) {
|
|
198
198
|
console.log(`Skipped (already has primary) sample: ${skippedHasPrimary.join(', ')}`);
|
|
@@ -205,7 +205,52 @@ async function fixPrimaryAddressChunk(data) {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
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
|
+
|
|
208
252
|
module.exports = {
|
|
209
253
|
scheduleFixPrimaryAddressForMembers,
|
|
210
254
|
fixPrimaryAddressChunk,
|
|
255
|
+
countMembersMissingPrimaryAddress,
|
|
211
256
|
};
|
package/backend/utils.js
CHANGED
|
@@ -164,6 +164,14 @@ const normalizeUrlForComparison = url => {
|
|
|
164
164
|
return url.toLowerCase().replace(/-\d+$/, '');
|
|
165
165
|
};
|
|
166
166
|
|
|
167
|
+
const extractUrlCounter = url => {
|
|
168
|
+
if (!url) return -1;
|
|
169
|
+
const lastSegment = url.split('-').pop() || '0';
|
|
170
|
+
return /^\d+$/.test(lastSegment) ? parseInt(lastSegment, 10) : -1;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const sortByUrlCounterDescending = (a, b) => extractUrlCounter(b.url) - extractUrlCounter(a.url);
|
|
174
|
+
|
|
167
175
|
async function getSecret(secretKey) {
|
|
168
176
|
return (await elevatedGetSecretValue(secretKey)).value;
|
|
169
177
|
}
|
|
@@ -209,6 +217,8 @@ module.exports = {
|
|
|
209
217
|
generateGeoHash,
|
|
210
218
|
isValidArray,
|
|
211
219
|
normalizeUrlForComparison,
|
|
220
|
+
sortByUrlCounterDescending,
|
|
221
|
+
extractUrlCounter,
|
|
212
222
|
queryAllItems,
|
|
213
223
|
formatDateToMonthYear,
|
|
214
224
|
isStudent,
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const csv = require('csv-parser');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Reads a Members Data CSV and extracts groups of non-unique URLs:
|
|
8
|
+
* each group is a URL plus the list of IDs and memberIds that share that URL.
|
|
9
|
+
* Only outputs groups where the same URL appears more than once.
|
|
10
|
+
* Report includes duplicateUrls, memberIdsWithDuplicateUrls (flat array of unique memberIds
|
|
11
|
+
* in duplicate groups), and nonUniqueMemberIds (memberIds that appear more than once, with count).
|
|
12
|
+
*
|
|
13
|
+
* Usage: node dev-only-scripts/extract-duplicate-url-groups.js <path-to-csv> [output-json-path]
|
|
14
|
+
* Example: node dev-only-scripts/extract-duplicate-url-groups.js "/Users/Besan/Downloads/Members+Data+Latest (17).csv"
|
|
15
|
+
*
|
|
16
|
+
* CSV must have columns: "url", "ID", and "memberId" (case-insensitive).
|
|
17
|
+
*/
|
|
18
|
+
function extractDuplicateUrlGroups(csvFilePath, outputJsonPath) {
|
|
19
|
+
if (!csvFilePath) {
|
|
20
|
+
console.error('Error: CSV file path is required');
|
|
21
|
+
console.error(
|
|
22
|
+
'Usage: node dev-only-scripts/extract-duplicate-url-groups.js <path-to-csv> [output-json-path]'
|
|
23
|
+
);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(csvFilePath)) {
|
|
28
|
+
console.error(`Error: File not found: ${csvFilePath}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const urlToRows = new Map(); // url -> [{ id, memberId }, ...]
|
|
33
|
+
let totalRows = 0;
|
|
34
|
+
let headersValidated = false;
|
|
35
|
+
let headers = null;
|
|
36
|
+
let urlColumnName = null;
|
|
37
|
+
let idColumnName = null;
|
|
38
|
+
let memberIdColumnName = null;
|
|
39
|
+
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
fs.createReadStream(csvFilePath)
|
|
42
|
+
.pipe(csv())
|
|
43
|
+
.on('headers', receivedHeaders => {
|
|
44
|
+
headers = receivedHeaders;
|
|
45
|
+
const normalizedHeaders = headers.map(h => {
|
|
46
|
+
const normalized = String(h).trim().replace(/["']/g, '');
|
|
47
|
+
return normalized.toLowerCase().trim();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const urlIndex = normalizedHeaders.indexOf('url');
|
|
51
|
+
const idIndex = normalizedHeaders.indexOf('id');
|
|
52
|
+
const memberIdIndex = normalizedHeaders.indexOf('memberid');
|
|
53
|
+
|
|
54
|
+
if (urlIndex === -1 || idIndex === -1 || memberIdIndex === -1) {
|
|
55
|
+
console.error(
|
|
56
|
+
'Error: CSV must contain "url", "ID", and "memberId" columns (case-insensitive)'
|
|
57
|
+
);
|
|
58
|
+
console.error(`Found columns: ${headers.join(', ')}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
urlColumnName = headers[urlIndex];
|
|
63
|
+
idColumnName = headers[idIndex];
|
|
64
|
+
memberIdColumnName = headers[memberIdIndex];
|
|
65
|
+
headersValidated = true;
|
|
66
|
+
})
|
|
67
|
+
.on('data', row => {
|
|
68
|
+
if (!headersValidated) {
|
|
69
|
+
headers = Object.keys(row);
|
|
70
|
+
const normalizedHeaders = headers.map(h => {
|
|
71
|
+
const normalized = String(h).trim().replace(/["']/g, '');
|
|
72
|
+
return normalized.toLowerCase().trim();
|
|
73
|
+
});
|
|
74
|
+
const urlIndex = normalizedHeaders.indexOf('url');
|
|
75
|
+
const idIndex = normalizedHeaders.indexOf('id');
|
|
76
|
+
const memberIdIndex = normalizedHeaders.indexOf('memberid');
|
|
77
|
+
if (urlIndex === -1 || idIndex === -1 || memberIdIndex === -1) {
|
|
78
|
+
console.error(
|
|
79
|
+
'Error: CSV must contain "url", "ID", and "memberId" columns (case-insensitive)'
|
|
80
|
+
);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
urlColumnName = headers[urlIndex];
|
|
84
|
+
idColumnName = headers[idIndex];
|
|
85
|
+
memberIdColumnName = headers[memberIdIndex];
|
|
86
|
+
headersValidated = true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
totalRows++;
|
|
90
|
+
|
|
91
|
+
const url = row[urlColumnName];
|
|
92
|
+
const id = row[idColumnName];
|
|
93
|
+
const memberId = row[memberIdColumnName];
|
|
94
|
+
|
|
95
|
+
if (!url || !id) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const trimmedUrl = url.trim();
|
|
100
|
+
const trimmedId = id.trim();
|
|
101
|
+
const trimmedMemberId =
|
|
102
|
+
memberId != null && String(memberId).trim() !== '' ? String(memberId).trim() : null;
|
|
103
|
+
|
|
104
|
+
if (!urlToRows.has(trimmedUrl)) {
|
|
105
|
+
urlToRows.set(trimmedUrl, []);
|
|
106
|
+
}
|
|
107
|
+
urlToRows.get(trimmedUrl).push({ id: trimmedId, memberId: trimmedMemberId });
|
|
108
|
+
})
|
|
109
|
+
.on('error', error => {
|
|
110
|
+
console.error('Error reading CSV file:', error.message);
|
|
111
|
+
reject(error);
|
|
112
|
+
})
|
|
113
|
+
.on('end', () => {
|
|
114
|
+
if (!headersValidated) {
|
|
115
|
+
console.error('Error: Could not read CSV headers');
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const groups = [];
|
|
120
|
+
for (const [url, rows] of urlToRows.entries()) {
|
|
121
|
+
if (rows.length > 1) {
|
|
122
|
+
const ids = rows.map(r => r.id);
|
|
123
|
+
const memberIds = rows.map(r => r.memberId).filter(Boolean);
|
|
124
|
+
groups.push({ url, ids, memberIds });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
groups.sort((a, b) => {
|
|
129
|
+
if (b.ids.length !== a.ids.length) return b.ids.length - a.ids.length;
|
|
130
|
+
return a.url.localeCompare(b.url);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const totalIdsInGroups = groups.reduce((sum, g) => sum + g.ids.length, 0);
|
|
134
|
+
const duplicateUrls = groups.map(g => g.url);
|
|
135
|
+
const allMemberIdOccurrences = groups.flatMap(g => g.memberIds);
|
|
136
|
+
const memberIdsWithDuplicateUrls = [...new Set(allMemberIdOccurrences)];
|
|
137
|
+
|
|
138
|
+
const memberIdCounts = new Map();
|
|
139
|
+
for (const memberId of allMemberIdOccurrences) {
|
|
140
|
+
memberIdCounts.set(memberId, (memberIdCounts.get(memberId) || 0) + 1);
|
|
141
|
+
}
|
|
142
|
+
const nonUniqueMemberIds = [...memberIdCounts.entries()]
|
|
143
|
+
.filter(([, count]) => count > 1)
|
|
144
|
+
.map(([memberId, count]) => ({ memberId, count }))
|
|
145
|
+
.sort((a, b) => b.count - a.count);
|
|
146
|
+
|
|
147
|
+
const report = {
|
|
148
|
+
totalRowsProcessed: totalRows,
|
|
149
|
+
totalDuplicateGroups: groups.length,
|
|
150
|
+
totalIdsInDuplicateGroups: totalIdsInGroups,
|
|
151
|
+
totalMemberIdOccurrencesInDuplicateGroups: allMemberIdOccurrences.length,
|
|
152
|
+
uniqueMemberIdsWithDuplicateUrls: memberIdsWithDuplicateUrls.length,
|
|
153
|
+
nonUniqueMemberIdsCount: nonUniqueMemberIds.length,
|
|
154
|
+
duplicateUrls,
|
|
155
|
+
memberIdsWithDuplicateUrls,
|
|
156
|
+
nonUniqueMemberIds,
|
|
157
|
+
groups,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
if (outputJsonPath) {
|
|
161
|
+
fs.writeFileSync(outputJsonPath, JSON.stringify(report, null, 2), 'utf8');
|
|
162
|
+
console.log(`Report written to: ${outputJsonPath}`);
|
|
163
|
+
} else {
|
|
164
|
+
const csvDir = path.dirname(csvFilePath);
|
|
165
|
+
const csvBasename = path.basename(csvFilePath, path.extname(csvFilePath));
|
|
166
|
+
const defaultPath = path.join(csvDir, `${csvBasename}-duplicate-url-groups.json`);
|
|
167
|
+
fs.writeFileSync(defaultPath, JSON.stringify(report, null, 2), 'utf8');
|
|
168
|
+
console.log(`Report written to: ${defaultPath}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log('\n=== Non-unique URL groups (same URL → list of IDs, memberIds) ===');
|
|
172
|
+
console.log(`Total rows processed: ${totalRows}`);
|
|
173
|
+
console.log(`Number of duplicate URL groups: ${groups.length}`);
|
|
174
|
+
console.log(`Total IDs in those groups: ${totalIdsInGroups}`);
|
|
175
|
+
console.log(`Total memberId occurrences in those groups: ${allMemberIdOccurrences.length}`);
|
|
176
|
+
console.log(`Unique memberIds in those groups: ${memberIdsWithDuplicateUrls.length}`);
|
|
177
|
+
console.log(
|
|
178
|
+
`MemberIds that appear more than once (non-unique): ${nonUniqueMemberIds.length}`
|
|
179
|
+
);
|
|
180
|
+
if (duplicateUrls.length > 0) {
|
|
181
|
+
console.log('\nFirst 15 duplicate URLs:');
|
|
182
|
+
duplicateUrls.slice(0, 15).forEach((url, i) => {
|
|
183
|
+
console.log(` ${i + 1}. ${url}`);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
resolve(report);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (require.main === module) {
|
|
193
|
+
const csvFilePath = process.argv[2];
|
|
194
|
+
const outputJsonPath = process.argv[3];
|
|
195
|
+
extractDuplicateUrlGroups(csvFilePath, outputJsonPath).catch(error => {
|
|
196
|
+
console.error('Fatal error:', error.message);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = { extractDuplicateUrlGroups };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const csv = require('csv-parser');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Reads a Members Data CSV and finds duplicate memberIds: any memberId (memberId column)
|
|
8
|
+
* that appears in more than one row. Outputs a JSON report with the list of
|
|
9
|
+
* duplicate memberIds and, per memberId, the rows (e.g. ID, url) where it appears.
|
|
10
|
+
*
|
|
11
|
+
* Usage: node dev-only-scripts/find-duplicate-ids.js <path-to-csv> [output-json-path]
|
|
12
|
+
*
|
|
13
|
+
* CSV must have a "memberId" column (case-insensitive). Optional: "ID", "url" for row details.
|
|
14
|
+
*/
|
|
15
|
+
function findDuplicateMemberIds(csvFilePath, outputJsonPath) {
|
|
16
|
+
if (!csvFilePath) {
|
|
17
|
+
console.error('Error: CSV file path is required');
|
|
18
|
+
console.error(
|
|
19
|
+
'Usage: node dev-only-scripts/find-duplicate-ids.js <path-to-csv> [output-json-path]'
|
|
20
|
+
);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!fs.existsSync(csvFilePath)) {
|
|
25
|
+
console.error(`Error: File not found: ${csvFilePath}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const memberIdToRows = new Map(); // memberId -> [{ rowNumber, id?, url? }, ...]
|
|
30
|
+
let totalRows = 0;
|
|
31
|
+
let headersValidated = false;
|
|
32
|
+
let headers = null;
|
|
33
|
+
let memberIdColumnName = null;
|
|
34
|
+
let idColumnName = null;
|
|
35
|
+
let urlColumnName = null;
|
|
36
|
+
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
fs.createReadStream(csvFilePath)
|
|
39
|
+
.pipe(csv())
|
|
40
|
+
.on('headers', receivedHeaders => {
|
|
41
|
+
headers = receivedHeaders;
|
|
42
|
+
const normalizedHeaders = headers.map(h => {
|
|
43
|
+
const normalized = String(h).trim().replace(/["']/g, '');
|
|
44
|
+
return normalized.toLowerCase().trim();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const memberIdIndex = normalizedHeaders.indexOf('memberid');
|
|
48
|
+
if (memberIdIndex === -1) {
|
|
49
|
+
console.error('Error: CSV must contain a "memberId" column (case-insensitive)');
|
|
50
|
+
console.error(`Found columns: ${headers.join(', ')}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
memberIdColumnName = headers[memberIdIndex];
|
|
55
|
+
idColumnName = headers[normalizedHeaders.indexOf('id')] || null;
|
|
56
|
+
urlColumnName = headers[normalizedHeaders.indexOf('url')] || null;
|
|
57
|
+
headersValidated = true;
|
|
58
|
+
})
|
|
59
|
+
.on('data', row => {
|
|
60
|
+
if (!headersValidated) {
|
|
61
|
+
headers = Object.keys(row);
|
|
62
|
+
const normalizedHeaders = headers.map(h => {
|
|
63
|
+
const normalized = String(h).trim().replace(/["']/g, '');
|
|
64
|
+
return normalized.toLowerCase().trim();
|
|
65
|
+
});
|
|
66
|
+
const memberIdIndex = normalizedHeaders.indexOf('memberid');
|
|
67
|
+
if (memberIdIndex === -1) {
|
|
68
|
+
console.error('Error: CSV must contain a "memberId" column (case-insensitive)');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
memberIdColumnName = headers[memberIdIndex];
|
|
72
|
+
idColumnName = headers[normalizedHeaders.indexOf('id')] || null;
|
|
73
|
+
urlColumnName = headers[normalizedHeaders.indexOf('url')] || null;
|
|
74
|
+
headersValidated = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
totalRows++;
|
|
78
|
+
const memberId = row[memberIdColumnName];
|
|
79
|
+
if (memberId == null || String(memberId).trim() === '') return;
|
|
80
|
+
|
|
81
|
+
const trimmedMemberId = String(memberId).trim();
|
|
82
|
+
const rowInfo = { rowNumber: totalRows };
|
|
83
|
+
if (idColumnName && row[idColumnName] != null)
|
|
84
|
+
rowInfo.id = String(row[idColumnName]).trim();
|
|
85
|
+
if (urlColumnName && row[urlColumnName] != null)
|
|
86
|
+
rowInfo.url = String(row[urlColumnName]).trim();
|
|
87
|
+
|
|
88
|
+
if (!memberIdToRows.has(trimmedMemberId)) {
|
|
89
|
+
memberIdToRows.set(trimmedMemberId, []);
|
|
90
|
+
}
|
|
91
|
+
memberIdToRows.get(trimmedMemberId).push(rowInfo);
|
|
92
|
+
})
|
|
93
|
+
.on('error', error => {
|
|
94
|
+
console.error('Error reading CSV file:', error.message);
|
|
95
|
+
reject(error);
|
|
96
|
+
})
|
|
97
|
+
.on('end', () => {
|
|
98
|
+
if (!headersValidated) {
|
|
99
|
+
console.error('Error: Could not read CSV headers');
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const groups = [];
|
|
104
|
+
for (const [memberId, rows] of memberIdToRows.entries()) {
|
|
105
|
+
if (rows.length > 1) {
|
|
106
|
+
groups.push({ memberId, count: rows.length, rows });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
groups.sort((a, b) => b.count - a.count);
|
|
111
|
+
|
|
112
|
+
const duplicateMemberIds = groups.map(g => g.memberId);
|
|
113
|
+
const totalDuplicateRows = groups.reduce((sum, g) => sum + g.count, 0);
|
|
114
|
+
|
|
115
|
+
const report = {
|
|
116
|
+
totalRowsProcessed: totalRows,
|
|
117
|
+
totalDuplicateMemberIds: groups.length,
|
|
118
|
+
totalRowsWithDuplicateMemberIds: totalDuplicateRows,
|
|
119
|
+
duplicateMemberIds,
|
|
120
|
+
groups,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (outputJsonPath) {
|
|
124
|
+
fs.writeFileSync(outputJsonPath, JSON.stringify(report, null, 2), 'utf8');
|
|
125
|
+
console.log(`Report written to: ${outputJsonPath}`);
|
|
126
|
+
} else {
|
|
127
|
+
const csvDir = path.dirname(csvFilePath);
|
|
128
|
+
const csvBasename = path.basename(csvFilePath, path.extname(csvFilePath));
|
|
129
|
+
const defaultPath = path.join(csvDir, `${csvBasename}-duplicate-member-ids-report.json`);
|
|
130
|
+
fs.writeFileSync(defaultPath, JSON.stringify(report, null, 2), 'utf8');
|
|
131
|
+
console.log(`Report written to: ${defaultPath}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log('\n=== Duplicate memberIds Report ===');
|
|
135
|
+
console.log(`Total rows processed: ${totalRows}`);
|
|
136
|
+
console.log(`memberIds that appear more than once: ${groups.length}`);
|
|
137
|
+
console.log(`Total rows with those memberIds: ${totalDuplicateRows}`);
|
|
138
|
+
if (groups.length > 0) {
|
|
139
|
+
console.log('\nDuplicate memberIds (first 20):');
|
|
140
|
+
groups.slice(0, 20).forEach((g, i) => {
|
|
141
|
+
console.log(` ${i + 1}. ${g.memberId} (${g.count} rows)`);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
resolve(report);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (require.main === module) {
|
|
151
|
+
const csvFilePath = process.argv[2];
|
|
152
|
+
const outputJsonPath = process.argv[3];
|
|
153
|
+
findDuplicateMemberIds(csvFilePath, outputJsonPath).catch(error => {
|
|
154
|
+
console.error('Fatal error:', error.message);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = { findDuplicateMemberIds };
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abmp-npm",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.52",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"check-cycles": "madge --circular .",
|
|
7
|
-
"test": "
|
|
7
|
+
"test": "jest backend/__tests__",
|
|
8
8
|
"lint": "npm run check-cycles && eslint .",
|
|
9
9
|
"lint:fix": "eslint . --fix",
|
|
10
10
|
"format": "prettier --write \"**/*.{js,json,md}\"",
|
|
11
11
|
"format:check": "prettier --check \"**/*.{js,json,md}\"",
|
|
12
12
|
"prepare": "husky",
|
|
13
|
-
"find-duplicates": "node dev-only-scripts/find-duplicate-urls.js"
|
|
13
|
+
"find-duplicates": "node dev-only-scripts/find-duplicate-urls.js",
|
|
14
|
+
"extract-duplicate-url-groups": "node dev-only-scripts/extract-duplicate-url-groups.js",
|
|
15
|
+
"find-duplicate-ids": "node dev-only-scripts/find-duplicate-ids.js"
|
|
14
16
|
},
|
|
15
17
|
"author": "",
|
|
16
18
|
"license": "ISC",
|
|
@@ -25,6 +27,7 @@
|
|
|
25
27
|
"eslint-plugin-promise": "^7.1.0",
|
|
26
28
|
"globals": "^15.10.0",
|
|
27
29
|
"husky": "^9.1.6",
|
|
30
|
+
"jest": "^30.3.0",
|
|
28
31
|
"madge": "^8.0.0",
|
|
29
32
|
"prettier": "^3.3.3"
|
|
30
33
|
},
|
|
@@ -45,9 +48,9 @@
|
|
|
45
48
|
"aws4": "^1.13.2",
|
|
46
49
|
"axios": "^1.13.1",
|
|
47
50
|
"crypto": "^1.0.1",
|
|
51
|
+
"csv-parser": "^3.0.0",
|
|
48
52
|
"jwt-js-decode": "^1.9.0",
|
|
49
53
|
"lodash": "^4.17.21",
|
|
50
|
-
"csv-parser": "^3.0.0",
|
|
51
54
|
"ngeohash": "^0.6.3",
|
|
52
55
|
"phone": "^3.1.67",
|
|
53
56
|
"psdev-task-manager": "1.1.10",
|
package/pages/personalDetails.js
CHANGED
|
@@ -358,8 +358,8 @@ async function personalDetailsOnReady({
|
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
function handleOptWebsiteCheckboxEnable(showExistingWebsite) {
|
|
361
|
-
_$w('#optWebsiteCheckbox').checked = !showExistingWebsite;
|
|
362
361
|
if (showExistingWebsite) {
|
|
362
|
+
_$w('#optWebsiteCheckbox').checked = false;
|
|
363
363
|
_$w('#optWebsiteCheckbox').customClassList.add('disabled-text');
|
|
364
364
|
_$w('#optWebsiteCheckbox').customClassList.add('disabled-checkbox');
|
|
365
365
|
_$w('#optWebsiteCheckbox').disable();
|