sr-npm 1.7.36 → 1.7.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/collectionConsts.js +46 -0
- package/backend/consts.js +109 -175
- package/backend/data.js +314 -299
- package/backend/fetchPositionsFromSRAPI.js +43 -30
- package/backend/index.js +6 -5
- package/backend/queries.js +11 -15
- package/backend/utils.js +6 -23
- package/index.js +5 -4
- package/package.json +4 -5
- package/.prettierrc +0 -16
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
};
|
package/backend/consts.js
CHANGED
|
@@ -1,185 +1,119 @@
|
|
|
1
|
-
const {
|
|
2
|
-
saveJobsDataToCMS,
|
|
3
|
-
saveJobsDescriptionsAndLocationToCMS,
|
|
4
|
-
aggregateJobsByFieldToCMS,
|
|
5
|
-
referenceJobsToField,
|
|
6
|
-
} = require('./data');
|
|
1
|
+
const {saveDataJobsToCMS,saveJobsDescriptionsAndLocationApplyUrlToCMS,aggregateJobsByFieldToCMS,referenceJobsToField,createApiKeyCollectionAndFillIt} = require('./data');
|
|
7
2
|
const { createCollectionIfMissing } = require('@hisense-staging/velo-npm/backend');
|
|
8
|
-
|
|
9
|
-
const QUERY_MAX_LIMIT = 1000;
|
|
10
|
-
|
|
3
|
+
const { COLLECTIONS, COLLECTIONS_FIELDS } = require('./collectionConsts');
|
|
11
4
|
const TASKS_NAMES = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const TASKS = {
|
|
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
|
-
};
|
|
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
|
+
}
|
|
127
17
|
|
|
128
|
-
const COLLECTIONS = {
|
|
129
|
-
AMOUNT_OF_JOBS_PER_DEPARTMENT: 'AmountOfJobsPerDepartment',
|
|
130
|
-
CITIES: 'cities',
|
|
131
|
-
JOBS: 'Jobs',
|
|
132
|
-
};
|
|
133
18
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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' } },
|
|
19
|
+
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,
|
|
165
35
|
},
|
|
166
|
-
{
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
|
170
42
|
},
|
|
171
|
-
|
|
172
|
-
|
|
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
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
173
109
|
|
|
174
110
|
const TASK_TYPE = {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
};
|
|
111
|
+
SCHEDULED: 'scheduled',
|
|
112
|
+
EVENT: 'event',
|
|
113
|
+
};
|
|
178
114
|
|
|
179
|
-
module.exports = {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
COLLECTIONS,
|
|
184
|
-
QUERY_MAX_LIMIT,
|
|
115
|
+
module.exports = {
|
|
116
|
+
TASKS_NAMES,
|
|
117
|
+
TASK_TYPE,
|
|
118
|
+
TASKS,
|
|
185
119
|
};
|
package/backend/data.js
CHANGED
|
@@ -1,338 +1,353 @@
|
|
|
1
1
|
const { items: wixData } = require('@wix/data');
|
|
2
2
|
const { fetchPositionsFromSRAPI, fetchJobDescription } = require('./fetchPositionsFromSRAPI');
|
|
3
|
-
const { chunkedBulkOperation
|
|
4
|
-
const {
|
|
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');
|
|
5
8
|
|
|
6
9
|
// Utility function to normalize city names
|
|
7
10
|
function normalizeCityName(city) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
.normalize('NFD')
|
|
12
|
-
.replace(/\p{Diacritic}/gu, '')
|
|
13
|
-
.trim();
|
|
11
|
+
if (!city) return city;
|
|
12
|
+
// Remove accents/diacritics, trim whitespace
|
|
13
|
+
return city.normalize('NFD').replace(/\p{Diacritic}/gu, '').trim();
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
async function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
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,
|
|
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,
|
|
95
44
|
processChunk: async (chunk, chunkNumber) => {
|
|
96
|
-
|
|
97
|
-
const chunkPromises = chunk.map(async job => {
|
|
45
|
+
console.log(`Saving chunk ${chunkNumber}/${totalChunks}: ${chunk.length} jobs`);
|
|
98
46
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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 };
|
|
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}`);
|
|
109
51
|
} catch (error) {
|
|
110
|
-
|
|
111
|
-
|
|
52
|
+
console.error(`✗ Error saving chunk ${chunkNumber}:`, error);
|
|
53
|
+
throw error;
|
|
112
54
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
});
|
|
129
|
-
|
|
130
|
-
console.log(
|
|
131
|
-
`📄 Page ${pageNumber} completed. Updated: ${totalUpdated}, Failed: ${totalFailed}, Total processed: ${totalProcessed}/${jobsWithNoDescriptions.totalCount}`
|
|
132
|
-
);
|
|
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 delay(3000);
|
|
143
|
-
}
|
|
144
|
-
} while (jobsWithNoDescriptions.hasNext());
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
console.log(`✓ All chunks processed. Total jobs saved: ${totalSaved}/${jobsData.length}`);
|
|
145
59
|
|
|
146
|
-
|
|
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}`);
|
|
60
|
+
}
|
|
152
61
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
62
|
+
async function saveJobsDescriptionsAndLocationApplyUrlToCMS() {
|
|
63
|
+
|
|
64
|
+
console.log('🚀 Starting job descriptions update process for ALL jobs using pagination...');
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
let jobsWithNoDescriptions = await getJobsWithNoDescriptions();
|
|
68
|
+
let totalUpdated = 0;
|
|
69
|
+
let totalFailed = 0;
|
|
70
|
+
let totalProcessed = 0;
|
|
71
|
+
let pageNumber = 1;
|
|
72
|
+
|
|
73
|
+
// Start with the first page query - limit to 100 jobs per page
|
|
74
|
+
// let jobsQuery = await wixData.query("Jobs").limit(300).find();
|
|
75
|
+
|
|
76
|
+
console.log(`Total jobs in database without descriptions: ${jobsWithNoDescriptions.totalCount}`);
|
|
77
|
+
|
|
78
|
+
if (jobsWithNoDescriptions.totalCount === 0) {
|
|
79
|
+
console.log('No jobs found in database');
|
|
80
|
+
return { success: true, message: 'No jobs found' };
|
|
81
|
+
}
|
|
82
|
+
|
|
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 = jobDetails.actions.applyOnWeb.url;
|
|
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);
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
async function aggregateJobsByFieldToCMS({ field, collection }) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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);
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
title: value,
|
|
204
|
+
_id: value.replace(/\s+/g, ''),
|
|
205
|
+
count: amount,
|
|
206
|
+
locationAddress: locAddress,
|
|
207
|
+
country: loc.country,
|
|
208
|
+
city: loc.city,
|
|
209
|
+
};
|
|
210
|
+
});
|
|
179
211
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
toSave = Object.entries(jobsPerField).map(([value, amount]) => {
|
|
184
|
-
const loc = cityLocations[value] || {};
|
|
185
|
-
value = normalizeCityName(value);
|
|
186
|
-
|
|
187
|
-
return {
|
|
212
|
+
else{
|
|
213
|
+
// Prepare array for bulkSave
|
|
214
|
+
toSave = Object.entries(jobsPerField).map(([value, amount]) => ({
|
|
188
215
|
title: value,
|
|
189
216
|
_id: value.replace(/\s+/g, ''),
|
|
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,
|
|
217
|
+
count: amount
|
|
209
218
|
}));
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
219
|
+
}
|
|
220
|
+
if (toSave.length === 0) {
|
|
221
|
+
console.log('No jobs found.');
|
|
222
|
+
return { success: true, message: 'No jobs to save.' };
|
|
223
|
+
}
|
|
215
224
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
+
}
|
|
224
233
|
}
|
|
225
234
|
|
|
226
235
|
async function getJobsWithNoDescriptions() {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
.find();
|
|
232
|
-
return jobswithoutdescriptionsQuery;
|
|
236
|
+
|
|
237
|
+
// let jobswithoutdescriptionsQuery = await wixData.query("Jobs").limit(1000).isEmpty("jobDescription").find(); // with 900 as the limit, 429 error won't happen
|
|
238
|
+
let jobswithoutdescriptionsQuery = await wixData.query("Jobs").limit(1000).isEmpty("applyLink").find(); // with 900 as the limit, 429 error won't happen
|
|
239
|
+
return jobswithoutdescriptionsQuery;
|
|
233
240
|
}
|
|
234
241
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
242
|
+
async function referenceJobsToField({
|
|
243
|
+
referenceField, // e.g., "city" or "department"
|
|
244
|
+
sourceCollection, // e.g., "cities" or "departments"
|
|
245
|
+
jobField, // e.g., "cityText" or "department"
|
|
246
|
+
|
|
247
|
+
}) {
|
|
248
|
+
// Fetch all source items (cities or departments)
|
|
249
|
+
const sources = await wixData.query(sourceCollection).limit(1000).find();
|
|
250
|
+
const sourceMap = {};
|
|
251
|
+
for (const item of sources.items) {
|
|
252
|
+
sourceMap[item.title] = item._id;
|
|
253
|
+
}
|
|
248
254
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
255
|
+
// Fetch all jobs
|
|
256
|
+
let jobsResults = await wixData.query("Jobs").limit(1000).find();
|
|
257
|
+
let jobsToUpdate = [];
|
|
258
|
+
console.log('jobsResults',jobsResults.items);
|
|
253
259
|
|
|
254
|
-
|
|
255
|
-
|
|
260
|
+
do {
|
|
261
|
+
for (const job of jobsResults.items) {
|
|
262
|
+
const refId = sourceMap[job[jobField]];
|
|
263
|
+
if (refId) {
|
|
264
|
+
jobsToUpdate.push({
|
|
265
|
+
...job,
|
|
266
|
+
[referenceField]: refId
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (jobsResults.hasNext()) {
|
|
271
|
+
jobsResults = await jobsResults.next();
|
|
272
|
+
} else {
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
} while (true);
|
|
276
|
+
|
|
277
|
+
// Remove system fields that cannot be updated
|
|
278
|
+
jobsToUpdate = jobsToUpdate.map(job => {
|
|
279
|
+
const { _createdDate, _updatedDate, ...rest } = job;
|
|
280
|
+
return rest;
|
|
281
|
+
});
|
|
256
282
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
}
|
|
283
|
+
// Bulk update in chunks of 1000
|
|
284
|
+
const chunkSize = 1000;
|
|
285
|
+
await chunkedBulkOperation({
|
|
286
|
+
items: jobsToUpdate,
|
|
287
|
+
chunkSize,
|
|
288
|
+
processChunk: async (chunk) => {
|
|
289
|
+
await wixData.bulkUpdate("Jobs", chunk);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
267
292
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
console.warn(`Max iterations (${maxIterations}) reached. Stopping to prevent infinite loop.`);
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
293
|
+
return { success: true, updated: jobsToUpdate.length };
|
|
294
|
+
}
|
|
273
295
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
296
|
+
function fetchJobLocation(jobDetails) {
|
|
297
|
+
const isZeroLocation = jobDetails.location.latitude === "0.0000" && jobDetails.location.longitude === "0.0000";
|
|
298
|
+
const jobLocation = {
|
|
299
|
+
location: isZeroLocation ? {} : {
|
|
300
|
+
latitude: parseFloat(jobDetails.location.latitude),
|
|
301
|
+
longitude: parseFloat(jobDetails.location.longitude)
|
|
302
|
+
},
|
|
303
|
+
city: jobDetails.location.city,
|
|
304
|
+
country: jobDetails.location.country,
|
|
305
|
+
formatted: [
|
|
306
|
+
jobDetails.location.city,
|
|
307
|
+
jobDetails.location.region,
|
|
308
|
+
jobDetails.location.regionCode,
|
|
309
|
+
jobDetails.location.country
|
|
310
|
+
].filter(Boolean).join(', '),
|
|
311
|
+
streetAddress: {},
|
|
312
|
+
subdivision: "",
|
|
313
|
+
postalCode: ""
|
|
314
|
+
};
|
|
315
|
+
return jobLocation;
|
|
285
316
|
|
|
286
|
-
|
|
287
|
-
jobsToUpdate = jobsToUpdate.map(job => {
|
|
288
|
-
const { _createdDate, _updatedDate, ...rest } = job;
|
|
289
|
-
return rest;
|
|
290
|
-
});
|
|
317
|
+
}
|
|
291
318
|
|
|
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
|
-
});
|
|
301
319
|
|
|
302
|
-
|
|
320
|
+
function getSmartToken() {
|
|
321
|
+
const elevatedGetSecretValue = auth.elevate(secrets.getSecretValue);
|
|
322
|
+
return elevatedGetSecretValue("x-smarttoken")
|
|
323
|
+
.then((secret) => {
|
|
324
|
+
return secret;
|
|
325
|
+
})
|
|
326
|
+
.catch((error) => {
|
|
327
|
+
console.error(error);
|
|
328
|
+
});
|
|
303
329
|
}
|
|
304
330
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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;
|
|
331
|
+
|
|
332
|
+
async function createApiKeyCollectionAndFillIt() {
|
|
333
|
+
console.log("Creating ApiKey collection and filling it with the smart token");
|
|
334
|
+
await createCollectionIfMissing(COLLECTIONS.API_KEY, COLLECTIONS_FIELDS.API_KEY);
|
|
335
|
+
console.log("Getting the smart token");
|
|
336
|
+
const token = await getSmartToken();
|
|
337
|
+
console.log("token is : ", token);
|
|
338
|
+
console.log("Inserting the smart token into the ApiKey collection");
|
|
339
|
+
await wixData.insert("ApiKey", {
|
|
340
|
+
token: token.value
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
console.log("Smart token inserted into the ApiKey collection");
|
|
331
344
|
}
|
|
332
345
|
|
|
346
|
+
|
|
333
347
|
module.exports = {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
348
|
+
saveDataJobsToCMS,
|
|
349
|
+
saveJobsDescriptionsAndLocationApplyUrlToCMS,
|
|
350
|
+
aggregateJobsByFieldToCMS,
|
|
351
|
+
referenceJobsToField,
|
|
352
|
+
createApiKeyCollectionAndFillIt,
|
|
353
|
+
};
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
const { fetch } = require('wix-fetch');
|
|
2
|
+
const { items: wixData } = require('@wix/data');
|
|
2
3
|
|
|
3
|
-
async function makeSmartRecruitersRequest(path) {
|
|
4
|
+
async function makeSmartRecruitersRequest(path,token) {
|
|
4
5
|
// const baseUrl = 'https://api.smartrecruiters.com'; // PROD
|
|
5
6
|
const baseUrl = 'https://aoxley54.wixstudio.com/external-template/_functions'; // TEST
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
const fullUrl = `${baseUrl}${path}`;
|
|
8
|
+
|
|
9
|
+
//console.log(`Making request to: ${fullUrl}`);
|
|
8
10
|
try {
|
|
9
11
|
const response = await fetch(fullUrl, {
|
|
10
12
|
method: 'GET',
|
|
11
13
|
headers: {
|
|
12
14
|
'Accept-Language': 'en',
|
|
13
|
-
accept: 'application/json',
|
|
14
|
-
'x-smarttoken':
|
|
15
|
-
Cookie:
|
|
16
|
-
|
|
17
|
-
},
|
|
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
|
+
}
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
if (response.ok) {
|
|
@@ -33,51 +34,53 @@ async function fetchPositionsFromSRAPI() {
|
|
|
33
34
|
let allPositions = [];
|
|
34
35
|
let totalFound = 0;
|
|
35
36
|
let nextPageId = null; // Start with no page ID for the first request
|
|
36
|
-
let
|
|
37
|
-
const MAX_PAGES = 30
|
|
37
|
+
let pageCount = 0;
|
|
38
|
+
const MAX_PAGES = 30 // Safety limit to prevent infinite loops
|
|
39
|
+
const token = await getSmartTokenFromCMS();
|
|
38
40
|
|
|
39
41
|
console.log('Starting to fetch all positions with pagination...');
|
|
40
42
|
|
|
41
43
|
do {
|
|
42
44
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
pageCount++;
|
|
46
|
+
|
|
45
47
|
// Build the API path - first request has no page parameter, subsequent use nextPageId
|
|
46
48
|
let apiPath = '/jobs?limit=50';
|
|
47
49
|
if (nextPageId) {
|
|
48
50
|
apiPath += `&nextPageId=${nextPageId}`;
|
|
49
51
|
}
|
|
50
|
-
|
|
51
|
-
console.log(`Fetching page ${
|
|
52
|
-
const response = await makeSmartRecruitersRequest(apiPath);
|
|
53
|
-
|
|
52
|
+
|
|
53
|
+
console.log(`Fetching page ${pageCount} with path: ${apiPath}`);
|
|
54
|
+
const response = await makeSmartRecruitersRequest(apiPath,token);
|
|
55
|
+
|
|
54
56
|
// Add positions from this page to our collection
|
|
55
57
|
if (response.content && Array.isArray(response.content)) {
|
|
56
58
|
allPositions = allPositions.concat(response.content);
|
|
57
|
-
console.log(`Page ${
|
|
59
|
+
console.log(`Page ${pageCount}: Found ${response.content.length} positions`);
|
|
58
60
|
}
|
|
59
|
-
|
|
61
|
+
|
|
60
62
|
// Update total count from first response
|
|
61
|
-
if (
|
|
63
|
+
if (pageCount === 1) {
|
|
62
64
|
totalFound = response.totalFound || 0;
|
|
63
65
|
console.log(`Total positions available: ${totalFound}`);
|
|
64
66
|
}
|
|
65
|
-
|
|
67
|
+
|
|
66
68
|
// Get the nextPageId for the next iteration
|
|
67
|
-
nextPageId = response.nextPageId && response.nextPageId !==
|
|
68
|
-
|
|
69
|
+
nextPageId = response.nextPageId && response.nextPageId !== "" ? response.nextPageId : null;
|
|
70
|
+
|
|
69
71
|
if (nextPageId) {
|
|
70
72
|
console.log(`Next page ID: ${nextPageId}`);
|
|
71
73
|
} else {
|
|
72
74
|
console.log('No more pages to fetch');
|
|
73
75
|
}
|
|
76
|
+
|
|
74
77
|
} catch (error) {
|
|
75
|
-
console.error(`Error fetching page ${
|
|
78
|
+
console.error(`Error fetching page ${pageCount}:`, error);
|
|
76
79
|
throw error;
|
|
77
80
|
}
|
|
78
|
-
|
|
81
|
+
|
|
79
82
|
// Safety check to prevent infinite loops
|
|
80
|
-
if (
|
|
83
|
+
if (pageCount >= MAX_PAGES) {
|
|
81
84
|
console.warn(`Reached maximum page limit of ${MAX_PAGES}. Stopping pagination.`);
|
|
82
85
|
break;
|
|
83
86
|
}
|
|
@@ -86,12 +89,12 @@ async function fetchPositionsFromSRAPI() {
|
|
|
86
89
|
console.log(`Finished fetching all pages. Total positions collected: ${allPositions.length}`);
|
|
87
90
|
|
|
88
91
|
// Return response in the same format as before, but with all positions
|
|
89
|
-
const result =
|
|
92
|
+
const result = {
|
|
90
93
|
totalFound: totalFound,
|
|
91
94
|
offset: 0,
|
|
92
95
|
limit: allPositions.length,
|
|
93
|
-
nextPageId:
|
|
94
|
-
content: allPositions
|
|
96
|
+
nextPageId: "", // Always empty since we've fetched everything
|
|
97
|
+
content: allPositions
|
|
95
98
|
};
|
|
96
99
|
|
|
97
100
|
const amountOfUniqueJobs = new Set(allPositions.map(job => job.id)).size;
|
|
@@ -108,7 +111,17 @@ async function fetchJobDescription(jobId) {
|
|
|
108
111
|
return await makeSmartRecruitersRequest(`/jobs/${jobId}`);
|
|
109
112
|
}
|
|
110
113
|
|
|
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
|
+
|
|
111
124
|
module.exports = {
|
|
112
|
-
|
|
113
|
-
|
|
125
|
+
fetchPositionsFromSRAPI,
|
|
126
|
+
fetchJobDescription,
|
|
114
127
|
};
|
package/backend/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
};
|
|
2
|
+
...require('./utils'),
|
|
3
|
+
...require('./queries'),
|
|
4
|
+
...require('./fetchPositionsFromSRAPI'),
|
|
5
|
+
...require('./consts'),
|
|
6
|
+
};
|
|
7
|
+
|
package/backend/queries.js
CHANGED
|
@@ -1,23 +1,19 @@
|
|
|
1
|
-
const { COLLECTIONS
|
|
2
|
-
const { items
|
|
1
|
+
const { COLLECTIONS } = require('./consts');
|
|
2
|
+
const { items } = require('@wix/data');
|
|
3
|
+
|
|
3
4
|
|
|
4
5
|
async function getAllPositions() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
.limit(QUERY_MAX_LIMIT)
|
|
8
|
-
.find()
|
|
9
|
-
.then(result => result.items);
|
|
6
|
+
return await items.query(COLLECTIONS.JOBS).find().items;
|
|
7
|
+
|
|
10
8
|
}
|
|
11
9
|
|
|
12
10
|
async function getPositionsByField(field, value) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.eq(field, value)
|
|
16
|
-
.find()
|
|
17
|
-
.then(result => result.items);
|
|
11
|
+
return await items.query(COLLECTIONS.JOBS).where(field, value).find().items;
|
|
12
|
+
|
|
18
13
|
}
|
|
19
14
|
|
|
15
|
+
|
|
20
16
|
module.exports = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
};
|
|
17
|
+
getAllPositions,
|
|
18
|
+
getPositionsByField,
|
|
19
|
+
};
|
package/backend/utils.js
CHANGED
|
@@ -1,28 +1,11 @@
|
|
|
1
1
|
async function chunkedBulkOperation({ items, chunkSize, processChunk }) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
async function delay(ms) {
|
|
9
|
-
await new Promise(resolve => setTimeout(resolve, ms));
|
|
10
|
-
}
|
|
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;
|
|
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);
|
|
20
5
|
}
|
|
21
|
-
}
|
|
22
6
|
}
|
|
23
7
|
|
|
8
|
+
|
|
24
9
|
module.exports = {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
processJobsForField,
|
|
28
|
-
};
|
|
10
|
+
chunkedBulkOperation,
|
|
11
|
+
};
|
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.37",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -18,9 +18,8 @@
|
|
|
18
18
|
"homepage": "https://github.com/psdevteamenterprise/sr-npm#readme",
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@hisense-staging/velo-npm": "1.7.249",
|
|
21
|
-
"@wix/data": "^1.0.211"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"prettier": "^3.6.2"
|
|
21
|
+
"@wix/data": "^1.0.211",
|
|
22
|
+
"@wix/essentials": "^0.1.24",
|
|
23
|
+
"@wix/secrets": "1.0.53"
|
|
25
24
|
}
|
|
26
25
|
}
|
package/.prettierrc
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"singleQuote": true,
|
|
3
|
-
"trailingComma": "es5",
|
|
4
|
-
"printWidth": 100,
|
|
5
|
-
"tabWidth": 2,
|
|
6
|
-
"semi": true,
|
|
7
|
-
"bracketSpacing": true,
|
|
8
|
-
"arrowParens": "avoid",
|
|
9
|
-
"endOfLine": "lf",
|
|
10
|
-
"quoteProps": "as-needed",
|
|
11
|
-
"jsxSingleQuote": false,
|
|
12
|
-
"jsxBracketSameLine": false,
|
|
13
|
-
"requirePragma": false,
|
|
14
|
-
"insertPragma": false,
|
|
15
|
-
"proseWrap": "preserve"
|
|
16
|
-
}
|