sr-npm 1.0.11 → 1.0.13
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/collectionConsts.js +76 -0
- package/backend/consts.js +55 -110
- package/backend/data.js +354 -264
- package/backend/fetchPositionsFromSRAPI.js +47 -40
- package/backend/index.js +5 -4
- package/backend/queries.js +15 -14
- package/backend/secretsData.js +14 -0
- package/backend/utils.js +81 -4
- package/index.js +4 -5
- package/package.json +14 -3
- package/pages/careersPage.js +457 -0
- package/pages/homePage.js +136 -0
- package/pages/index.js +6 -0
- package/pages/positionPage.js +34 -0
- package/public/filterUtils.js +39 -0
- package/public/index.js +6 -0
- package/public/mapUtils.js +16 -0
- package/public/utils.js +45 -0
- package/tests/fetchPositionsFromSRApiTest.spec.js +35 -0
- package/tests/testsUtils.js +7 -0
- package/.wix/debug.log +0 -111
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const { fetch } = require('wix-fetch');
|
|
2
|
+
const { items: wixData } = require('@wix/data');
|
|
3
|
+
const { COLLECTIONS } = require('./collectionConsts');
|
|
4
|
+
const secretsData = require('./secretsData');
|
|
3
5
|
async function makeSmartRecruitersRequest(path) {
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
//console.log(`Making request to: ${fullUrl}`);
|
|
6
|
+
const baseUrl = 'https://api.smartrecruiters.com';
|
|
7
|
+
const fullUrl = `${baseUrl}${path}`;
|
|
8
|
+
|
|
8
9
|
try {
|
|
9
10
|
const response = await fetch(fullUrl, {
|
|
10
11
|
method: 'GET',
|
|
11
12
|
headers: {
|
|
12
13
|
'Accept-Language': 'en',
|
|
13
14
|
'accept': 'application/json',
|
|
14
|
-
'x-smarttoken': 'DCRA1-1d30ea5fe9be42d9b9ae94ff933ebef5',
|
|
15
15
|
'Cookie': 'AWSALB=GYltFw3fLKortMxHR5vIOT1CuUROyhWNIX/qL8ZnPl1/8mhOcnIsBKYslzmNJPEzSy/jvNbO+6tXpH8yqcpQJagYt57MhbKlLqTSzoNq1G/w7TjOxPGR3UTdXW0d; AWSALBCORS=GYltFw3fLKortMxHR5vIOT1CuUROyhWNIX/qL8ZnPl1/8mhOcnIsBKYslzmNJPEzSy/jvNbO+6tXpH8yqcpQJagYt57MhbKlLqTSzoNq1G/w7TjOxPGR3UTdXW0d'
|
|
16
16
|
}
|
|
17
17
|
});
|
|
@@ -28,70 +28,60 @@ async function makeSmartRecruitersRequest(path) {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
async function fetchPositionsFromSRAPI() {
|
|
32
32
|
let allPositions = [];
|
|
33
33
|
let totalFound = 0;
|
|
34
|
-
let
|
|
35
|
-
let pageCount = 0;
|
|
34
|
+
let page = 0;
|
|
36
35
|
const MAX_PAGES = 30 // Safety limit to prevent infinite loops
|
|
37
|
-
|
|
36
|
+
const companyId = await getCompanyIdFromCMS();
|
|
38
37
|
console.log('Starting to fetch all positions with pagination...');
|
|
38
|
+
let offset=0;
|
|
39
39
|
|
|
40
40
|
do {
|
|
41
41
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
page++;
|
|
43
|
+
|
|
44
44
|
// Build the API path - first request has no page parameter, subsequent use nextPageId
|
|
45
|
-
|
|
46
|
-
if (nextPageId) {
|
|
47
|
-
apiPath += `&nextPageId=${nextPageId}`;
|
|
48
|
-
}
|
|
45
|
+
const apiPath = `/v1/companies/${companyId}/postings?offset=${offset}`;
|
|
49
46
|
|
|
50
|
-
console.log(`Fetching page ${
|
|
47
|
+
console.log(`Fetching page ${page} with path: ${apiPath}`);
|
|
51
48
|
const response = await makeSmartRecruitersRequest(apiPath);
|
|
52
49
|
|
|
53
50
|
// Add positions from this page to our collection
|
|
54
51
|
if (response.content && Array.isArray(response.content)) {
|
|
55
52
|
allPositions = allPositions.concat(response.content);
|
|
56
|
-
console.log(`Page ${
|
|
53
|
+
console.log(`Page ${page}: Found ${response.content.length} positions`);
|
|
57
54
|
}
|
|
58
|
-
|
|
55
|
+
|
|
59
56
|
// Update total count from first response
|
|
60
|
-
if (
|
|
57
|
+
if (page === 1) {
|
|
61
58
|
totalFound = response.totalFound || 0;
|
|
62
59
|
console.log(`Total positions available: ${totalFound}`);
|
|
63
60
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (nextPageId) {
|
|
69
|
-
console.log(`Next page ID: ${nextPageId}`);
|
|
70
|
-
} else {
|
|
71
|
-
console.log('No more pages to fetch');
|
|
72
|
-
}
|
|
73
|
-
|
|
61
|
+
|
|
62
|
+
offset+=100;
|
|
63
|
+
|
|
74
64
|
} catch (error) {
|
|
75
|
-
console.error(`Error fetching page ${
|
|
65
|
+
console.error(`Error fetching page ${page}:`, error);
|
|
76
66
|
throw error;
|
|
77
67
|
}
|
|
78
|
-
|
|
68
|
+
|
|
79
69
|
// Safety check to prevent infinite loops
|
|
80
|
-
if (
|
|
70
|
+
if (page >= MAX_PAGES) {
|
|
81
71
|
console.warn(`Reached maximum page limit of ${MAX_PAGES}. Stopping pagination.`);
|
|
82
72
|
break;
|
|
83
73
|
}
|
|
84
|
-
} while (
|
|
74
|
+
} while (offset<totalFound); // Continue while there's a nextPageId
|
|
85
75
|
|
|
86
76
|
console.log(`Finished fetching all pages. Total positions collected: ${allPositions.length}`);
|
|
87
77
|
|
|
88
78
|
// Return response in the same format as before, but with all positions
|
|
89
|
-
const result =
|
|
79
|
+
const result = {
|
|
90
80
|
totalFound: totalFound,
|
|
91
81
|
offset: 0,
|
|
92
82
|
limit: allPositions.length,
|
|
93
|
-
nextPageId:
|
|
94
|
-
content: allPositions
|
|
83
|
+
nextPageId: '', // Always empty since we've fetched everything
|
|
84
|
+
content: allPositions,
|
|
95
85
|
};
|
|
96
86
|
|
|
97
87
|
const amountOfUniqueJobs = new Set(allPositions.map(job => job.id)).size;
|
|
@@ -104,6 +94,23 @@ export async function fetchPositionsFromSRAPI() {
|
|
|
104
94
|
return result;
|
|
105
95
|
}
|
|
106
96
|
|
|
107
|
-
|
|
108
|
-
|
|
97
|
+
async function fetchJobDescription(jobId) {
|
|
98
|
+
const companyId = await getCompanyIdFromCMS();
|
|
99
|
+
return await makeSmartRecruitersRequest(`/v1/companies/${companyId}/postings/${jobId}`);
|
|
109
100
|
}
|
|
101
|
+
|
|
102
|
+
async function getCompanyIdFromCMS() {
|
|
103
|
+
const result = await wixData.query(COLLECTIONS.COMPANY_ID).limit(1).find();
|
|
104
|
+
if (result.items.length > 0) {
|
|
105
|
+
return result.items[0].companyId;
|
|
106
|
+
} else {
|
|
107
|
+
throw new Error('[getCompanyIdFromCMS], No companyId found');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
fetchPositionsFromSRAPI,
|
|
114
|
+
fetchJobDescription,
|
|
115
|
+
getCompanyIdFromCMS
|
|
116
|
+
};
|
package/backend/index.js
CHANGED
package/backend/queries.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
const { COLLECTIONS } = require('./
|
|
2
|
-
const { items } = require('@wix/data');
|
|
1
|
+
const { COLLECTIONS } = require('./collectionConsts');
|
|
2
|
+
const { items: wixData } = require('@wix/data');
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
async function getAllPositions() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
return wixData
|
|
7
|
+
.query(COLLECTIONS.JOBS)
|
|
8
|
+
.limit(1000)
|
|
9
|
+
.find()
|
|
10
|
+
.then(result => result.items);
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
async function getPositionsByField(field, value) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
return wixData
|
|
15
|
+
.query(COLLECTIONS.JOBS)
|
|
16
|
+
.eq(field, value)
|
|
17
|
+
.find()
|
|
18
|
+
.then(result => result.items);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
20
21
|
module.exports = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
};
|
|
22
|
+
getAllPositions,
|
|
23
|
+
getPositionsByField,
|
|
24
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { secrets } = require("@wix/secrets");
|
|
2
|
+
const { auth } = require('@wix/essentials');
|
|
3
|
+
|
|
4
|
+
const getSecretValue = auth.elevate(secrets.getSecretValue);
|
|
5
|
+
|
|
6
|
+
function getCompanyId() {
|
|
7
|
+
return getSecretValue("companyID")
|
|
8
|
+
.then((secret) => {
|
|
9
|
+
return secret;
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
module.exports = {
|
|
13
|
+
getCompanyId
|
|
14
|
+
};
|
package/backend/utils.js
CHANGED
|
@@ -1,6 +1,83 @@
|
|
|
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
|
+
console.warn(`Job ${job._id} missing required field '${field}' - continue`);
|
|
16
|
+
continue;
|
|
5
17
|
}
|
|
6
|
-
|
|
18
|
+
else{
|
|
19
|
+
jobsPerField[job[field]] = (jobsPerField[job[field]] || 0) + 1;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function fillCityLocationAndLocationAddress(jobs, cityLocations,citylocationAddress) {
|
|
25
|
+
for (const job of jobs) {
|
|
26
|
+
if (!cityLocations[job.cityText] && !citylocationAddress[job.cityText]) {
|
|
27
|
+
cityLocations[job.cityText] = job.location;
|
|
28
|
+
citylocationAddress[job.cityText] = job.locationAddress;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function prepareToSaveArray(jobsPerField, cityLocations, field,citylocationAddress) {
|
|
34
|
+
if (field === 'cityText') {
|
|
35
|
+
return Object.entries(jobsPerField).map(([value, amount]) => {
|
|
36
|
+
const loc = cityLocations[value] || {};
|
|
37
|
+
const locAddress = citylocationAddress[value] || {};
|
|
38
|
+
value = normalizeCityName(value);
|
|
39
|
+
return {
|
|
40
|
+
title: value,
|
|
41
|
+
_id: value.replace(/\s+/g, ''),
|
|
42
|
+
count: amount,
|
|
43
|
+
locationAddress: locAddress,
|
|
44
|
+
country: loc.country,
|
|
45
|
+
city: loc.city,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
} else {
|
|
49
|
+
return Object.entries(jobsPerField).map(([value, amount]) => ({
|
|
50
|
+
title: value,
|
|
51
|
+
_id: sanitizeId(value),
|
|
52
|
+
count: amount,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeCityName(city) {
|
|
58
|
+
if (!city) return city;
|
|
59
|
+
// Remove accents/diacritics, trim whitespace
|
|
60
|
+
return city
|
|
61
|
+
.normalize('NFD')
|
|
62
|
+
.replace(/\p{Diacritic}/gu, '')
|
|
63
|
+
.trim();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function sanitizeId(input) {
|
|
67
|
+
if (!input) return '';
|
|
68
|
+
const withoutDiacritics = String(input)
|
|
69
|
+
.normalize('NFD')
|
|
70
|
+
.replace(/\p{Diacritic}/gu, '');
|
|
71
|
+
const withAnd = withoutDiacritics.replace(/&/g, 'and');
|
|
72
|
+
return withAnd.replace(/[^A-Za-z0-9-]/g, '');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
chunkedBulkOperation,
|
|
77
|
+
delay,
|
|
78
|
+
countJobsPerGivenField,
|
|
79
|
+
fillCityLocationAndLocationAddress,
|
|
80
|
+
prepareToSaveArray,
|
|
81
|
+
normalizeCityName,
|
|
82
|
+
sanitizeId,
|
|
83
|
+
};
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sr-npm",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
7
|
+
"test": "jest"
|
|
8
8
|
},
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -17,6 +17,17 @@
|
|
|
17
17
|
},
|
|
18
18
|
"homepage": "https://github.com/psdevteamenterprise/sr-npm#readme",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@hisense-staging/velo-npm": "
|
|
20
|
+
"@hisense-staging/velo-npm": "3.0.19",
|
|
21
|
+
"@wix/data": "^1.0.211",
|
|
22
|
+
"@wix/essentials": "^0.1.24",
|
|
23
|
+
"@wix/secrets": "^1.0.53",
|
|
24
|
+
"@wix/site-location": "^1.0.0",
|
|
25
|
+
"@wix/site-window": "^1.0.0",
|
|
26
|
+
"axios": "^1.11.0",
|
|
27
|
+
"jest": "^30.0.5",
|
|
28
|
+
"tests-utils": "^1.0.7"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"prettier": "^3.6.2"
|
|
21
32
|
}
|
|
22
33
|
}
|