abmp-npm 2.0.13 → 2.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/cms-data-methods.js +3 -1
- package/backend/daily-pull/process-member-methods.js +1 -0
- package/backend/index.js +2 -0
- package/backend/routers/utils.js +6 -3
- package/backend/tasks/consts.js +4 -0
- package/backend/tasks/index.js +1 -0
- package/backend/tasks/migration-methods.js +20 -0
- package/backend/tasks/tasks-configs.js +35 -1
- package/backend/tasks/url-migration-methods.js +378 -0
- package/backend/utils.js +9 -8
- package/dev-only-scripts/find-duplicate-urls.js +0 -1
- package/package.json +1 -1
- package/pages/Profile.js +2 -2
- package/pages/personalDetails.js +32 -1
- package/public/Utils/sharedUtils.js +1 -1
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
GEO_HASH_PRECISION,
|
|
9
9
|
MAX__MEMBERS_SEARCH_RESULTS,
|
|
10
10
|
WIX_QUERY_MAX_LIMIT,
|
|
11
|
+
MEMBERSHIPS_TYPES,
|
|
11
12
|
} = require('./consts.js');
|
|
12
13
|
const { wixData } = require('./elevated-modules');
|
|
13
14
|
|
|
@@ -31,6 +32,7 @@ function buildMembersSearchQuery(data) {
|
|
|
31
32
|
.query(COLLECTIONS.MEMBERS_DATA)
|
|
32
33
|
.ne('optOut', true)
|
|
33
34
|
.ne('action', 'drop')
|
|
35
|
+
.ne('memberships.membertype', MEMBERSHIPS_TYPES.PAC_STAFF)
|
|
34
36
|
.eq('isVisible', true);
|
|
35
37
|
let filterConfig = [
|
|
36
38
|
{
|
|
@@ -105,7 +107,7 @@ function buildMembersSearchQuery(data) {
|
|
|
105
107
|
query = query.contains('fullName', filter.searchText);
|
|
106
108
|
}
|
|
107
109
|
if (!includeStudents) {
|
|
108
|
-
query = query.ne('memberships.membertype',
|
|
110
|
+
query = query.ne('memberships.membertype', MEMBERSHIPS_TYPES.STUDENT);
|
|
109
111
|
}
|
|
110
112
|
return query;
|
|
111
113
|
},
|
package/backend/index.js
CHANGED
package/backend/routers/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { getMainAddress } = require('../../public/Utils/sharedUtils');
|
|
2
2
|
const { getMemberBySlug } = require('../members-data-methods');
|
|
3
3
|
const {
|
|
4
|
-
|
|
4
|
+
getMoreAddressesToDisplay,
|
|
5
5
|
formatDateToMonthYear,
|
|
6
6
|
hasStudentMembership,
|
|
7
7
|
isPAC_STAFF,
|
|
@@ -48,7 +48,10 @@ function transformMemberToProfileData(member, siteAssociation) {
|
|
|
48
48
|
?.map(val => val.license)
|
|
49
49
|
.filter(Boolean)
|
|
50
50
|
.join(', ');
|
|
51
|
-
const
|
|
51
|
+
const moreAddressesToDisplay = getMoreAddressesToDisplay(
|
|
52
|
+
member.addresses,
|
|
53
|
+
member.addressDisplayOption
|
|
54
|
+
);
|
|
52
55
|
|
|
53
56
|
const memberships = member.memberships || [];
|
|
54
57
|
const siteAssociationMembership = memberships.find(m => m.association === siteAssociation);
|
|
@@ -67,7 +70,7 @@ function transformMemberToProfileData(member, siteAssociation) {
|
|
|
67
70
|
mainAddress,
|
|
68
71
|
testimonials: member.testimonial || [],
|
|
69
72
|
licenceNo,
|
|
70
|
-
|
|
73
|
+
moreAddressesToDisplay,
|
|
71
74
|
memberSince:
|
|
72
75
|
(member.showABMP &&
|
|
73
76
|
siteAssociationMembership &&
|
package/backend/tasks/consts.js
CHANGED
|
@@ -12,6 +12,10 @@ const TASKS_NAMES = {
|
|
|
12
12
|
syncMemberLoginEmails: 'syncMemberLoginEmails',
|
|
13
13
|
scheduleContactFormEmailMigration: 'scheduleContactFormEmailMigration',
|
|
14
14
|
migrateContactFormEmails: 'migrateContactFormEmails',
|
|
15
|
+
scheduleMigrateExistingUrls: 'scheduleMigrateExistingUrls',
|
|
16
|
+
migrateUrlsChunk: 'migrateUrlsChunk',
|
|
17
|
+
scheduleGenerateMissingUrls: 'scheduleGenerateMissingUrls',
|
|
18
|
+
generateUrlsChunk: 'generateUrlsChunk',
|
|
15
19
|
};
|
|
16
20
|
|
|
17
21
|
module.exports = {
|
package/backend/tasks/index.js
CHANGED
|
@@ -20,7 +20,27 @@ function scheduleExternalProfileImageMigration() {
|
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// Schedule URL migration from backup collection
|
|
24
|
+
function scheduleUrlMigration() {
|
|
25
|
+
return taskManager().schedule({
|
|
26
|
+
name: TASKS_NAMES.scheduleMigrateExistingUrls,
|
|
27
|
+
data: {},
|
|
28
|
+
type: 'scheduled',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Schedule URL generation for members without URLs
|
|
33
|
+
function scheduleUrlGeneration() {
|
|
34
|
+
return taskManager().schedule({
|
|
35
|
+
name: TASKS_NAMES.scheduleGenerateMissingUrls,
|
|
36
|
+
data: {},
|
|
37
|
+
type: 'scheduled',
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
23
41
|
module.exports = {
|
|
24
42
|
scheduleConvertHtmlToRichContent,
|
|
25
43
|
scheduleExternalProfileImageMigration,
|
|
44
|
+
scheduleUrlMigration,
|
|
45
|
+
scheduleUrlGeneration,
|
|
26
46
|
};
|
|
@@ -17,6 +17,12 @@ const {
|
|
|
17
17
|
scheduleEmailSync,
|
|
18
18
|
syncMemberLoginEmails,
|
|
19
19
|
} = require('./tasks-process-methods');
|
|
20
|
+
const {
|
|
21
|
+
scheduleMigrateExistingUrls,
|
|
22
|
+
migrateUrlsChunk,
|
|
23
|
+
scheduleGenerateMissingUrls,
|
|
24
|
+
generateUrlsChunk,
|
|
25
|
+
} = require('./url-migration-methods');
|
|
20
26
|
|
|
21
27
|
const getDailyMembersDataSyncChildTasks = () => {
|
|
22
28
|
// we don't want to sync none action as it means this members data hasn't changed and we don't need to sync it
|
|
@@ -89,7 +95,7 @@ const TASKS = {
|
|
|
89
95
|
getIdentifier: () => 'SHOULD_NEVER_SKIP',
|
|
90
96
|
process: updateSiteMapS3,
|
|
91
97
|
shouldSkipCheck: () => false,
|
|
92
|
-
estimatedDurationSec:
|
|
98
|
+
estimatedDurationSec: 70,
|
|
93
99
|
},
|
|
94
100
|
[TASKS_NAMES.scheduleContactFormEmailMigration]: {
|
|
95
101
|
name: TASKS_NAMES.scheduleContactFormEmailMigration,
|
|
@@ -119,6 +125,34 @@ const TASKS = {
|
|
|
119
125
|
shouldSkipCheck: () => false,
|
|
120
126
|
estimatedDurationSec: 45,
|
|
121
127
|
},
|
|
128
|
+
[TASKS_NAMES.scheduleMigrateExistingUrls]: {
|
|
129
|
+
name: TASKS_NAMES.scheduleMigrateExistingUrls,
|
|
130
|
+
getIdentifier: () => 'SHOULD_NEVER_SKIP',
|
|
131
|
+
process: scheduleMigrateExistingUrls,
|
|
132
|
+
shouldSkipCheck: () => false,
|
|
133
|
+
estimatedDurationSec: 80,
|
|
134
|
+
},
|
|
135
|
+
[TASKS_NAMES.migrateUrlsChunk]: {
|
|
136
|
+
name: TASKS_NAMES.migrateUrlsChunk,
|
|
137
|
+
getIdentifier: task => task.data,
|
|
138
|
+
process: migrateUrlsChunk,
|
|
139
|
+
shouldSkipCheck: () => false,
|
|
140
|
+
estimatedDurationSec: 80,
|
|
141
|
+
},
|
|
142
|
+
[TASKS_NAMES.scheduleGenerateMissingUrls]: {
|
|
143
|
+
name: TASKS_NAMES.scheduleGenerateMissingUrls,
|
|
144
|
+
getIdentifier: () => 'SHOULD_NEVER_SKIP',
|
|
145
|
+
process: scheduleGenerateMissingUrls,
|
|
146
|
+
shouldSkipCheck: () => false,
|
|
147
|
+
estimatedDurationSec: 80,
|
|
148
|
+
},
|
|
149
|
+
[TASKS_NAMES.generateUrlsChunk]: {
|
|
150
|
+
name: TASKS_NAMES.generateUrlsChunk,
|
|
151
|
+
getIdentifier: task => task.data,
|
|
152
|
+
process: generateUrlsChunk,
|
|
153
|
+
shouldSkipCheck: () => false,
|
|
154
|
+
estimatedDurationSec: 80,
|
|
155
|
+
},
|
|
122
156
|
};
|
|
123
157
|
|
|
124
158
|
module.exports = { TASKS };
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
const { taskManager } = require('psdev-task-manager');
|
|
2
|
+
|
|
3
|
+
const { COLLECTIONS } = require('../../public/consts');
|
|
4
|
+
const { ensureUniqueUrl } = require('../daily-pull/process-member-methods');
|
|
5
|
+
const { wixData } = require('../elevated-modules');
|
|
6
|
+
const { bulkSaveMembers } = require('../members-data-methods');
|
|
7
|
+
const { queryAllItems, chunkArray } = require('../utils');
|
|
8
|
+
|
|
9
|
+
const { TASKS_NAMES } = require('./consts');
|
|
10
|
+
|
|
11
|
+
const COLLECTION_WITH_URLS = 'MembersDataWithUrls';
|
|
12
|
+
const CHUNK_SIZE = 5000; // 5k members per task for migration
|
|
13
|
+
const GENERATION_CHUNK_SIZE = 1000; // 1k members per task for URL generation
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Step 1: Migrate existing URLs from backup collection
|
|
17
|
+
* Queries backup collection and schedules tasks with memberIds and URLs
|
|
18
|
+
*/
|
|
19
|
+
async function scheduleMigrateExistingUrls() {
|
|
20
|
+
console.log('=== Scheduling Step 1: Migrate Existing URLs ===');
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const membersQuery = await wixData.query(COLLECTION_WITH_URLS);
|
|
24
|
+
const startTime = Date.now();
|
|
25
|
+
const membersWithUrls = await queryAllItems(membersQuery);
|
|
26
|
+
const endTime = Date.now();
|
|
27
|
+
console.log(`QueryAllItems time: ${endTime - startTime}ms`);
|
|
28
|
+
|
|
29
|
+
const validMembers = membersWithUrls.filter(member => member.memberId && member.url);
|
|
30
|
+
console.log(`${validMembers.length} members have valid memberId and URL`);
|
|
31
|
+
|
|
32
|
+
if (validMembers.length === 0) {
|
|
33
|
+
console.log('No members to migrate URLs for');
|
|
34
|
+
return {
|
|
35
|
+
success: true,
|
|
36
|
+
message: 'No members need URL migration',
|
|
37
|
+
totalMembers: 0,
|
|
38
|
+
tasksScheduled: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const migrationData = validMembers.map(member => ({
|
|
43
|
+
memberId: member.memberId,
|
|
44
|
+
url: member.url,
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
const chunks = chunkArray(migrationData, CHUNK_SIZE);
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
50
|
+
const chunk = chunks[i];
|
|
51
|
+
const task = {
|
|
52
|
+
name: TASKS_NAMES.migrateUrlsChunk,
|
|
53
|
+
data: {
|
|
54
|
+
urlData: chunk,
|
|
55
|
+
chunkIndex: i,
|
|
56
|
+
totalChunks: chunks.length,
|
|
57
|
+
},
|
|
58
|
+
type: 'scheduled',
|
|
59
|
+
};
|
|
60
|
+
await taskManager().schedule(task);
|
|
61
|
+
console.log(`Scheduled migration task ${i + 1}/${chunks.length} (${chunk.length} members)`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result = {
|
|
65
|
+
success: true,
|
|
66
|
+
message: `Scheduled ${chunks.length} tasks for ${validMembers.length} members`,
|
|
67
|
+
totalMembers: validMembers.length,
|
|
68
|
+
tasksScheduled: chunks.length,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
console.log('=== Migration Scheduling Complete ===');
|
|
72
|
+
console.log(JSON.stringify(result, null, 2));
|
|
73
|
+
|
|
74
|
+
return result;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error('Error scheduling URL migration:', error);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Process a chunk of URL migrations (called by task manager)
|
|
83
|
+
* Fetches members by memberId and updates with URLs using bulkSave
|
|
84
|
+
*/
|
|
85
|
+
async function migrateUrlsChunk(data) {
|
|
86
|
+
const { urlData, chunkIndex, totalChunks } = data;
|
|
87
|
+
console.log(
|
|
88
|
+
`Processing migration chunk ${chunkIndex + 1}/${totalChunks} (${urlData.length} members)`
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const result = {
|
|
92
|
+
successful: 0,
|
|
93
|
+
failed: 0,
|
|
94
|
+
skipped: 0,
|
|
95
|
+
errors: [],
|
|
96
|
+
skippedIds: [],
|
|
97
|
+
failedIds: [],
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const memberIds = urlData.map(({ memberId }) => memberId);
|
|
102
|
+
|
|
103
|
+
console.log(`Fetching ${memberIds.length} members from database...`);
|
|
104
|
+
const query = await wixData.query(COLLECTIONS.MEMBERS_DATA).hasSome('memberId', memberIds);
|
|
105
|
+
const members = await queryAllItems(query);
|
|
106
|
+
console.log(`Found ${members.length} members in database`);
|
|
107
|
+
|
|
108
|
+
const memberMap = new Map();
|
|
109
|
+
members.forEach(member => {
|
|
110
|
+
if (member.memberId) {
|
|
111
|
+
memberMap.set(member.memberId, member);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const membersToUpdate = [];
|
|
116
|
+
for (const { memberId, url } of urlData) {
|
|
117
|
+
const member = memberMap.get(memberId);
|
|
118
|
+
|
|
119
|
+
if (!member) {
|
|
120
|
+
console.log(`Member with memberId ${memberId} not found - skipping`);
|
|
121
|
+
result.skipped++;
|
|
122
|
+
result.skippedIds.push(memberId);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (member.url === url) {
|
|
127
|
+
console.log(`Member ${member._id} already has URL ${url} - skipping`);
|
|
128
|
+
result.skipped++;
|
|
129
|
+
result.skippedIds.push(memberId);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
membersToUpdate.push({
|
|
134
|
+
...member,
|
|
135
|
+
url: url,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (membersToUpdate.length === 0) {
|
|
140
|
+
console.log('No members need updating in this batch');
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(
|
|
145
|
+
`Started updating ${membersToUpdate.length} members with URLs in chunk ${chunkIndex}`
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
await bulkSaveMembers(membersToUpdate);
|
|
150
|
+
result.successful += membersToUpdate.length;
|
|
151
|
+
console.log(`✅ Successfully updated ${membersToUpdate.length} members`);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error(`❌ Error bulk saving members:`, error);
|
|
154
|
+
result.failed += membersToUpdate.length;
|
|
155
|
+
// Add all member IDs to failedIds
|
|
156
|
+
result.failedIds.push(...membersToUpdate.map(m => m.memberId));
|
|
157
|
+
result.errors.push({
|
|
158
|
+
error: error.message,
|
|
159
|
+
memberCount: membersToUpdate.length,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log(
|
|
164
|
+
`Chunk ${chunkIndex + 1} complete: ${result.successful} success, ${result.failed} failed, ${result.skipped} skipped`
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Log failed and skipped IDs if any
|
|
168
|
+
if (result.failedIds.length > 0) {
|
|
169
|
+
console.log(`❌ Failed memberIds (${result.failedIds.length}):`, result.failedIds);
|
|
170
|
+
}
|
|
171
|
+
if (result.skippedIds.length > 0) {
|
|
172
|
+
console.log(`⏭️ Skipped memberIds (${result.skippedIds.length}):`, result.skippedIds);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return result;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error(`Error processing migration chunk ${chunkIndex}:`, error);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Step 2: Generate URLs for members without URLs
|
|
184
|
+
* Queries members without URLs and schedules generation tasks
|
|
185
|
+
*/
|
|
186
|
+
async function scheduleGenerateMissingUrls() {
|
|
187
|
+
console.log('=== Scheduling Step 2: Generate Missing URLs ===');
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const membersQuery = await wixData.query(COLLECTIONS.MEMBERS_DATA).isEmpty('url');
|
|
191
|
+
const membersToUpdate = await queryAllItems(membersQuery);
|
|
192
|
+
|
|
193
|
+
console.log(`Found ${membersToUpdate.length} members without URLs`);
|
|
194
|
+
|
|
195
|
+
if (membersToUpdate.length === 0) {
|
|
196
|
+
console.log('No members need URL generation');
|
|
197
|
+
return {
|
|
198
|
+
success: true,
|
|
199
|
+
message: 'No members need URL generation',
|
|
200
|
+
totalMembers: 0,
|
|
201
|
+
tasksScheduled: 0,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const chunks = chunkArray(membersToUpdate, GENERATION_CHUNK_SIZE);
|
|
206
|
+
|
|
207
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
208
|
+
const chunk = chunks[i];
|
|
209
|
+
const task = {
|
|
210
|
+
name: TASKS_NAMES.generateUrlsChunk,
|
|
211
|
+
data: {
|
|
212
|
+
memberIds: chunk.map(m => m._id),
|
|
213
|
+
chunkIndex: i,
|
|
214
|
+
totalChunks: chunks.length,
|
|
215
|
+
},
|
|
216
|
+
type: 'scheduled',
|
|
217
|
+
};
|
|
218
|
+
await taskManager().schedule(task);
|
|
219
|
+
console.log(`Scheduled generation task ${i + 1}/${chunks.length} (${chunk.length} members)`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const result = {
|
|
223
|
+
success: true,
|
|
224
|
+
message: `Scheduled ${chunks.length} tasks for ${membersToUpdate.length} members`,
|
|
225
|
+
totalMembers: membersToUpdate.length,
|
|
226
|
+
tasksScheduled: chunks.length,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
console.log('=== Generation Scheduling Complete ===');
|
|
230
|
+
console.log(JSON.stringify(result, null, 2));
|
|
231
|
+
|
|
232
|
+
return result;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error('Error scheduling URL generation:', error);
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Process a chunk of URL generation (called by task manager)
|
|
241
|
+
* Fetches members, generates URLs, and bulk saves
|
|
242
|
+
*/
|
|
243
|
+
async function generateUrlsChunk(data) {
|
|
244
|
+
const { memberIds, chunkIndex, totalChunks } = data;
|
|
245
|
+
console.log(
|
|
246
|
+
`Processing generation chunk ${chunkIndex + 1}/${totalChunks} (${memberIds.length} members)`
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const result = {
|
|
250
|
+
successful: 0,
|
|
251
|
+
failed: 0,
|
|
252
|
+
skipped: 0,
|
|
253
|
+
errors: [],
|
|
254
|
+
skippedIds: [],
|
|
255
|
+
failedIds: [],
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
// Fetch members in smaller batches to avoid cursor size limits
|
|
260
|
+
// hasSome with too many IDs creates cursors that exceed Wix's 150KB limit
|
|
261
|
+
const FETCH_BATCH_SIZE = 200;
|
|
262
|
+
console.log(
|
|
263
|
+
`Fetching ${memberIds.length} members from database in batches of ${FETCH_BATCH_SIZE}...`
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const members = [];
|
|
267
|
+
const idBatches = chunkArray(memberIds, FETCH_BATCH_SIZE);
|
|
268
|
+
|
|
269
|
+
for (let i = 0; i < idBatches.length; i++) {
|
|
270
|
+
const idBatch = idBatches[i];
|
|
271
|
+
const batchMembers = await queryAllItems(
|
|
272
|
+
wixData.query(COLLECTIONS.MEMBERS_DATA).hasSome('_id', idBatch)
|
|
273
|
+
);
|
|
274
|
+
members.push(...batchMembers);
|
|
275
|
+
console.log(`Fetched batch ${i + 1}/${idBatches.length}: ${batchMembers.length} members`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
console.log(`Found ${members.length} members in database`);
|
|
279
|
+
|
|
280
|
+
// Create a map of _id -> member for quick lookup
|
|
281
|
+
const memberMap = new Map();
|
|
282
|
+
members.forEach(member => {
|
|
283
|
+
memberMap.set(member._id, member);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Process each member and generate URLs
|
|
287
|
+
const membersToUpdate = [];
|
|
288
|
+
for (const memberId of memberIds) {
|
|
289
|
+
const member = memberMap.get(memberId);
|
|
290
|
+
|
|
291
|
+
if (!member) {
|
|
292
|
+
console.log(`Member ${memberId} not found - skipping`);
|
|
293
|
+
result.skipped++;
|
|
294
|
+
result.skippedIds.push(memberId);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (member.url) {
|
|
299
|
+
console.log(`Member ${memberId} already has URL - skipping`);
|
|
300
|
+
result.skipped++;
|
|
301
|
+
result.skippedIds.push(memberId);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const name = member.fullName || `${member.firstName || ''} ${member.lastName || ''}`.trim();
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
const uniqueUrl = await ensureUniqueUrl({
|
|
309
|
+
url: '',
|
|
310
|
+
memberId: member.memberId,
|
|
311
|
+
fullName: name || '', // Let ensureUniqueUrl handle fallback for empty names
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
console.log(`✅ Generated URL for member ${memberId}: ${uniqueUrl}`);
|
|
315
|
+
membersToUpdate.push({
|
|
316
|
+
...member,
|
|
317
|
+
url: uniqueUrl,
|
|
318
|
+
});
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.error(`❌ Failed to generate URL for member ${memberId}:`, error);
|
|
321
|
+
result.failed++;
|
|
322
|
+
result.failedIds.push(memberId);
|
|
323
|
+
result.errors.push({
|
|
324
|
+
memberId,
|
|
325
|
+
error: error.message || 'Unknown error',
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (membersToUpdate.length === 0) {
|
|
331
|
+
console.log('No members need updating in this batch');
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
console.log(
|
|
336
|
+
`Started updating ${membersToUpdate.length} members with generated URLs in chunk ${chunkIndex}`
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
await bulkSaveMembers(membersToUpdate);
|
|
341
|
+
result.successful += membersToUpdate.length;
|
|
342
|
+
console.log(`✅ Successfully updated ${membersToUpdate.length} members`);
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.error(`❌ Error bulk saving members:`, error);
|
|
345
|
+
result.failed += membersToUpdate.length;
|
|
346
|
+
// Add all member _ids to failedIds
|
|
347
|
+
result.failedIds.push(...membersToUpdate.map(m => m._id));
|
|
348
|
+
result.errors.push({
|
|
349
|
+
error: error.message,
|
|
350
|
+
memberCount: membersToUpdate.length,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
console.log(
|
|
355
|
+
`Chunk ${chunkIndex + 1} complete: ${result.successful} success, ${result.failed} failed, ${result.skipped} skipped`
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
// Log failed and skipped IDs if any
|
|
359
|
+
if (result.failedIds.length > 0) {
|
|
360
|
+
console.log(`❌ Failed memberIds (${result.failedIds.length}):`, result.failedIds);
|
|
361
|
+
}
|
|
362
|
+
if (result.skippedIds.length > 0) {
|
|
363
|
+
console.log(`⏭️ Skipped memberIds (${result.skippedIds.length}):`, result.skippedIds);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return result;
|
|
367
|
+
} catch (error) {
|
|
368
|
+
console.error(`Error processing generation chunk ${chunkIndex}:`, error);
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
module.exports = {
|
|
374
|
+
scheduleMigrateExistingUrls,
|
|
375
|
+
migrateUrlsChunk,
|
|
376
|
+
scheduleGenerateMissingUrls,
|
|
377
|
+
generateUrlsChunk,
|
|
378
|
+
};
|
package/backend/utils.js
CHANGED
|
@@ -4,7 +4,7 @@ const { site } = require('@wix/urls');
|
|
|
4
4
|
const { encode } = require('ngeohash');
|
|
5
5
|
|
|
6
6
|
const { COLLECTIONS, ADDRESS_STATUS_TYPES } = require('../public/consts');
|
|
7
|
-
const { formatAddress, generateId } = require('../public/Utils/sharedUtils');
|
|
7
|
+
const { formatAddress, generateId, findMainAddress } = require('../public/Utils/sharedUtils');
|
|
8
8
|
|
|
9
9
|
const { CONFIG_KEYS, GEO_HASH_PRECISION, MEMBERSHIPS_TYPES } = require('./consts');
|
|
10
10
|
const { wixData } = require('./elevated-modules');
|
|
@@ -91,16 +91,17 @@ function getAddressDisplayOptions(member) {
|
|
|
91
91
|
}
|
|
92
92
|
return displayOptions;
|
|
93
93
|
}
|
|
94
|
-
function
|
|
94
|
+
function getMoreAddressesToDisplay(addresses = [], addressDisplayOption = []) {
|
|
95
95
|
const visible = addresses.filter(addr => addr.addressStatus !== ADDRESS_STATUS_TYPES.DONT_SHOW);
|
|
96
96
|
if (visible.length < 2) {
|
|
97
97
|
return [];
|
|
98
98
|
}
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
const mainAddress = findMainAddress(addressDisplayOption, addresses);
|
|
100
|
+
const remainingAddressesToFormat = mainAddress
|
|
101
|
+
? visible.filter(addr => addr?.key !== mainAddress.key)
|
|
102
|
+
: visible;
|
|
103
|
+
|
|
104
|
+
return remainingAddressesToFormat
|
|
104
105
|
.map(addr => {
|
|
105
106
|
const addressString = formatAddress(addr);
|
|
106
107
|
return addressString ? { _id: generateId(), address: addressString } : null;
|
|
@@ -214,7 +215,7 @@ module.exports = {
|
|
|
214
215
|
getSiteBaseUrl,
|
|
215
216
|
encodeXml,
|
|
216
217
|
formatDateOnly,
|
|
217
|
-
|
|
218
|
+
getMoreAddressesToDisplay,
|
|
218
219
|
isPAC_STAFF,
|
|
219
220
|
searchAllItems,
|
|
220
221
|
};
|
package/package.json
CHANGED
package/pages/Profile.js
CHANGED
|
@@ -59,9 +59,9 @@ async function profileOnReady({ $w: _$w }) {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
function setupAdditionalAddresses() {
|
|
62
|
-
_$w('#moreAdressesRepeater').data = profileData.
|
|
62
|
+
_$w('#moreAdressesRepeater').data = profileData.moreAddressesToDisplay;
|
|
63
63
|
|
|
64
|
-
if (profileData.
|
|
64
|
+
if (profileData.moreAddressesToDisplay.length > 0) {
|
|
65
65
|
_$w('#moreLocationButton').expand();
|
|
66
66
|
_$w('#addressTitle').collapse();
|
|
67
67
|
}
|
package/pages/personalDetails.js
CHANGED
|
@@ -1294,6 +1294,7 @@ async function personalDetailsOnReady({
|
|
|
1294
1294
|
streetAddress: {
|
|
1295
1295
|
name: extractStreetName(address.line1),
|
|
1296
1296
|
number: extractStreetNumber(address.line1),
|
|
1297
|
+
apt: address.line2 || '',
|
|
1297
1298
|
},
|
|
1298
1299
|
city: address.city || '',
|
|
1299
1300
|
subdivision: address.state || '',
|
|
@@ -1309,20 +1310,49 @@ async function personalDetailsOnReady({
|
|
|
1309
1310
|
if (!addressInputValue) return null;
|
|
1310
1311
|
|
|
1311
1312
|
let line1 = '';
|
|
1313
|
+
let line2 = '';
|
|
1314
|
+
|
|
1312
1315
|
if (addressInputValue.streetAddress) {
|
|
1313
1316
|
const number = addressInputValue.streetAddress.number || '';
|
|
1314
1317
|
const name = addressInputValue.streetAddress.name || '';
|
|
1315
1318
|
line1 = `${number} ${name}`.trim();
|
|
1319
|
+
|
|
1320
|
+
// Capture apartment/suite/building info from streetAddress.apt (undocumented but exists)
|
|
1321
|
+
if (addressInputValue.streetAddress.apt) {
|
|
1322
|
+
line2 = addressInputValue.streetAddress.apt;
|
|
1323
|
+
}
|
|
1316
1324
|
}
|
|
1317
1325
|
|
|
1318
1326
|
if (!line1 && addressInputValue.formatted) {
|
|
1319
1327
|
line1 = addressInputValue.formatted.split(',')[0]?.trim() || '';
|
|
1320
1328
|
}
|
|
1321
1329
|
|
|
1330
|
+
// If line2 is still empty, try to extract building/suite info from formatted address
|
|
1331
|
+
if (!line2 && addressInputValue.formatted) {
|
|
1332
|
+
const formattedParts = addressInputValue.formatted.split(',').map(part => part.trim());
|
|
1333
|
+
// Look for BLDG/STE/APT/UNIT/SUITE info in the formatted parts
|
|
1334
|
+
for (let i = 1; i < formattedParts.length; i++) {
|
|
1335
|
+
const part = formattedParts[i];
|
|
1336
|
+
const lowerPart = part.toLowerCase();
|
|
1337
|
+
if (
|
|
1338
|
+
lowerPart.includes('bldg') ||
|
|
1339
|
+
lowerPart.includes('ste') ||
|
|
1340
|
+
lowerPart.includes('apt') ||
|
|
1341
|
+
lowerPart.includes('unit') ||
|
|
1342
|
+
lowerPart.includes('suite') ||
|
|
1343
|
+
lowerPart.includes('#') ||
|
|
1344
|
+
lowerPart.includes('building')
|
|
1345
|
+
) {
|
|
1346
|
+
line2 = part;
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1322
1352
|
return {
|
|
1323
1353
|
key: existingAddress?.key || generateId(),
|
|
1324
1354
|
line1,
|
|
1325
|
-
line2: existingAddress?.line2 || '',
|
|
1355
|
+
line2: line2 || existingAddress?.line2 || '',
|
|
1326
1356
|
city: addressInputValue.city || '',
|
|
1327
1357
|
state: addressInputValue.subdivision || '',
|
|
1328
1358
|
postalcode: addressInputValue.postalCode || '',
|
|
@@ -1705,6 +1735,7 @@ async function personalDetailsOnReady({
|
|
|
1705
1735
|
const parts = [];
|
|
1706
1736
|
|
|
1707
1737
|
if (addr.line1) parts.push(addr.line1);
|
|
1738
|
+
if (addr.line2) parts.push(addr.line2); // Include building/suite info
|
|
1708
1739
|
if (addr.city) parts.push(addr.city);
|
|
1709
1740
|
if (addr.state && addr.postalcode) {
|
|
1710
1741
|
parts.push(`${addr.state} ${addr.postalcode}`);
|
|
@@ -91,7 +91,7 @@ function formatAddress(item) {
|
|
|
91
91
|
const limitedPostalCode = item.postalcode.slice(0, 5); //show only 5 digits to not show full user address
|
|
92
92
|
switch (item.addressStatus) {
|
|
93
93
|
case ADDRESS_STATUS_TYPES.FULL_ADDRESS:
|
|
94
|
-
addressParts = [item.line1, item.city, item.state, limitedPostalCode];
|
|
94
|
+
addressParts = [item.line1, item.line2, item.city, item.state, limitedPostalCode];
|
|
95
95
|
break;
|
|
96
96
|
case ADDRESS_STATUS_TYPES.STATE_CITY_ZIP:
|
|
97
97
|
addressParts = [item.city, item.state, limitedPostalCode];
|