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 +16 -0
- package/backend/consts.js +175 -109
- package/backend/data.js +231 -319
- package/backend/fetchPositionsFromSRAPI.js +30 -43
- package/backend/index.js +5 -6
- package/backend/queries.js +15 -11
- package/backend/utils.js +69 -5
- package/index.js +4 -5
- package/package.json +6 -5
- package/backend/collectionConsts.js +0 -46
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 {
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
175
|
+
SCHEDULED: 'scheduled',
|
|
176
|
+
EVENT: 'event',
|
|
177
|
+
};
|
|
114
178
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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 {
|
|
5
|
-
const {
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return jobswithoutdescriptionsQuery;
|
|
239
|
-
}
|
|
68
|
+
console.log(
|
|
69
|
+
`Total jobs in database without descriptions: ${jobsWithNoDescriptions.totalCount}`
|
|
70
|
+
);
|
|
240
71
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
277
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
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
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
16
|
-
'x-smarttoken':
|
|
17
|
-
|
|
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
|
|
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
|
-
|
|
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 ${
|
|
54
|
-
const response = await makeSmartRecruitersRequest(apiPath
|
|
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 ${
|
|
57
|
+
console.log(`Page ${page}: Found ${response.content.length} positions`);
|
|
60
58
|
}
|
|
61
|
-
|
|
59
|
+
|
|
62
60
|
// Update total count from first response
|
|
63
|
-
if (
|
|
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 !==
|
|
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 ${
|
|
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 (
|
|
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:
|
|
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
|
-
|
|
126
|
-
|
|
112
|
+
fetchPositionsFromSRAPI,
|
|
113
|
+
fetchJobDescription,
|
|
127
114
|
};
|
package/backend/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
...require('./utils'),
|
|
3
|
+
...require('./queries'),
|
|
4
|
+
...require('./fetchPositionsFromSRAPI'),
|
|
5
|
+
...require('./consts'),
|
|
6
|
+
};
|
package/backend/queries.js
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
const { COLLECTIONS
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
21
|
+
getAllPositions,
|
|
22
|
+
getPositionsByField,
|
|
23
|
+
};
|
package/backend/utils.js
CHANGED
|
@@ -1,11 +1,75 @@
|
|
|
1
1
|
async function chunkedBulkOperation({ items, chunkSize, processChunk }) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
11
|
-
|
|
69
|
+
chunkedBulkOperation,
|
|
70
|
+
delay,
|
|
71
|
+
countJobsPerGivenField,
|
|
72
|
+
fillCityLocation,
|
|
73
|
+
prepateToSaveArray,
|
|
74
|
+
normalizeCityName,
|
|
75
|
+
};
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sr-npm",
|
|
3
|
-
"version": "1.7.
|
|
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.
|
|
21
|
-
"@wix/data": "^1.0.211"
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
};
|