sr-npm 1.7.38 → 1.7.40

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