sr-npm 1.7.65 → 1.7.67

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/data.js CHANGED
@@ -1,321 +1,272 @@
1
1
  const { items: wixData } = require('@wix/data');
2
2
  const { fetchPositionsFromSRAPI, fetchJobDescription } = require('./fetchPositionsFromSRAPI');
3
- const { chunkedBulkOperation } = require('./utils');
4
3
  const { createCollectionIfMissing } = require('@hisense-staging/velo-npm/backend');
5
4
  const { COLLECTIONS, COLLECTIONS_FIELDS } = require('./collectionConsts');
6
5
  const { secrets } = require("@wix/secrets");
7
6
  const { auth } = require('@wix/essentials');
7
+ const { chunkedBulkOperation, delay, countJobsPerGivenField, fillCityLocationAndLocationAddress ,prepareToSaveArray,normalizeCityName} = require('./utils');
8
+ const { getAllPositions } = require('./queries');
9
+
10
+
11
+ function validatePosition(position) {
12
+ if (!position.id) {
13
+ throw new Error('Position id is required');
14
+ }
15
+ if (!position.title) {
16
+ throw new Error('Position title is required');
17
+ }
18
+ if (!position.department || !position.department.label) {
19
+ throw new Error('Position department is required and must have a label');
20
+ }
21
+ if (!position.location || !position.location.city || !position.location.remote) {
22
+ throw new Error('Position location is required and must have a city and remote');
23
+ }
8
24
 
9
- // Utility function to normalize city names
10
- function normalizeCityName(city) {
11
- if (!city) return city;
12
- // Remove accents/diacritics, trim whitespace
13
- return city.normalize('NFD').replace(/\p{Diacritic}/gu, '').trim();
14
25
  }
15
26
 
16
- async function saveDataJobsToCMS() {
17
- const positions = await fetchPositionsFromSRAPI();
18
- // bulk insert to jobs collection without descriptions first
19
- const jobsData = positions.content.map((position) => {
20
- const basicJob = {
21
- _id: position.id,
22
- title: position.title,
23
- department: position.department.label,
24
- cityText: normalizeCityName(position.location.city),
25
- location: position.location,
26
- country: position.location.country,
27
- remote: position.location.remote,
28
- language: position.language.label,
29
- postingStatus: position.postingStatus,
30
- jobDescription: null // Will be filled later
31
- }
32
- return basicJob;
33
- });
34
-
35
- const chunkSize = 1000;
36
- let totalSaved = 0;
37
- const totalChunks = Math.ceil(jobsData.length / chunkSize);
38
-
39
- console.log(`Processing ${jobsData.length} jobs in ${totalChunks} chunks of max ${chunkSize} items each`);
27
+ async function saveJobsDataToCMS() {
28
+ const positions = await fetchPositionsFromSRAPI();
29
+ // bulk insert to jobs collection without descriptions first
30
+ const jobsData = positions.content.map(position => {
31
+ validatePosition(position);
32
+ const basicJob = {
33
+ _id: position.id,
34
+ title: position.title,
35
+ department: position.department.label,
36
+ cityText: normalizeCityName(position.location.city),
37
+ location: position.location,
38
+ country: position.location.country,
39
+ remote: position.location.remote,
40
+ language: position.language.label,
41
+ postingStatus: position.postingStatus,
42
+ jobDescription: null, // Will be filled later
43
+ };
44
+ return basicJob;
45
+ });
46
+
47
+ const chunkSize = 1000;
48
+ let totalSaved = 0;
49
+ const totalChunks = Math.ceil(jobsData.length / chunkSize);
50
+
51
+ console.log(
52
+ `Processing ${jobsData.length} jobs in ${totalChunks} chunks of max ${chunkSize} items each`
53
+ );
54
+
55
+ await chunkedBulkOperation({
56
+ items: jobsData,
57
+ chunkSize,
58
+ processChunk: async (chunk, chunkNumber) => {
59
+ console.log(`Saving chunk ${chunkNumber}/${totalChunks}: ${chunk.length} jobs`);
60
+ try {
61
+ const result = await wixData.bulkSave('Jobs1', chunk);
62
+ const saved = result.inserted + result.updated || chunk.length;
63
+ totalSaved += saved;
64
+ console.log(
65
+ `✓ Chunk ${chunkNumber} saved successfully. Inserted: ${result.inserted}, Updated: ${result.updated}`
66
+ );
67
+ } catch (error) {
68
+ console.error(`✗ Error saving chunk ${chunkNumber}:`, error);
69
+ throw error;
70
+ }
71
+ },
72
+ });
73
+
74
+ console.log(`✓ All chunks processed. Total jobs saved: ${totalSaved}/${jobsData.length}`);
75
+ }
76
+
77
+ async function saveJobsDescriptionsAndLocationToCMS() {
78
+ console.log('🚀 Starting job descriptions update process for ALL jobs');
79
+
80
+ try {
81
+ let jobsWithNoDescriptions = await getJobsWithNoDescriptions();
82
+ let totalUpdated = 0;
83
+ let totalFailed = 0;
84
+ let totalProcessed = 0;
85
+
86
+ console.log(
87
+ `Total jobs in database without descriptions: ${jobsWithNoDescriptions?.items?.length}`
88
+ );
89
+
90
+ if (jobsWithNoDescriptions.items.length === 0) {
91
+ console.log('No jobs found in database');
92
+ return { success: true, message: 'No jobs found' };
93
+ }
94
+
95
+
96
+ const API_CHUNK_SIZE = 80;
97
+ const pageChunks = Math.ceil(jobsWithNoDescriptions.items.length / API_CHUNK_SIZE);
40
98
 
41
99
  await chunkedBulkOperation({
42
- items: jobsData,
43
- chunkSize,
44
- processChunk: async (chunk, chunkNumber) => {
45
- console.log(`Saving chunk ${chunkNumber}/${totalChunks}: ${chunk.length} jobs`);
46
- try {
47
- const result = await wixData.bulkSave(COLLECTIONS.JOBS, chunk);
48
- const saved = result.inserted + result.updated || chunk.length;
49
- totalSaved += saved;
50
- console.log(`✓ Chunk ${chunkNumber} saved successfully. Inserted: ${result.inserted}, Updated: ${result.updated}`);
51
- } catch (error) {
52
- console.error(`✗ Error saving chunk ${chunkNumber}:`, error);
53
- throw error;
54
- }
100
+ items: jobsWithNoDescriptions.items,
101
+ chunkSize: API_CHUNK_SIZE,
102
+ processChunk: async (chunk, chunkNumber) => {
103
+ console.log(` Processing API chunk ${chunkNumber}/${pageChunks} (${chunk.length} jobs)`);
104
+ const chunkPromises = chunk.map(async job => {
105
+ try {
106
+ const jobDetails = await fetchJobDescription(job._id);
107
+ const jobLocation = fetchJobLocation(jobDetails);
108
+
109
+ const updatedJob = {
110
+ ...job,
111
+ locationAddress: jobLocation,
112
+ jobDescription: jobDetails.jobAd.sections,
113
+ };
114
+ await wixData.update('Jobs1', updatedJob);
115
+ return { success: true, jobId: job._id, title: job.title };
116
+ } catch (error) {
117
+ console.error(` ❌ Failed to update ${job.title} (${job._id}):`, error);
118
+ return { success: false, jobId: job._id, title: job.title, error: error.message };
119
+ }
120
+ });
121
+ const chunkResults = await Promise.all(chunkPromises);
122
+ const chunkSuccesses = chunkResults.filter(r => r.success).length;
123
+ const chunkFailures = chunkResults.filter(r => !r.success).length;
124
+ totalUpdated += chunkSuccesses;
125
+ totalFailed += chunkFailures;
126
+ totalProcessed += chunk.length;
127
+ console.log(
128
+ ` API chunk ${chunkNumber} completed: ${chunkSuccesses} success, ${chunkFailures} failed`
129
+ );
130
+ if (chunkNumber * API_CHUNK_SIZE < jobsWithNoDescriptions.items.length) {
131
+ console.log(' Waiting 2 seconds before next API chunk...');
132
+ await delay(2000);
55
133
  }
134
+ },
56
135
  });
57
-
58
- console.log(`✓ All chunks processed. Total jobs saved: ${totalSaved}/${jobsData.length}`);
59
136
 
60
- }
61
137
 
62
- async function saveJobsDescriptionsAndLocationApplyUrlToCMS() {
63
-
64
- console.log('🚀 Starting job descriptions update process for ALL jobs using pagination...');
65
-
66
- try {
67
- let jobsWithNoDescriptions = await getJobsWithNoDescriptions();
68
- let totalUpdated = 0;
69
- let totalFailed = 0;
70
- let totalProcessed = 0;
71
- let pageNumber = 1;
72
-
73
- // Start with the first page query - limit to 100 jobs per page
74
- // let jobsQuery = await wixData.query("Jobs").limit(300).find();
75
-
76
- console.log(`Total jobs in database without descriptions: ${jobsWithNoDescriptions.totalCount}`);
77
-
78
- if (jobsWithNoDescriptions.totalCount === 0) {
79
- console.log('No jobs found in database');
80
- return { success: true, message: 'No jobs found' };
81
- }
82
138
 
83
- // Process all pages using hasNext() pagination
84
- do {
85
- const currentPageJobs = jobsWithNoDescriptions.items;
86
- console.log(`\n📄 Processing page ${pageNumber} with ${currentPageJobs.length} jobs...`);
87
-
88
- // Process jobs in smaller chunks of 5 for API calls within each page
89
- const API_CHUNK_SIZE = 80;
90
- const pageChunks = Math.ceil(currentPageJobs.length / API_CHUNK_SIZE);
91
-
92
- await chunkedBulkOperation({
93
- items: currentPageJobs,
94
- chunkSize: API_CHUNK_SIZE,
95
- processChunk: async (chunk, chunkNumber) => {
96
- console.log(` Processing API chunk ${chunkNumber}/${pageChunks} (${chunk.length} jobs)`);
97
- const chunkPromises = chunk.map(async (job) => {
98
- try {
99
- // console.log(` Fetching description for: ${job.title} (${job._id})`);
100
- const jobDetails = await fetchJobDescription(job._id);
101
- const jobLocation = fetchJobLocation(jobDetails)
102
- const applyLink = fetchApplyLink(jobDetails);
103
-
104
- const updatedJob = {
105
- ...job,
106
- locationAddress: jobLocation,
107
- jobDescription: jobDetails.jobAd.sections,
108
- applyLink: applyLink
109
- };
110
- await wixData.update(COLLECTIONS.JOBS, updatedJob);
111
- // console.log(` ✅ Updated description for: ${job.title}`);
112
- return { success: true, jobId: job._id, title: job.title };
113
- } catch (error) {
114
- console.error(` ❌ Failed to update ${job.title} (${job._id}):`, error);
115
- return { success: false, jobId: job._id, title: job.title, error: error.message };
116
- }
117
- });
118
- const chunkResults = await Promise.all(chunkPromises);
119
- const chunkSuccesses = chunkResults.filter(r => r.success).length;
120
- const chunkFailures = chunkResults.filter(r => !r.success).length;
121
- totalUpdated += chunkSuccesses;
122
- totalFailed += chunkFailures;
123
- totalProcessed += chunk.length;
124
- console.log(` API chunk ${chunkNumber} completed: ${chunkSuccesses} success, ${chunkFailures} failed`);
125
- if (chunkNumber * API_CHUNK_SIZE < currentPageJobs.length) {
126
- console.log(' Waiting 2 seconds before next API chunk...');
127
- await new Promise(resolve => setTimeout(resolve, 2000));
128
- }
129
- }
130
- });
131
-
132
- console.log(`📄 Page ${pageNumber} completed. Updated: ${totalUpdated}, Failed: ${totalFailed}, Total processed: ${totalProcessed}/${jobsWithNoDescriptions.totalCount}`);
133
-
134
- // Check if there are more pages and get the next page
135
- if (jobsWithNoDescriptions.hasNext()) {
136
- console.log('🔄 Moving to next page...');
137
- jobsWithNoDescriptions = await jobsWithNoDescriptions.next();
138
- pageNumber++;
139
-
140
- // Add a delay between pages
141
- console.log('Waiting 3 seconds before next page...');
142
- await new Promise(resolve => setTimeout(resolve, 3000));
143
- }
144
-
145
- } while (jobsWithNoDescriptions.hasNext());
146
-
147
- console.log(`\n✅ Finished updating ALL job descriptions using pagination!`);
148
- console.log(`📊 Final Results:`);
149
- console.log(` Total pages processed: ${pageNumber}`);
150
- console.log(` Total jobs processed: ${totalProcessed}`);
151
- console.log(` Total updated: ${totalUpdated}`);
152
- console.log(` Total failed: ${totalFailed}`);
153
-
154
- return {
155
- success: true,
156
- totalPages: pageNumber,
157
- totalProcessed: totalProcessed,
158
- totalUpdated: totalUpdated,
159
- totalFailed: totalFailed,
160
- message: `Successfully updated ${totalUpdated} job descriptions out of ${totalProcessed} total jobs across ${pageNumber} pages`
161
- };
162
-
163
- } catch (error) {
164
- console.error('❌ Error in updateJobDescriptions:', error);
165
- throw error;
166
- }
139
+ console.log(`\n✅ Finished updating ALL job descriptions`);
140
+ console.log(`📊 Final Results:`);
141
+ console.log(` Total jobs processed: ${totalProcessed}`);
142
+ console.log(` Total updated: ${totalUpdated}`);
143
+ console.log(` Total failed: ${totalFailed}`);
144
+
145
+ return {
146
+ success: true,
147
+ totalProcessed: totalProcessed,
148
+ totalUpdated: totalUpdated,
149
+ totalFailed: totalFailed,
150
+ message: `Successfully updated ${totalUpdated} job descriptions out of ${totalProcessed} total jobs`,
151
+ };
152
+ } catch (error) {
153
+ console.error('❌ Error in updateJobDescriptions:', error);
154
+ throw error;
155
+ }
167
156
  }
168
157
 
169
- async function aggregateJobsByFieldToCMS({ field, collection }) {
170
- console.log(`counting jobs per ${field}.`);
171
- let jobsPerField = {};
172
- let cityLocations = {};
173
- let query = wixData.query(COLLECTIONS.JOBS).limit(1000);
174
- let results = await query.find();
175
- const cityLocationAddress={}
176
- let page = 1;
177
- do {
178
- console.log(`Page ${page}: ${results.items.length} jobs.`);
179
- for (const job of results.items) {
180
- if (!job[field])
181
- {
182
- throw new Error(`Job ${job._id} has no ${field} field`);
183
- }
184
- jobsPerField[job[field]] = (jobsPerField[job[field]] || 0) + 1;
185
- if (field === 'cityText' && !cityLocationAddress[job[field]] && !cityLocations[job[field]]) {
186
- cityLocationAddress[job[field]] = job.locationAddress;
187
- cityLocations[job[field]] = job.location;
188
- }
189
- }
190
- if (results.hasNext()) {
191
- results = await results.next();
192
- page++;
193
- }
194
- } while (results.hasNext());
195
- let toSave = [];
158
+
159
+ function iterateOverAllJobs(results, field) {
160
+ const jobsPerField = {};
161
+ const cityLocations = {};
162
+ const citylocationAddress = {};
163
+ countJobsPerGivenField(results, field, jobsPerField);
196
164
  if (field === 'cityText') {
197
- toSave = Object.entries(jobsPerField).map(([value, amount]) => {
198
- const locAddress = cityLocationAddress[value] || {};
199
- const loc = cityLocations[value] || {};
200
- value = normalizeCityName(value);
201
-
202
- return {
203
- title: value,
204
- _id: value.replace(/\s+/g, ''),
205
- count: amount,
206
- locationAddress: locAddress,
207
- country: loc.country,
208
- city: loc.city,
209
- };
210
- });
211
- }
212
- else{
213
- // Prepare array for bulkSave
214
- toSave = Object.entries(jobsPerField).map(([value, amount]) => ({
215
- title: value,
216
- _id: value.replace(/\s+/g, ''),
217
- count: amount
218
- }));
219
- }
220
- if (toSave.length === 0) {
221
- console.log('No jobs found.');
222
- return { success: true, message: 'No jobs to save.' };
165
+ fillCityLocationAndLocationAddress(results, cityLocations,citylocationAddress);
223
166
  }
167
+ return { jobsPerField, cityLocations,citylocationAddress };
168
+ }
224
169
 
225
- try {
226
- const saveResult = await wixData.bulkSave(collection, toSave);
227
- console.log(`Saved ${toSave.length} ${field} counts to ${collection}.`);
228
- return { success: true, saved: toSave.length, result: saveResult };
229
- } catch (err) {
230
- console.error(`Error saving jobs per ${field}:`, err);
231
- return { success: false, error: err.message };
232
- }
170
+ async function aggregateJobsByFieldToCMS({ field, collection }) {
171
+ console.log(`counting jobs per ${field}.`);
172
+ let results = await getAllPositions();
173
+ const { jobsPerField, cityLocations,citylocationAddress } = iterateOverAllJobs(results, field);
174
+ const toSave = prepareToSaveArray(jobsPerField, cityLocations, field,citylocationAddress);
175
+ if (toSave.length === 0) {
176
+ console.log('No jobs found.');
177
+ return { success: true, message: 'No jobs to save.' };
178
+ }
179
+ try {
180
+ const saveResult = await wixData.bulkSave(collection, toSave);
181
+ console.log(`Saved ${toSave.length} ${field} counts to ${collection}.`);
182
+ return { success: true, saved: toSave.length, result: saveResult };
183
+ } catch (err) {
184
+ console.error(`Error saving jobs per ${field}:`, err);
185
+ return { success: false, error: err.message };
186
+ }
233
187
  }
234
188
 
235
189
  async function getJobsWithNoDescriptions() {
236
-
237
- let jobswithoutdescriptionsQuery = await wixData.query(COLLECTIONS.JOBS).limit(1000).isEmpty("jobDescription").find(); // with 900 as the limit, 429 error won't happen
238
- return jobswithoutdescriptionsQuery;
190
+ let jobswithoutdescriptionsQuery = await wixData
191
+ .query('Jobs1')
192
+ .limit(1000)
193
+ .isEmpty('jobDescription')
194
+ .find();
195
+ return jobswithoutdescriptionsQuery;
239
196
  }
240
197
 
241
- async function referenceJobsToField({
242
- referenceField, // e.g., "city" or "department"
243
- sourceCollection, // e.g., "cities" or "departments"
244
- jobField, // e.g., "cityText" or "department"
245
-
246
- }) {
247
- // Fetch all source items (cities or departments)
248
- const sources = await wixData.query(sourceCollection).limit(1000).find();
249
- const sourceMap = {};
250
- for (const item of sources.items) {
251
- sourceMap[item.title] = item._id;
252
- }
253
-
254
- // Fetch all jobs
255
- let jobsResults = await wixData.query(COLLECTIONS.JOBS).limit(1000).find();
256
- let jobsToUpdate = [];
257
- console.log('jobsResults',jobsResults.items);
258
-
259
- do {
260
- for (const job of jobsResults.items) {
261
- const refId = sourceMap[job[jobField]];
262
- if (refId) {
263
- jobsToUpdate.push({
264
- ...job,
265
- [referenceField]: refId
266
- });
267
- }
268
- }
269
- if (jobsResults.hasNext()) {
270
- jobsResults = await jobsResults.next();
271
- } else {
272
- break;
273
- }
274
- } while (true);
198
+ /**
199
+ * @param {Object} params
200
+ * @param {"city"|"departmentRef"} params.referenceField
201
+ * @param {"cities1"|"AmountOfJobsPerDepartment1"} params.sourceCollection
202
+ * @param {"cityText"|"department"} params.jobField
203
+ */
204
+ async function referenceJobsToField({ referenceField, sourceCollection, jobField }) {
205
+ // Fetch all source items (cities or departments)
206
+ const sources = await wixData.query(sourceCollection).limit(1000).find();
207
+ const sourceMap = {};
208
+ for (const item of sources.items) {
209
+ sourceMap[item.title] = item._id;
210
+ }
275
211
 
276
- // Remove system fields that cannot be updated
277
- jobsToUpdate = jobsToUpdate.map(job => {
278
- const { _createdDate, _updatedDate, ...rest } = job;
279
- return rest;
280
- });
212
+ // Fetch all jobs
213
+ let jobsResults = await getAllPositions();
214
+ let jobsToUpdate = [];
215
+
216
+ for (const job of jobsResults) {
217
+ const refId = sourceMap[job[jobField]];
218
+ if (refId) {
219
+ jobsToUpdate.push({
220
+ ...job,
221
+ [referenceField]: refId,
222
+ });
223
+ }
224
+ }
281
225
 
282
- // Bulk update in chunks of 1000
283
- const chunkSize = 1000;
284
- await chunkedBulkOperation({
285
- items: jobsToUpdate,
286
- chunkSize,
287
- processChunk: async (chunk) => {
288
- await wixData.bulkUpdate(COLLECTIONS.JOBS, chunk);
289
- }
290
- });
226
+ // Remove system fields that cannot be updated
227
+ jobsToUpdate = jobsToUpdate.map(job => {
228
+ const { _createdDate, _updatedDate, ...rest } = job;
229
+ return rest;
230
+ });
291
231
 
292
- return { success: true, updated: jobsToUpdate.length };
293
- }
232
+ // Bulk update in chunks of 1000
233
+ const chunkSize = 1000;
234
+ await chunkedBulkOperation({
235
+ items: jobsToUpdate,
236
+ chunkSize,
237
+ processChunk: async chunk => {
238
+ await wixData.bulkUpdate('Jobs1', chunk);
239
+ },
240
+ });
294
241
 
295
- function fetchApplyLink(jobDetails) {
296
- return jobDetails.actions.applyOnWeb.url;
242
+ return { success: true, updated: jobsToUpdate.length };
297
243
  }
298
244
 
299
245
  function fetchJobLocation(jobDetails) {
300
- const jobLocation = {
301
- location: {
302
- latitude: parseFloat(jobDetails.location.latitude),
303
- longitude: parseFloat(jobDetails.location.longitude)
246
+ const isZeroLocation =
247
+ jobDetails.location.latitude === '0.0000' && jobDetails.location.longitude === '0.0000';
248
+ const jobLocation = {
249
+ location: isZeroLocation
250
+ ? {}
251
+ : {
252
+ latitude: parseFloat(jobDetails.location.latitude),
253
+ longitude: parseFloat(jobDetails.location.longitude),
304
254
  },
305
- city: jobDetails.location.city,
306
- country: jobDetails.location.country,
307
- formatted: [
308
- jobDetails.location.city,
309
- jobDetails.location.region,
310
- jobDetails.location.regionCode,
311
- jobDetails.location.country
312
- ].filter(Boolean).join(', '),
313
- streetAddress: {},
314
- subdivision: "",
315
- postalCode: ""
316
- };
317
- return jobLocation;
318
-
255
+ city: jobDetails.location.city,
256
+ country: jobDetails.location.country,
257
+ formatted: [
258
+ jobDetails.location.city,
259
+ jobDetails.location.region,
260
+ jobDetails.location.regionCode,
261
+ jobDetails.location.country,
262
+ ]
263
+ .filter(Boolean)
264
+ .join(', '),
265
+ streetAddress: {},
266
+ subdivision: '',
267
+ postalCode: '',
268
+ };
269
+ return jobLocation;
319
270
  }
320
271
 
321
272
 
@@ -327,13 +278,14 @@ function getSmartToken() {
327
278
  })
328
279
  .catch((error) => {
329
280
  console.error(error);
281
+ throw error;
330
282
  });
331
283
  }
332
284
 
333
285
 
334
286
  async function createApiKeyCollectionAndFillIt() {
335
287
  console.log("Creating ApiKey collection and filling it with the smart token");
336
- await createCollectionIfMissing(COLLECTIONS.API_KEY, COLLECTIONS_FIELDS.API_KEY);
288
+ await createCollectionIfMissing(COLLECTIONS.API_KEY, COLLECTIONS_FIELDS.API_KEY,null,'singleItem');
337
289
  console.log("Getting the smart token");
338
290
  const token = await getSmartToken();
339
291
  console.log("token is : ", token);
@@ -347,9 +299,9 @@ async function createApiKeyCollectionAndFillIt() {
347
299
 
348
300
 
349
301
  module.exports = {
350
- saveDataJobsToCMS,
351
- saveJobsDescriptionsAndLocationApplyUrlToCMS,
302
+ saveJobsDataToCMS,
303
+ saveJobsDescriptionsAndLocationToCMS,
352
304
  aggregateJobsByFieldToCMS,
353
305
  referenceJobsToField,
354
306
  createApiKeyCollectionAndFillIt,
355
- };
307
+ };