sr-npm 1.7.65 → 1.7.67
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 +35 -9
- package/backend/consts.js +125 -93
- package/backend/data.js +240 -288
- package/backend/fetchPositionsFromSRAPI.js +21 -22
- package/backend/index.js +5 -6
- package/backend/queries.js +14 -11
- package/backend/utils.js +64 -5
- package/index.js +4 -5
- package/package.json +6 -3
- package/pages/homePage.js +108 -0
- package/pages/index.js +5 -0
- package/pages/positionPage.js +35 -0
- package/public/filterUtils.js +36 -0
- package/public/index.js +5 -0
- package/public/utils.js +28 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { fetch } = require('wix-fetch');
|
|
2
2
|
const { items: wixData } = require('@wix/data');
|
|
3
3
|
const { COLLECTIONS } = require('./collectionConsts');
|
|
4
|
+
|
|
4
5
|
async function makeSmartRecruitersRequest(path,token) {
|
|
5
6
|
// const baseUrl = 'https://api.smartrecruiters.com'; // PROD
|
|
6
7
|
const baseUrl = 'https://aoxley54.wixstudio.com/external-template/_functions'; // TEST
|
|
@@ -34,7 +35,7 @@ async function fetchPositionsFromSRAPI() {
|
|
|
34
35
|
let allPositions = [];
|
|
35
36
|
let totalFound = 0;
|
|
36
37
|
let nextPageId = null; // Start with no page ID for the first request
|
|
37
|
-
let
|
|
38
|
+
let page = 0;
|
|
38
39
|
const MAX_PAGES = 30 // Safety limit to prevent infinite loops
|
|
39
40
|
const token = await getSmartTokenFromCMS();
|
|
40
41
|
|
|
@@ -42,45 +43,44 @@ async function fetchPositionsFromSRAPI() {
|
|
|
42
43
|
|
|
43
44
|
do {
|
|
44
45
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
page++;
|
|
47
|
+
|
|
47
48
|
// Build the API path - first request has no page parameter, subsequent use nextPageId
|
|
48
49
|
let apiPath = '/jobs?limit=50';
|
|
49
50
|
if (nextPageId) {
|
|
50
51
|
apiPath += `&nextPageId=${nextPageId}`;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
console.log(`Fetching page ${
|
|
54
|
+
console.log(`Fetching page ${page} with path: ${apiPath}`);
|
|
54
55
|
const response = await makeSmartRecruitersRequest(apiPath,token);
|
|
55
56
|
|
|
56
57
|
// Add positions from this page to our collection
|
|
57
58
|
if (response.content && Array.isArray(response.content)) {
|
|
58
59
|
allPositions = allPositions.concat(response.content);
|
|
59
|
-
console.log(`Page ${
|
|
60
|
+
console.log(`Page ${page}: Found ${response.content.length} positions`);
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
+
|
|
62
63
|
// Update total count from first response
|
|
63
|
-
if (
|
|
64
|
+
if (page === 1) {
|
|
64
65
|
totalFound = response.totalFound || 0;
|
|
65
66
|
console.log(`Total positions available: ${totalFound}`);
|
|
66
67
|
}
|
|
67
|
-
|
|
68
|
+
|
|
68
69
|
// Get the nextPageId for the next iteration
|
|
69
|
-
nextPageId = response.nextPageId && response.nextPageId !==
|
|
70
|
-
|
|
70
|
+
nextPageId = response.nextPageId && response.nextPageId !== '' ? response.nextPageId : null;
|
|
71
|
+
|
|
71
72
|
if (nextPageId) {
|
|
72
73
|
console.log(`Next page ID: ${nextPageId}`);
|
|
73
74
|
} else {
|
|
74
75
|
console.log('No more pages to fetch');
|
|
75
76
|
}
|
|
76
|
-
|
|
77
77
|
} catch (error) {
|
|
78
|
-
console.error(`Error fetching page ${
|
|
78
|
+
console.error(`Error fetching page ${page}:`, error);
|
|
79
79
|
throw error;
|
|
80
80
|
}
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
// Safety check to prevent infinite loops
|
|
83
|
-
if (
|
|
83
|
+
if (page >= MAX_PAGES) {
|
|
84
84
|
console.warn(`Reached maximum page limit of ${MAX_PAGES}. Stopping pagination.`);
|
|
85
85
|
break;
|
|
86
86
|
}
|
|
@@ -89,12 +89,12 @@ async function fetchPositionsFromSRAPI() {
|
|
|
89
89
|
console.log(`Finished fetching all pages. Total positions collected: ${allPositions.length}`);
|
|
90
90
|
|
|
91
91
|
// Return response in the same format as before, but with all positions
|
|
92
|
-
const result =
|
|
92
|
+
const result = {
|
|
93
93
|
totalFound: totalFound,
|
|
94
94
|
offset: 0,
|
|
95
95
|
limit: allPositions.length,
|
|
96
|
-
nextPageId:
|
|
97
|
-
content: allPositions
|
|
96
|
+
nextPageId: '', // Always empty since we've fetched everything
|
|
97
|
+
content: allPositions,
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
const amountOfUniqueJobs = new Set(allPositions.map(job => job.id)).size;
|
|
@@ -108,8 +108,7 @@ async function fetchPositionsFromSRAPI() {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
async function fetchJobDescription(jobId) {
|
|
111
|
-
|
|
112
|
-
return await makeSmartRecruitersRequest(`/jobs/${jobId}`,token);
|
|
111
|
+
return await makeSmartRecruitersRequest(`/jobs/${jobId}`);
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
async function getSmartTokenFromCMS() {
|
|
@@ -117,12 +116,12 @@ async function getSmartTokenFromCMS() {
|
|
|
117
116
|
if (result.items.length > 0) {
|
|
118
117
|
return result.items[0].token; // This is your string token
|
|
119
118
|
} else {
|
|
120
|
-
|
|
119
|
+
throw new Error('[getSmartTokenFromCMS], No token found');
|
|
121
120
|
}
|
|
122
121
|
}
|
|
123
122
|
|
|
124
123
|
|
|
125
124
|
module.exports = {
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
fetchPositionsFromSRAPI,
|
|
126
|
+
fetchJobDescription,
|
|
128
127
|
};
|
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,22 @@
|
|
|
1
|
-
const {
|
|
2
|
-
const { items } = require('@wix/data');
|
|
3
|
-
|
|
1
|
+
const { items: wixData } = require('@wix/data');
|
|
4
2
|
|
|
5
3
|
async function getAllPositions() {
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
return wixData
|
|
5
|
+
.query('Jobs1')
|
|
6
|
+
.limit(1000)
|
|
7
|
+
.find()
|
|
8
|
+
.then(result => result.items);
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
async function getPositionsByField(field, value) {
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
return wixData
|
|
13
|
+
.query('Jobs1')
|
|
14
|
+
.eq(field, value)
|
|
15
|
+
.find()
|
|
16
|
+
.then(result => result.items);
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
|
|
16
19
|
module.exports = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
20
|
+
getAllPositions,
|
|
21
|
+
getPositionsByField,
|
|
22
|
+
};
|
package/backend/utils.js
CHANGED
|
@@ -1,11 +1,70 @@
|
|
|
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 fillCityLocationAndLocationAddress(jobs, cityLocations,citylocationAddress) {
|
|
22
|
+
for (const job of jobs) {
|
|
23
|
+
if (!cityLocations[job.cityText] && !citylocationAddress[job.cityText]) {
|
|
24
|
+
cityLocations[job.cityText] = job.location;
|
|
25
|
+
citylocationAddress[job.cityText] = job.locationAddress;
|
|
5
26
|
}
|
|
27
|
+
}
|
|
6
28
|
}
|
|
7
29
|
|
|
30
|
+
function prepareToSaveArray(jobsPerField, cityLocations, field,citylocationAddress) {
|
|
31
|
+
if (field === 'cityText') {
|
|
32
|
+
return Object.entries(jobsPerField).map(([value, amount]) => {
|
|
33
|
+
const loc = cityLocations[value] || {};
|
|
34
|
+
const locAddress = citylocationAddress[value] || {};
|
|
35
|
+
value = normalizeCityName(value);
|
|
36
|
+
return {
|
|
37
|
+
title: value,
|
|
38
|
+
_id: value.replace(/\s+/g, ''),
|
|
39
|
+
count: amount,
|
|
40
|
+
locationAddress: locAddress,
|
|
41
|
+
country: loc.country,
|
|
42
|
+
city: loc.city,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
return Object.entries(jobsPerField).map(([value, amount]) => ({
|
|
47
|
+
title: value,
|
|
48
|
+
_id: value.replace(/\s+/g, ''),
|
|
49
|
+
count: amount,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeCityName(city) {
|
|
55
|
+
if (!city) return city;
|
|
56
|
+
// Remove accents/diacritics, trim whitespace
|
|
57
|
+
return city
|
|
58
|
+
.normalize('NFD')
|
|
59
|
+
.replace(/\p{Diacritic}/gu, '')
|
|
60
|
+
.trim();
|
|
61
|
+
}
|
|
8
62
|
|
|
9
63
|
module.exports = {
|
|
10
|
-
|
|
11
|
-
|
|
64
|
+
chunkedBulkOperation,
|
|
65
|
+
delay,
|
|
66
|
+
countJobsPerGivenField,
|
|
67
|
+
fillCityLocationAndLocationAddress,
|
|
68
|
+
prepareToSaveArray,
|
|
69
|
+
normalizeCityName,
|
|
70
|
+
};
|
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.67",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -17,9 +17,12 @@
|
|
|
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
21
|
"@wix/data": "^1.0.211",
|
|
22
22
|
"@wix/essentials": "^0.1.24",
|
|
23
|
-
"@wix/secrets": "1.0.53"
|
|
23
|
+
"@wix/secrets": "^1.0.53"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"prettier": "^3.6.2"
|
|
24
27
|
}
|
|
25
28
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const {
|
|
2
|
+
debounce,
|
|
3
|
+
getFilter,
|
|
4
|
+
} = require('../public/filterUtils');
|
|
5
|
+
const { location } = require('@wix/site-location');
|
|
6
|
+
|
|
7
|
+
async function homePageOnReady(_$w) {
|
|
8
|
+
console.log('homePageOnReady inisde SR-NPM');
|
|
9
|
+
await bind(_$w);
|
|
10
|
+
await init(_$w);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function bind(_$w) {
|
|
14
|
+
_$w('#teamRepeater').onItemReady(($item, itemData) => {
|
|
15
|
+
$item('#teamButton').label = `View ${itemData.count} Open Positions`;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
_$w('#citiesDataset').onReady(async () => {
|
|
19
|
+
const numOfItems = await _$w('#citiesDataset').getTotalCount();
|
|
20
|
+
const items = await _$w('#citiesDataset').getItems(0, numOfItems);
|
|
21
|
+
let baseUrl = await location.baseUrl();
|
|
22
|
+
const linkUrl = `${baseUrl}/positions`;
|
|
23
|
+
|
|
24
|
+
const markers = items.items.map(item => {
|
|
25
|
+
const location = item.locationAddress.location;
|
|
26
|
+
return {
|
|
27
|
+
location: {
|
|
28
|
+
latitude: location.latitude,
|
|
29
|
+
longitude: location.longitude
|
|
30
|
+
},
|
|
31
|
+
address: item.locationAddress.formatted,
|
|
32
|
+
description: `<a href=${linkUrl} target="_parent" rel="noopener noreferrer" style="color:#000000;text-decoration:underline;font-weight:bold;">View ${item.count} Open Positions</a>`
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
//@ts-ignore
|
|
36
|
+
_$w('#googleMaps').setMarkers(markers);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function init(_$w) {
|
|
41
|
+
const debouncedInput = debounce(handleSearchInput, 400);
|
|
42
|
+
|
|
43
|
+
_$w('#searchInput').onInput(debouncedInput);
|
|
44
|
+
_$w('#searchInput').maxLength = 40;
|
|
45
|
+
|
|
46
|
+
_$w('#searchInput').onKeyPress((event) => {
|
|
47
|
+
if (event.key === 'Enter') {
|
|
48
|
+
handleEnterPress(_$w('#searchInput').value);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
_$w('#searchInput').onFocus(() => {
|
|
53
|
+
if (_$w(`#resultsContainer`).collapsed) {
|
|
54
|
+
_$w(`#resultsContainer`).expand();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
_$w('#searchInput').onBlur(() => {
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
if (!_$w(`#resultsContainer`).collapsed) {
|
|
61
|
+
_$w(`#resultsContainer`).collapse();
|
|
62
|
+
}
|
|
63
|
+
}, 250);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
_$w('#locationItem').onClick((event)=>{
|
|
67
|
+
handleOnLocationClick(event, '#locationsRepeater', '#googleMaps');
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function handleSearchInput(_$w) {
|
|
72
|
+
let searchInput;
|
|
73
|
+
let count;
|
|
74
|
+
|
|
75
|
+
searchInput = _$w('#searchInput').value;
|
|
76
|
+
const trimmedInput = searchInput.trim();
|
|
77
|
+
|
|
78
|
+
const fieldsToSearch = [
|
|
79
|
+
{ field: 'title', searchTerm: trimmedInput },
|
|
80
|
+
{ field: 'cityText', searchTerm: trimmedInput }
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const filter = await getFilter(fieldsToSearch, 'or');
|
|
84
|
+
|
|
85
|
+
await _$w('#jobsDataset').setFilter(filter);
|
|
86
|
+
await _$w('#jobsDataset').refresh();
|
|
87
|
+
|
|
88
|
+
count = _$w('#jobsDataset').getTotalCount();
|
|
89
|
+
|
|
90
|
+
if (count > 0) {
|
|
91
|
+
_$w('#resultsContainer').expand();
|
|
92
|
+
_$w('#searchMultiStateBox').changeState('results');
|
|
93
|
+
} else {
|
|
94
|
+
_$w('#searchMultiStateBox').changeState('noResults');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function handleEnterPress(searchInput) {
|
|
99
|
+
const trimmedInput = searchInput.trim();
|
|
100
|
+
|
|
101
|
+
if (trimmedInput) {
|
|
102
|
+
location.to(`/positions?keyWord=${trimmedInput}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
homePageOnReady,
|
|
108
|
+
};
|
package/pages/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const {
|
|
2
|
+
htmlToText,
|
|
3
|
+
} = require('../public/utils');
|
|
4
|
+
|
|
5
|
+
async function positionPageOnReady(_$w) {
|
|
6
|
+
|
|
7
|
+
console.log('positionPageOnReady inisde SR-NPM');
|
|
8
|
+
await bind(_$w);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function bind(_$w) {
|
|
12
|
+
_$w('#datasetJobsItem').onReady(async () => {
|
|
13
|
+
|
|
14
|
+
const item = await _$w('#datasetJobsItem').getCurrentItem();
|
|
15
|
+
|
|
16
|
+
_$w('#companyDescriptionText').text = htmlToText(item.jobDescription.companyDescription.text);
|
|
17
|
+
_$w('#responsibilitiesText').text = htmlToText(item.jobDescription.jobDescription.text);
|
|
18
|
+
_$w('#qualificationsText').text = htmlToText(item.jobDescription.qualifications.text);
|
|
19
|
+
_$w('#relatedJobsTitleText').text = `More ${item.department} Positions`;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
_$w('#relatedJobsDataset').onReady(async () => {
|
|
23
|
+
const count = await _$w('#relatedJobsDataset').getTotalCount();
|
|
24
|
+
if(!count){
|
|
25
|
+
_$w('#relatedJobsSection').collapse();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
positionPageOnReady,
|
|
34
|
+
};
|
|
35
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const { items:wixData } = require('@wix/data');
|
|
2
|
+
|
|
3
|
+
export function getFilter(fieldsToSearch = [], mode = 'or') {
|
|
4
|
+
const baseFilter = wixData.filter();
|
|
5
|
+
// if no fields to search, return empty filter
|
|
6
|
+
if (fieldsToSearch.length === 0) {
|
|
7
|
+
return baseFilter;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// build filters
|
|
11
|
+
let filter;
|
|
12
|
+
fieldsToSearch.forEach(({ field, searchTerm }) => {
|
|
13
|
+
const condition = typeof searchTerm === 'boolean'
|
|
14
|
+
? baseFilter.eq(field, searchTerm)
|
|
15
|
+
: baseFilter.contains(field, searchTerm);
|
|
16
|
+
|
|
17
|
+
filter = filter
|
|
18
|
+
? (mode === 'or' ? filter.or(condition) : filter.and(condition))
|
|
19
|
+
: condition;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return filter;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function debounce(fn, delay = 400) {
|
|
26
|
+
let timeout;
|
|
27
|
+
return function (...args) {
|
|
28
|
+
clearTimeout(timeout);
|
|
29
|
+
timeout = setTimeout(() => fn.apply(this, args), delay);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
getFilter,
|
|
35
|
+
debounce
|
|
36
|
+
};
|
package/public/index.js
ADDED
package/public/utils.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
function htmlToText(html) {
|
|
4
|
+
if (!html) return '';
|
|
5
|
+
|
|
6
|
+
// Remove HTML tags and decode entities
|
|
7
|
+
let text = html
|
|
8
|
+
.replace(/<br\s*\/?>/gi, '\n')
|
|
9
|
+
.replace(/<\/?(p|div|h[1-6])\s*\/?>/gi, '\n')
|
|
10
|
+
.replace(/<li[^>]*>/gi, '• ')
|
|
11
|
+
.replace(/<\/li>/gi, '\n')
|
|
12
|
+
.replace(/<[^>]*>/g, '')
|
|
13
|
+
.replace(/&/g, '&')
|
|
14
|
+
.replace(/</g, '<')
|
|
15
|
+
.replace(/>/g, '>')
|
|
16
|
+
.replace(/"/g, '"')
|
|
17
|
+
.replace(/'/g, "'")
|
|
18
|
+
.replace(/ /g, ' ');
|
|
19
|
+
|
|
20
|
+
console.log(text);
|
|
21
|
+
// Clean up whitespace
|
|
22
|
+
return text.replace(/\n\s*\n+/g, '\n\n').replace(/[ \t]+\n/g, '\n').trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
htmlToText
|
|
28
|
+
};
|