sr-npm 1.7.35 → 1.7.36

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