sr-npm 1.7.63 → 1.7.65

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.
@@ -1,7 +1,7 @@
1
1
  const COLLECTIONS = {
2
- AMOUNT_OF_JOBS_PER_DEPARTMENT: 'AmountOfJobsPerDepartment1',
3
- CITIES: 'cities1',
4
- JOBS: 'Jobs1',
2
+ AMOUNT_OF_JOBS_PER_DEPARTMENT: 'AmountOfJobsPerDepartment',
3
+ CITIES: 'cities',
4
+ JOBS: 'Jobs',
5
5
  API_KEY: 'ApiKey',
6
6
  }
7
7
 
@@ -9,31 +9,29 @@ const COLLECTIONS_FIELDS = {
9
9
  AMOUNT_OF_JOBS_PER_DEPARTMENT: [
10
10
  {key:'title', type: 'TEXT'},
11
11
  { key: 'count', type: 'NUMBER' },
12
+ { key: 'image', type: 'IMAGE' },
12
13
  ],
13
14
  CITIES: [
14
- {key:'title', type: 'TEXT'},
15
- { key: 'regionCode', type: 'TEXT' },
15
+ { key: 'title', type: 'TEXT' },
16
16
  { key: 'city', type: 'TEXT' },
17
- {key:'location', type: 'OBJECT'},
17
+ {key:'locationAddress', type: 'ADDRESS'},
18
18
  {key:'count', type: 'NUMBER'},
19
19
  {key:'country', type: 'TEXT'},
20
- {key:'remote', type: 'TEXT'},
21
- {key:'countryCode', type: 'TEXT'},
22
- {key:'manual', type: 'TEXT'},
23
- {key:'region', type: 'TEXT'},
24
- {key:'latitude', type: 'NUMBER'},
25
- {key:'longitude', type: 'NUMBER'},
26
20
  ],
27
21
  JOBS: [
28
22
  {key:'location', type: 'OBJECT'},
23
+ {key:'title', type: 'TEXT'},
24
+ {key:'locationAddress', type: 'ADDRESS'},
29
25
  {key:'postingStatus', type: 'TEXT'},
30
26
  {key:'country', type: 'TEXT'},
31
27
  {key:'department', type: 'TEXT'},
32
28
  {key:'language', type: 'TEXT'},
29
+ {key:'remote', type: 'BOOLEAN'},
33
30
  {key:'jobDescription', type: 'OBJECT'},
34
- {key:'cityText', type: 'TEXT'},
35
- {key:'departmentRef', type: 'REFERENCE', typeMetadata: { reference: { referencedCollectionId: 'AmountOfJobsPerDepartment1' } } },
36
- {key:'city', type: 'REFERENCE', typeMetadata: { reference: { referencedCollectionId: 'cities1' } } },
31
+ {key:'cityText', type: 'TEXT'},
32
+ {key:'applyLink', type: 'URL'},
33
+ {key:'departmentref', type: 'REFERENCE', typeMetadata: { reference: { referencedCollectionId: 'AmountOfJobsPerDepartment' } } },
34
+ {key:'city', type: 'REFERENCE', typeMetadata: { reference: { referencedCollectionId: 'cities' } } },
37
35
  ],
38
36
  API_KEY: [
39
37
  {key:'token', type: 'TEXT'},
package/backend/consts.js CHANGED
@@ -1,19 +1,10 @@
1
- const {
2
- saveJobsDataToCMS,
3
- saveJobsDescriptionsAndLocationToCMS,
4
- aggregateJobsByFieldToCMS,
5
- referenceJobsToField,
6
- createApiKeyCollectionAndFillIt,
7
- } = require('./data');
1
+ const {saveDataJobsToCMS,saveJobsDescriptionsAndLocationApplyUrlToCMS,aggregateJobsByFieldToCMS,referenceJobsToField,createApiKeyCollectionAndFillIt} = require('./data');
8
2
  const { createCollectionIfMissing } = require('@hisense-staging/velo-npm/backend');
9
3
  const { COLLECTIONS, COLLECTIONS_FIELDS } = require('./collectionConsts');
10
-
11
- const QUERY_MAX_LIMIT = 1000;
12
-
13
4
  const TASKS_NAMES = {
14
5
  SYNC_JOBS: 'syncJobsFromSRAPIToCMS',
15
6
  INSERT_JOBS_TO_CMS: 'insertJobsToCMS',
16
- INSERT_JOBS_DESCRIPTIONS_TO_CMS: 'insertJobsDescriptionsToCMS',
7
+ INSERT_JOBS_DESCRIPTIONS_LOCATION_APPLY_URL_TO_CMS: 'insertJobsDescriptionsLocationApplyUrlToCMS',
17
8
  FILL_JOBS_PER_CITY_COLLECTION: 'fillJobsPerCityCollection',
18
9
  FILL_JOBS_PER_DEPARTMENT_COLLECTION: 'fillJobsPerDepartmentCollection',
19
10
  REFERENCE_JOBS_TO_LOCATIONS: 'referenceJobsToLocations',
@@ -26,126 +17,103 @@ const TASKS_NAMES = {
26
17
 
27
18
 
28
19
  const TASKS = {
29
- [TASKS_NAMES.SYNC_JOBS]: {
30
- name: TASKS_NAMES.SYNC_JOBS,
31
- childTasks: [
32
- { name: TASKS_NAMES.CREATE_JOBS_COLLECTION },
33
- { name: TASKS_NAMES.CREATE_CITIES_COLLECTION },
34
- { name: TASKS_NAMES.CREATE_AMOUNT_OF_JOBS_PER_DEPARTMENT_COLLECTION },
35
- { name: TASKS_NAMES.INSERT_JOBS_TO_CMS },
36
- { name: TASKS_NAMES.INSERT_JOBS_DESCRIPTIONS_TO_CMS },
37
- { name: TASKS_NAMES.FILL_JOBS_PER_CITY_COLLECTION },
38
- { name: TASKS_NAMES.FILL_JOBS_PER_DEPARTMENT_COLLECTION },
39
- { name: TASKS_NAMES.REFERENCE_JOBS_TO_LOCATIONS },
40
- { name: TASKS_NAMES.REFERENCE_JOBS_TO_DEPARTMENT },
41
- ],
42
- scheduleChildrenSequentially: true,
43
- estimatedDurationSec: 30,
44
- },
45
- [TASKS_NAMES.CREATE_JOBS_COLLECTION]: {
46
- name: TASKS_NAMES.CREATE_JOBS_COLLECTION,
47
- getIdentifier: () => 'SHOULD_NEVER_SKIP',
48
- process: () => createCollectionIfMissing(COLLECTIONS.JOBS, COLLECTIONS_FIELDS.JOBS),
49
- shouldSkipCheck: () => false,
50
- estimatedDurationSec: 3,
51
- },
52
- [TASKS_NAMES.CREATE_CITIES_COLLECTION]: {
53
- name: TASKS_NAMES.CREATE_CITIES_COLLECTION,
54
- getIdentifier: () => 'SHOULD_NEVER_SKIP',
55
- process: () => createCollectionIfMissing(COLLECTIONS.CITIES, COLLECTIONS_FIELDS.CITIES),
56
- shouldSkipCheck: () => false,
57
- estimatedDurationSec: 3,
58
- },
59
- [TASKS_NAMES.CREATE_AMOUNT_OF_JOBS_PER_DEPARTMENT_COLLECTION]: {
60
- name: TASKS_NAMES.CREATE_AMOUNT_OF_JOBS_PER_DEPARTMENT_COLLECTION,
61
- getIdentifier: () => 'SHOULD_NEVER_SKIP',
62
- process: () =>
63
- createCollectionIfMissing(
64
- COLLECTIONS.AMOUNT_OF_JOBS_PER_DEPARTMENT,
65
- COLLECTIONS_FIELDS.AMOUNT_OF_JOBS_PER_DEPARTMENT
66
- ),
67
- shouldSkipCheck: () => false,
68
- estimatedDurationSec: 3,
69
- },
70
- [TASKS_NAMES.INSERT_JOBS_TO_CMS]: {
71
- name: TASKS_NAMES.INSERT_JOBS_TO_CMS,
72
- getIdentifier: () => 'SHOULD_NEVER_SKIP',
73
- process: saveJobsDataToCMS,
74
- shouldSkipCheck: () => false,
75
- estimatedDurationSec: 20,
76
- },
77
- [TASKS_NAMES.INSERT_JOBS_DESCRIPTIONS_TO_CMS]: {
78
- name: TASKS_NAMES.INSERT_JOBS_DESCRIPTIONS_TO_CMS,
79
- getIdentifier: () => 'SHOULD_NEVER_SKIP',
80
- process: saveJobsDescriptionsAndLocationToCMS,
81
- shouldSkipCheck: () => false,
82
- estimatedDurationSec: 20,
83
- },
84
- [TASKS_NAMES.FILL_JOBS_PER_CITY_COLLECTION]: {
85
- name: TASKS_NAMES.FILL_JOBS_PER_CITY_COLLECTION,
86
- getIdentifier: () => 'SHOULD_NEVER_SKIP',
87
- process: () =>
88
- aggregateJobsByFieldToCMS({
89
- field: COLLECTIONS_FIELDS.JOBS[6].key,
90
- collection: COLLECTIONS.CITIES,
91
- }),
92
- shouldSkipCheck: () => false,
93
- estimatedDurationSec: 3,
94
- },
95
- [TASKS_NAMES.FILL_JOBS_PER_DEPARTMENT_COLLECTION]: {
96
- name: TASKS_NAMES.FILL_JOBS_PER_DEPARTMENT_COLLECTION,
97
- getIdentifier: () => 'SHOULD_NEVER_SKIP',
98
- process: () =>
99
- aggregateJobsByFieldToCMS({
100
- field: COLLECTIONS_FIELDS.JOBS[3].key,
101
- collection: COLLECTIONS.AMOUNT_OF_JOBS_PER_DEPARTMENT,
102
- }),
103
- shouldSkipCheck: () => false,
104
- estimatedDurationSec: 3,
105
- },
106
- [TASKS_NAMES.REFERENCE_JOBS_TO_LOCATIONS]: {
107
- name: TASKS_NAMES.REFERENCE_JOBS_TO_LOCATIONS,
108
- getIdentifier: () => 'SHOULD_NEVER_SKIP',
109
- process: () =>
110
- referenceJobsToField({
111
- referenceField: COLLECTIONS_FIELDS.JOBS[8].key,
112
- sourceCollection: COLLECTIONS.CITIES,
113
- jobField: COLLECTIONS_FIELDS.JOBS[6].key,
114
- }),
115
- shouldSkipCheck: () => false,
116
- estimatedDurationSec: 3,
117
- },
118
- [TASKS_NAMES.REFERENCE_JOBS_TO_DEPARTMENT]: {
119
- name: TASKS_NAMES.REFERENCE_JOBS_TO_DEPARTMENT,
120
- getIdentifier: () => 'SHOULD_NEVER_SKIP',
121
- process: () =>
122
- referenceJobsToField({
123
- referenceField: COLLECTIONS_FIELDS.JOBS[7].key,
124
- sourceCollection: COLLECTIONS.AMOUNT_OF_JOBS_PER_DEPARTMENT,
125
- jobField: COLLECTIONS_FIELDS.JOBS[3].key,
126
- }),
127
- shouldSkipCheck: () => false,
128
- estimatedDurationSec: 3,
129
- },
130
- [TASKS_NAMES.CREATE_API_KEY_COLLECTION_AND_FILL_IT]: {
131
- name: TASKS_NAMES.CREATE_API_KEY_COLLECTION_AND_FILL_IT,
132
- getIdentifier:()=> "SHOULD_NEVER_SKIP",
133
- process:()=>createApiKeyCollectionAndFillIt(),
134
- shouldSkipCheck:()=>false,
135
- estimatedDurationSec:3
136
- }
137
- };
20
+ [TASKS_NAMES.SYNC_JOBS]: {
21
+ name: TASKS_NAMES.SYNC_JOBS,
22
+ childTasks: [
23
+ { name: TASKS_NAMES.CREATE_JOBS_COLLECTION },
24
+ { name: TASKS_NAMES.CREATE_CITIES_COLLECTION },
25
+ {name: TASKS_NAMES.CREATE_AMOUNT_OF_JOBS_PER_DEPARTMENT_COLLECTION},
26
+ { name: TASKS_NAMES.INSERT_JOBS_TO_CMS },
27
+ { name: TASKS_NAMES.INSERT_JOBS_DESCRIPTIONS_LOCATION_APPLY_URL_TO_CMS },
28
+ { name: TASKS_NAMES.FILL_JOBS_PER_CITY_COLLECTION },
29
+ { name: TASKS_NAMES.FILL_JOBS_PER_DEPARTMENT_COLLECTION },
30
+ { name: TASKS_NAMES.REFERENCE_JOBS_TO_LOCATIONS },
31
+ { name: TASKS_NAMES.REFERENCE_JOBS_TO_DEPARTMENT },
32
+ ],
33
+ scheduleChildrenSequentially: true,
34
+ estimatedDurationSec: 30,
35
+ },
36
+ [TASKS_NAMES.CREATE_JOBS_COLLECTION]: {
37
+ name: TASKS_NAMES.CREATE_JOBS_COLLECTION,
38
+ getIdentifier:()=> "SHOULD_NEVER_SKIP",
39
+ process:()=>createCollectionIfMissing(COLLECTIONS.JOBS, COLLECTIONS_FIELDS.JOBS,{ insert: 'ADMIN', update: 'ADMIN', remove: 'ADMIN', read: 'ANYONE' }),
40
+ shouldSkipCheck:()=>false,
41
+ estimatedDurationSec:3
42
+ },
43
+ [TASKS_NAMES.CREATE_CITIES_COLLECTION]: {
44
+ name: TASKS_NAMES.CREATE_CITIES_COLLECTION,
45
+ getIdentifier:()=> "SHOULD_NEVER_SKIP",
46
+ process:()=>createCollectionIfMissing(COLLECTIONS.CITIES, COLLECTIONS_FIELDS.CITIES),
47
+ shouldSkipCheck:()=>false,
48
+ estimatedDurationSec:3
49
+ },
50
+ [TASKS_NAMES.CREATE_AMOUNT_OF_JOBS_PER_DEPARTMENT_COLLECTION]: {
51
+ name: TASKS_NAMES.CREATE_AMOUNT_OF_JOBS_PER_DEPARTMENT_COLLECTION,
52
+ getIdentifier:()=> "SHOULD_NEVER_SKIP",
53
+ process:()=>createCollectionIfMissing(COLLECTIONS.AMOUNT_OF_JOBS_PER_DEPARTMENT, COLLECTIONS_FIELDS.AMOUNT_OF_JOBS_PER_DEPARTMENT),
54
+ shouldSkipCheck:()=>false,
55
+ estimatedDurationSec:3
56
+ },
57
+ [TASKS_NAMES.INSERT_JOBS_TO_CMS]: {
58
+ name: TASKS_NAMES.INSERT_JOBS_TO_CMS,
59
+ getIdentifier:()=> "SHOULD_NEVER_SKIP",
60
+ process:saveDataJobsToCMS,
61
+ shouldSkipCheck:()=>false,
62
+ estimatedDurationSec:20
63
+ },
64
+ [TASKS_NAMES.INSERT_JOBS_DESCRIPTIONS_LOCATION_APPLY_URL_TO_CMS]: {
65
+ name: TASKS_NAMES.INSERT_JOBS_DESCRIPTIONS_LOCATION_APPLY_URL_TO_CMS,
66
+ getIdentifier:()=> "SHOULD_NEVER_SKIP",
67
+ process:saveJobsDescriptionsAndLocationApplyUrlToCMS,
68
+ shouldSkipCheck:()=>false,
69
+ estimatedDurationSec:20
70
+ },
71
+ [TASKS_NAMES.FILL_JOBS_PER_CITY_COLLECTION]: {
72
+ name: TASKS_NAMES.FILL_JOBS_PER_CITY_COLLECTION,
73
+ getIdentifier:()=> "SHOULD_NEVER_SKIP",
74
+ process:()=>aggregateJobsByFieldToCMS({ field: 'cityText', collection: COLLECTIONS.CITIES }),
75
+ shouldSkipCheck:()=>false,
76
+ estimatedDurationSec:3
77
+ },
78
+ [TASKS_NAMES.FILL_JOBS_PER_DEPARTMENT_COLLECTION]: {
79
+ name: TASKS_NAMES.FILL_JOBS_PER_DEPARTMENT_COLLECTION,
80
+ getIdentifier:()=> "SHOULD_NEVER_SKIP",
81
+ process:()=>aggregateJobsByFieldToCMS({ field: 'department', collection: COLLECTIONS.AMOUNT_OF_JOBS_PER_DEPARTMENT }),
82
+ shouldSkipCheck:()=>false,
83
+ estimatedDurationSec:3
84
+ },
85
+ [TASKS_NAMES.REFERENCE_JOBS_TO_LOCATIONS]: {
86
+ name: TASKS_NAMES.REFERENCE_JOBS_TO_LOCATIONS,
87
+ getIdentifier:()=> "SHOULD_NEVER_SKIP",
88
+ process:()=>referenceJobsToField({ referenceField: 'city', sourceCollection: COLLECTIONS.CITIES, jobField: 'cityText' }),
89
+ shouldSkipCheck:()=>false,
90
+ estimatedDurationSec:3
91
+ },
92
+ [TASKS_NAMES.REFERENCE_JOBS_TO_DEPARTMENT]: {
93
+ name: TASKS_NAMES.REFERENCE_JOBS_TO_DEPARTMENT,
94
+ getIdentifier:()=> "SHOULD_NEVER_SKIP",
95
+ process:()=>referenceJobsToField({ referenceField: 'departmentref', sourceCollection: COLLECTIONS.AMOUNT_OF_JOBS_PER_DEPARTMENT, jobField: 'department' }),
96
+ shouldSkipCheck:()=>false,
97
+ estimatedDurationSec:3
98
+ },
99
+ [TASKS_NAMES.CREATE_API_KEY_COLLECTION_AND_FILL_IT]: {
100
+ name: TASKS_NAMES.CREATE_API_KEY_COLLECTION_AND_FILL_IT,
101
+ getIdentifier:()=> "SHOULD_NEVER_SKIP",
102
+ process:()=>createApiKeyCollectionAndFillIt(),
103
+ shouldSkipCheck:()=>false,
104
+ estimatedDurationSec:3
105
+ }
106
+ }
138
107
 
139
108
 
140
109
 
141
110
  const TASK_TYPE = {
142
- SCHEDULED: 'scheduled',
143
- EVENT: 'event',
144
- };
111
+ SCHEDULED: 'scheduled',
112
+ EVENT: 'event',
113
+ };
145
114
 
146
115
  module.exports = {
147
116
  TASKS_NAMES,
148
117
  TASK_TYPE,
149
118
  TASKS,
150
- QUERY_MAX_LIMIT
151
119
  };
package/backend/data.js CHANGED
@@ -1,257 +1,322 @@
1
1
  const { items: wixData } = require('@wix/data');
2
2
  const { fetchPositionsFromSRAPI, fetchJobDescription } = require('./fetchPositionsFromSRAPI');
3
+ const { chunkedBulkOperation } = require('./utils');
3
4
  const { createCollectionIfMissing } = require('@hisense-staging/velo-npm/backend');
4
5
  const { COLLECTIONS, COLLECTIONS_FIELDS } = require('./collectionConsts');
5
6
  const { secrets } = require("@wix/secrets");
6
7
  const { auth } = require('@wix/essentials');
7
- const { chunkedBulkOperation, delay, countJobsPerGivenField, fillCityLocation ,prepateToSaveArray,normalizeCityName} = require('./utils');
8
- const { getAllPositions } = require('./queries');
9
8
 
10
-
11
-
12
- async function saveJobsDataToCMS() {
13
- const positions = await fetchPositionsFromSRAPI();
14
- // bulk insert to jobs collection without descriptions first
15
- const jobsData = positions.content.map(position => {
16
- const basicJob = {
17
- _id: position.id,
18
- title: position.title,
19
- department: position.department.label,
20
- cityText: normalizeCityName(position.location.city),
21
- location: position.location,
22
- country: position.location.country,
23
- remote: position.location.remote,
24
- language: position.language.label,
25
- postingStatus: position.postingStatus,
26
- jobDescription: null, // Will be filled later
27
- };
28
- return basicJob;
29
- });
30
-
31
- const chunkSize = 1000;
32
- let totalSaved = 0;
33
- const totalChunks = Math.ceil(jobsData.length / chunkSize);
34
-
35
- console.log(
36
- `Processing ${jobsData.length} jobs in ${totalChunks} chunks of max ${chunkSize} items each`
37
- );
38
-
39
- await chunkedBulkOperation({
40
- items: jobsData,
41
- chunkSize,
42
- processChunk: async (chunk, chunkNumber) => {
43
- console.log(`Saving chunk ${chunkNumber}/${totalChunks}: ${chunk.length} jobs`);
44
- try {
45
- const result = await wixData.bulkSave('Jobs1', chunk);
46
- const saved = result.inserted + result.updated || chunk.length;
47
- totalSaved += saved;
48
- console.log(
49
- `✓ Chunk ${chunkNumber} saved successfully. Inserted: ${result.inserted}, Updated: ${result.updated}`
50
- );
51
- } catch (error) {
52
- console.error(`✗ Error saving chunk ${chunkNumber}:`, error);
53
- throw error;
54
- }
55
- },
56
- });
57
-
58
- console.log(`✓ All chunks processed. Total jobs saved: ${totalSaved}/${jobsData.length}`);
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();
59
14
  }
60
15
 
61
- async function saveJobsDescriptionsAndLocationToCMS() {
62
- console.log('🚀 Starting job descriptions update process for ALL jobs');
63
-
64
- try {
65
- let jobsWithNoDescriptions = await getJobsWithNoDescriptions();
66
- let totalUpdated = 0;
67
- let totalFailed = 0;
68
- let totalProcessed = 0;
69
-
70
- console.log(
71
- `Total jobs in database without descriptions: ${jobsWithNoDescriptions?.items?.length}`
72
- );
73
-
74
- if (jobsWithNoDescriptions.items.length === 0) {
75
- console.log('No jobs found in database');
76
- return { success: true, message: 'No jobs found' };
77
- }
78
-
79
-
80
- const API_CHUNK_SIZE = 80;
81
- const pageChunks = Math.ceil(jobsWithNoDescriptions.items.length / API_CHUNK_SIZE);
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`);
82
40
 
83
41
  await chunkedBulkOperation({
84
- items: jobsWithNoDescriptions.items,
85
- chunkSize: API_CHUNK_SIZE,
86
- processChunk: async (chunk, chunkNumber) => {
87
- console.log(` Processing API chunk ${chunkNumber}/${pageChunks} (${chunk.length} jobs)`);
88
- const chunkPromises = chunk.map(async job => {
89
- try {
90
- const jobDetails = await fetchJobDescription(job._id);
91
- const jobLocation = fetchJobLocation(jobDetails);
92
-
93
- const updatedJob = {
94
- ...job,
95
- locationAddress: jobLocation,
96
- jobDescription: jobDetails.jobAd.sections,
97
- };
98
- await wixData.update('Jobs1', updatedJob);
99
- return { success: true, jobId: job._id, title: job.title };
100
- } catch (error) {
101
- console.error(` ❌ Failed to update ${job.title} (${job._id}):`, error);
102
- return { success: false, jobId: job._id, title: job.title, error: error.message };
103
- }
104
- });
105
- const chunkResults = await Promise.all(chunkPromises);
106
- const chunkSuccesses = chunkResults.filter(r => r.success).length;
107
- const chunkFailures = chunkResults.filter(r => !r.success).length;
108
- totalUpdated += chunkSuccesses;
109
- totalFailed += chunkFailures;
110
- totalProcessed += chunk.length;
111
- console.log(
112
- ` API chunk ${chunkNumber} completed: ${chunkSuccesses} success, ${chunkFailures} failed`
113
- );
114
- if (chunkNumber * API_CHUNK_SIZE < jobsWithNoDescriptions.items.length) {
115
- console.log(' Waiting 2 seconds before next API chunk...');
116
- await delay(2000);
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
+ }
117
55
  }
118
- },
119
56
  });
57
+
58
+ console.log(`✓ All chunks processed. Total jobs saved: ${totalSaved}/${jobsData.length}`);
120
59
 
121
-
122
-
123
- console.log(`\n✅ Finished updating ALL job descriptions`);
124
- console.log(`📊 Final Results:`);
125
- console.log(` Total jobs processed: ${totalProcessed}`);
126
- console.log(` Total updated: ${totalUpdated}`);
127
- console.log(` Total failed: ${totalFailed}`);
128
-
129
- return {
130
- success: true,
131
- totalProcessed: totalProcessed,
132
- totalUpdated: totalUpdated,
133
- totalFailed: totalFailed,
134
- message: `Successfully updated ${totalUpdated} job descriptions out of ${totalProcessed} total jobs`,
135
- };
136
- } catch (error) {
137
- console.error('❌ Error in updateJobDescriptions:', error);
138
- throw error;
139
- }
140
60
  }
141
61
 
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
+ }
142
82
 
143
- function iterateOverAllJobs(results, field) {
144
- const jobsPerField = {};
145
- const cityLocations = {};
146
- countJobsPerGivenField(results, field, jobsPerField);
147
- if (field === 'cityText') {
148
- fillCityLocation(results, cityLocations);
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;
149
166
  }
150
- return { jobsPerField, cityLocations };
151
167
  }
152
168
 
153
169
  async function aggregateJobsByFieldToCMS({ field, collection }) {
154
- console.log(`counting jobs per ${field}.`);
155
- let results = await getAllPositions();
156
- const { jobsPerField, cityLocations } = iterateOverAllJobs(results, field);
157
- const toSave = prepateToSaveArray(jobsPerField, cityLocations, field);
158
- if (toSave.length === 0) {
159
- console.log('No jobs found.');
160
- return { success: true, message: 'No jobs to save.' };
161
- }
162
- try {
163
- const saveResult = await wixData.bulkSave(collection, toSave);
164
- console.log(`Saved ${toSave.length} ${field} counts to ${collection}.`);
165
- return { success: true, saved: toSave.length, result: saveResult };
166
- } catch (err) {
167
- console.error(`Error saving jobs per ${field}:`, err);
168
- return { success: false, error: err.message };
169
- }
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 = [];
196
+ 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.' };
223
+ }
224
+
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
233
  }
171
234
 
172
235
  async function getJobsWithNoDescriptions() {
173
- let jobswithoutdescriptionsQuery = await wixData
174
- .query('Jobs1')
175
- .limit(1000)
176
- .isEmpty('jobDescription')
177
- .find();
178
- return jobswithoutdescriptionsQuery;
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;
179
239
  }
180
240
 
181
- /**
182
- * @param {Object} params
183
- * @param {"city"|"departmentRef"} params.referenceField
184
- * @param {"cities1"|"AmountOfJobsPerDepartment1"} params.sourceCollection
185
- * @param {"cityText"|"department"} params.jobField
186
- */
187
- async function referenceJobsToField({ referenceField, sourceCollection, jobField }) {
188
- // Fetch all source items (cities or departments)
189
- const sources = await wixData.query(sourceCollection).limit(1000).find();
190
- const sourceMap = {};
191
- for (const item of sources.items) {
192
- sourceMap[item.title] = item._id;
193
- }
194
-
195
- // Fetch all jobs
196
- let jobsResults = await getAllPositions();
197
- let jobsToUpdate = [];
198
-
199
- for (const job of jobsResults) {
200
- const refId = sourceMap[job[jobField]];
201
- if (refId) {
202
- jobsToUpdate.push({
203
- ...job,
204
- [referenceField]: refId,
205
- });
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;
206
252
  }
207
- }
208
253
 
209
- // Remove system fields that cannot be updated
210
- jobsToUpdate = jobsToUpdate.map(job => {
211
- const { _createdDate, _updatedDate, ...rest } = job;
212
- return rest;
213
- });
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);
275
+
276
+ // Remove system fields that cannot be updated
277
+ jobsToUpdate = jobsToUpdate.map(job => {
278
+ const { _createdDate, _updatedDate, ...rest } = job;
279
+ return rest;
280
+ });
281
+
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
+ });
214
291
 
215
- // Bulk update in chunks of 1000
216
- const chunkSize = 1000;
217
- await chunkedBulkOperation({
218
- items: jobsToUpdate,
219
- chunkSize,
220
- processChunk: async chunk => {
221
- await wixData.bulkUpdate('Jobs1', chunk);
222
- },
223
- });
292
+ return { success: true, updated: jobsToUpdate.length };
293
+ }
224
294
 
225
- return { success: true, updated: jobsToUpdate.length };
295
+ function fetchApplyLink(jobDetails) {
296
+ return jobDetails.actions.applyOnWeb.url;
226
297
  }
227
298
 
228
299
  function fetchJobLocation(jobDetails) {
229
- const isZeroLocation =
230
- jobDetails.location.latitude === '0.0000' && jobDetails.location.longitude === '0.0000';
231
- const jobLocation = {
232
- location: isZeroLocation
233
- ? {}
234
- : {
235
- latitude: parseFloat(jobDetails.location.latitude),
236
- longitude: parseFloat(jobDetails.location.longitude),
300
+ const jobLocation = {
301
+ location: {
302
+ latitude: parseFloat(jobDetails.location.latitude),
303
+ longitude: parseFloat(jobDetails.location.longitude)
237
304
  },
238
- city: jobDetails.location.city,
239
- country: jobDetails.location.country,
240
- formatted: [
241
- jobDetails.location.city,
242
- jobDetails.location.region,
243
- jobDetails.location.regionCode,
244
- jobDetails.location.country,
245
- ]
246
- .filter(Boolean)
247
- .join(', '),
248
- streetAddress: {},
249
- subdivision: '',
250
- postalCode: '',
251
- };
252
- return jobLocation;
253
- }
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;
254
318
 
319
+ }
255
320
 
256
321
 
257
322
  function getSmartToken() {
@@ -268,7 +333,7 @@ function getSmartToken() {
268
333
 
269
334
  async function createApiKeyCollectionAndFillIt() {
270
335
  console.log("Creating ApiKey collection and filling it with the smart token");
271
- await createCollectionIfMissing(COLLECTIONS.API_KEY, COLLECTIONS_FIELDS.API_KEY,null,'singleItem');
336
+ await createCollectionIfMissing(COLLECTIONS.API_KEY, COLLECTIONS_FIELDS.API_KEY);
272
337
  console.log("Getting the smart token");
273
338
  const token = await getSmartToken();
274
339
  console.log("token is : ", token);
@@ -282,9 +347,9 @@ async function createApiKeyCollectionAndFillIt() {
282
347
 
283
348
 
284
349
  module.exports = {
285
- saveJobsDataToCMS,
286
- saveJobsDescriptionsAndLocationToCMS,
350
+ saveDataJobsToCMS,
351
+ saveJobsDescriptionsAndLocationApplyUrlToCMS,
287
352
  aggregateJobsByFieldToCMS,
288
353
  referenceJobsToField,
289
354
  createApiKeyCollectionAndFillIt,
290
- };
355
+ };
@@ -1,7 +1,6 @@
1
1
  const { fetch } = require('wix-fetch');
2
2
  const { items: wixData } = require('@wix/data');
3
3
  const { COLLECTIONS } = require('./collectionConsts');
4
-
5
4
  async function makeSmartRecruitersRequest(path,token) {
6
5
  // const baseUrl = 'https://api.smartrecruiters.com'; // PROD
7
6
  const baseUrl = 'https://aoxley54.wixstudio.com/external-template/_functions'; // TEST
@@ -35,7 +34,7 @@ async function fetchPositionsFromSRAPI() {
35
34
  let allPositions = [];
36
35
  let totalFound = 0;
37
36
  let nextPageId = null; // Start with no page ID for the first request
38
- let page = 0;
37
+ let pageCount = 0;
39
38
  const MAX_PAGES = 30 // Safety limit to prevent infinite loops
40
39
  const token = await getSmartTokenFromCMS();
41
40
 
@@ -43,44 +42,45 @@ async function fetchPositionsFromSRAPI() {
43
42
 
44
43
  do {
45
44
  try {
46
- page++;
47
-
45
+ pageCount++;
46
+
48
47
  // Build the API path - first request has no page parameter, subsequent use nextPageId
49
48
  let apiPath = '/jobs?limit=50';
50
49
  if (nextPageId) {
51
50
  apiPath += `&nextPageId=${nextPageId}`;
52
51
  }
53
52
 
54
- console.log(`Fetching page ${page} with path: ${apiPath}`);
53
+ console.log(`Fetching page ${pageCount} with path: ${apiPath}`);
55
54
  const response = await makeSmartRecruitersRequest(apiPath,token);
56
55
 
57
56
  // Add positions from this page to our collection
58
57
  if (response.content && Array.isArray(response.content)) {
59
58
  allPositions = allPositions.concat(response.content);
60
- console.log(`Page ${page}: Found ${response.content.length} positions`);
59
+ console.log(`Page ${pageCount}: Found ${response.content.length} positions`);
61
60
  }
62
-
61
+
63
62
  // Update total count from first response
64
- if (page === 1) {
63
+ if (pageCount === 1) {
65
64
  totalFound = response.totalFound || 0;
66
65
  console.log(`Total positions available: ${totalFound}`);
67
66
  }
68
-
67
+
69
68
  // Get the nextPageId for the next iteration
70
- nextPageId = response.nextPageId && response.nextPageId !== '' ? response.nextPageId : null;
71
-
69
+ nextPageId = response.nextPageId && response.nextPageId !== "" ? response.nextPageId : null;
70
+
72
71
  if (nextPageId) {
73
72
  console.log(`Next page ID: ${nextPageId}`);
74
73
  } else {
75
74
  console.log('No more pages to fetch');
76
75
  }
76
+
77
77
  } catch (error) {
78
- console.error(`Error fetching page ${page}:`, error);
78
+ console.error(`Error fetching page ${pageCount}:`, error);
79
79
  throw error;
80
80
  }
81
-
81
+
82
82
  // Safety check to prevent infinite loops
83
- if (page >= MAX_PAGES) {
83
+ if (pageCount >= MAX_PAGES) {
84
84
  console.warn(`Reached maximum page limit of ${MAX_PAGES}. Stopping pagination.`);
85
85
  break;
86
86
  }
@@ -89,12 +89,12 @@ async function fetchPositionsFromSRAPI() {
89
89
  console.log(`Finished fetching all pages. Total positions collected: ${allPositions.length}`);
90
90
 
91
91
  // Return response in the same format as before, but with all positions
92
- const result = {
92
+ const result = {
93
93
  totalFound: totalFound,
94
94
  offset: 0,
95
95
  limit: allPositions.length,
96
- nextPageId: '', // Always empty since we've fetched everything
97
- content: allPositions,
96
+ nextPageId: "", // Always empty since we've fetched everything
97
+ content: allPositions
98
98
  };
99
99
 
100
100
  const amountOfUniqueJobs = new Set(allPositions.map(job => job.id)).size;
@@ -108,7 +108,8 @@ async function fetchPositionsFromSRAPI() {
108
108
  }
109
109
 
110
110
  async function fetchJobDescription(jobId) {
111
- return await makeSmartRecruitersRequest(`/jobs/${jobId}`);
111
+ const token = await getSmartTokenFromCMS();
112
+ return await makeSmartRecruitersRequest(`/jobs/${jobId}`,token);
112
113
  }
113
114
 
114
115
  async function getSmartTokenFromCMS() {
@@ -122,6 +123,6 @@ async function getSmartTokenFromCMS() {
122
123
 
123
124
 
124
125
  module.exports = {
125
- fetchPositionsFromSRAPI,
126
- fetchJobDescription,
126
+ fetchPositionsFromSRAPI,
127
+ fetchJobDescription,
127
128
  };
package/backend/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  module.exports = {
2
- ...require('./utils'),
3
- ...require('./queries'),
4
- ...require('./fetchPositionsFromSRAPI'),
5
- ...require('./consts'),
6
- };
2
+ ...require('./utils'),
3
+ ...require('./queries'),
4
+ ...require('./fetchPositionsFromSRAPI'),
5
+ ...require('./consts'),
6
+ };
7
+
@@ -1,22 +1,19 @@
1
- const { items: wixData } = require('@wix/data');
1
+ const { COLLECTIONS } = require('./collectionConsts');
2
+ const { items } = require('@wix/data');
3
+
2
4
 
3
5
  async function getAllPositions() {
4
- return wixData
5
- .query('Jobs1')
6
- .limit(1000)
7
- .find()
8
- .then(result => result.items);
6
+ return await items.query(COLLECTIONS.JOBS).find().items;
7
+
9
8
  }
10
9
 
11
10
  async function getPositionsByField(field, value) {
12
- return wixData
13
- .query('Jobs1')
14
- .eq(field, value)
15
- .find()
16
- .then(result => result.items);
11
+ return await items.query(COLLECTIONS.JOBS).where(field, value).find().items;
12
+
17
13
  }
18
14
 
15
+
19
16
  module.exports = {
20
- getAllPositions,
21
- getPositionsByField,
22
- };
17
+ getAllPositions,
18
+ getPositionsByField,
19
+ };
package/backend/utils.js CHANGED
@@ -1,75 +1,11 @@
1
1
  async function chunkedBulkOperation({ items, chunkSize, processChunk }) {
2
- for (let i = 0; i < items.length; i += chunkSize) {
3
- const chunk = items.slice(i, i + chunkSize);
4
- await processChunk(chunk, Math.floor(i / chunkSize) + 1);
5
- }
6
- }
7
-
8
- async function delay(ms) {
9
- await new Promise(resolve => setTimeout(resolve, ms));
10
- }
11
-
12
- function countJobsPerGivenField(jobs, field,jobsPerField) {
13
- for (const job of jobs) {
14
- if (!job[field]) {
15
- throw new Error(`Job ${job._id} has no ${field} field`);
16
- }
17
- jobsPerField[job[field]] = (jobsPerField[job[field]] || 0) + 1;
18
- }
19
- }
20
-
21
- function fillCityLocation(jobs, cityLocations) {
22
- for (const job of jobs) {
23
- if (!cityLocations[job.cityText]) {
24
- cityLocations[job.cityText] = job.location;
2
+ for (let i = 0; i < items.length; i += chunkSize) {
3
+ const chunk = items.slice(i, i + chunkSize);
4
+ await processChunk(chunk, Math.floor(i / chunkSize) + 1);
25
5
  }
26
- }
27
6
  }
28
7
 
29
- function prepareToSaveArray(jobsPerField, cityLocations, field) {
30
- if (field === 'cityText') {
31
- return Object.entries(jobsPerField).map(([value, amount]) => {
32
- const loc = cityLocations[value] || {};
33
- value = normalizeCityName(value);
34
- return {
35
- title: value,
36
- _id: value.replace(/\s+/g, ''),
37
- count: amount,
38
- location: loc,
39
- countryCode: loc.countryCode,
40
- country: loc.country,
41
- region: loc.region,
42
- city: loc.city,
43
- manual: loc.manual.toString(),
44
- remote: loc.remote.toString(),
45
- regionCode: loc.regionCode,
46
- latitude: loc.latitude,
47
- longitude: loc.longitude,
48
- };
49
- });
50
- } else {
51
- return Object.entries(jobsPerField).map(([value, amount]) => ({
52
- title: value,
53
- _id: value.replace(/\s+/g, ''),
54
- count: amount,
55
- }));
56
- }
57
- }
58
-
59
- function normalizeCityName(city) {
60
- if (!city) return city;
61
- // Remove accents/diacritics, trim whitespace
62
- return city
63
- .normalize('NFD')
64
- .replace(/\p{Diacritic}/gu, '')
65
- .trim();
66
- }
67
8
 
68
9
  module.exports = {
69
- chunkedBulkOperation,
70
- delay,
71
- countJobsPerGivenField,
72
- fillCityLocation,
73
- prepateToSaveArray: prepareToSaveArray,
74
- normalizeCityName,
75
- };
10
+ chunkedBulkOperation,
11
+ };
package/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  module.exports = {
2
- ...require('./public'),
3
- ...require('./pages'),
4
- ...require('./backend'),
5
- };
2
+ ...require('./public'),
3
+ ...require('./pages'),
4
+ ...require('./backend'),
5
+ };
6
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sr-npm",
3
- "version": "1.7.63",
3
+ "version": "1.7.65",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -17,12 +17,9 @@
17
17
  },
18
18
  "homepage": "https://github.com/psdevteamenterprise/sr-npm#readme",
19
19
  "dependencies": {
20
- "@hisense-staging/velo-npm": "3.0.19",
20
+ "@hisense-staging/velo-npm": "1.7.251",
21
21
  "@wix/data": "^1.0.211",
22
22
  "@wix/essentials": "^0.1.24",
23
- "@wix/secrets": "^1.0.53"
24
- },
25
- "devDependencies": {
26
- "prettier": "^3.6.2"
23
+ "@wix/secrets": "1.0.53"
27
24
  }
28
25
  }
package/.prettierrc DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "singleQuote": true,
3
- "trailingComma": "es5",
4
- "printWidth": 100,
5
- "tabWidth": 2,
6
- "semi": true,
7
- "bracketSpacing": true,
8
- "arrowParens": "avoid",
9
- "endOfLine": "lf",
10
- "quoteProps": "as-needed",
11
- "jsxSingleQuote": false,
12
- "jsxBracketSameLine": false,
13
- "requirePragma": false,
14
- "insertPragma": false,
15
- "proseWrap": "preserve"
16
- }