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.
@@ -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 pageCount = 0;
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
- pageCount++;
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 ${pageCount} with path: ${apiPath}`);
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 ${pageCount}: Found ${response.content.length} positions`);
60
+ console.log(`Page ${page}: Found ${response.content.length} positions`);
60
61
  }
61
-
62
+
62
63
  // Update total count from first response
63
- if (pageCount === 1) {
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 !== "" ? response.nextPageId : null;
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 ${pageCount}:`, error);
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 (pageCount >= MAX_PAGES) {
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: "", // Always empty since we've fetched everything
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
- const token = await getSmartTokenFromCMS();
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
- return null; // No token found
119
+ throw new Error('[getSmartTokenFromCMS], No token found');
121
120
  }
122
121
  }
123
122
 
124
123
 
125
124
  module.exports = {
126
- fetchPositionsFromSRAPI,
127
- fetchJobDescription,
125
+ fetchPositionsFromSRAPI,
126
+ fetchJobDescription,
128
127
  };
package/backend/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  module.exports = {
2
- ...require('./utils'),
3
- ...require('./queries'),
4
- ...require('./fetchPositionsFromSRAPI'),
5
- ...require('./consts'),
6
- };
7
-
2
+ ...require('./utils'),
3
+ ...require('./queries'),
4
+ ...require('./fetchPositionsFromSRAPI'),
5
+ ...require('./consts'),
6
+ };
@@ -1,19 +1,22 @@
1
- const { COLLECTIONS } = require('./collectionConsts');
2
- const { items } = require('@wix/data');
3
-
1
+ const { items: wixData } = require('@wix/data');
4
2
 
5
3
  async function getAllPositions() {
6
- return await items.query(COLLECTIONS.JOBS).find().items;
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
- return await items.query(COLLECTIONS.JOBS).where(field, value).find().items;
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
- getAllPositions,
18
- getPositionsByField,
19
- };
20
+ getAllPositions,
21
+ getPositionsByField,
22
+ };
package/backend/utils.js CHANGED
@@ -1,11 +1,70 @@
1
1
  async function chunkedBulkOperation({ items, chunkSize, processChunk }) {
2
- for (let i = 0; i < items.length; i += chunkSize) {
3
- const chunk = items.slice(i, i + chunkSize);
4
- await processChunk(chunk, Math.floor(i / chunkSize) + 1);
2
+ for (let i = 0; i < items.length; i += chunkSize) {
3
+ const chunk = items.slice(i, i + chunkSize);
4
+ await processChunk(chunk, Math.floor(i / chunkSize) + 1);
5
+ }
6
+ }
7
+
8
+ async function delay(ms) {
9
+ await new Promise(resolve => setTimeout(resolve, ms));
10
+ }
11
+
12
+ function countJobsPerGivenField(jobs, field,jobsPerField) {
13
+ for (const job of jobs) {
14
+ if (!job[field]) {
15
+ throw new Error(`Job ${job._id} has no ${field} field`);
16
+ }
17
+ jobsPerField[job[field]] = (jobsPerField[job[field]] || 0) + 1;
18
+ }
19
+ }
20
+
21
+ function 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
- chunkedBulkOperation,
11
- };
64
+ chunkedBulkOperation,
65
+ delay,
66
+ countJobsPerGivenField,
67
+ fillCityLocationAndLocationAddress,
68
+ prepareToSaveArray,
69
+ normalizeCityName,
70
+ };
package/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  module.exports = {
2
- ...require('./public'),
3
- ...require('./pages'),
4
- ...require('./backend'),
5
- };
6
-
2
+ ...require('./public'),
3
+ ...require('./pages'),
4
+ ...require('./backend'),
5
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sr-npm",
3
- "version": "1.7.65",
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": "1.7.251",
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,5 @@
1
+ module.exports = {
2
+ ...require('./positionPage'),
3
+ ...require('./homePage'),
4
+ };
5
+
@@ -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
+ };
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ ...require('./utils'),
3
+ ...require('./filterUtils'),
4
+ };
5
+
@@ -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(/&amp;/g, '&')
14
+ .replace(/&lt;/g, '<')
15
+ .replace(/&gt;/g, '>')
16
+ .replace(/&quot;/g, '"')
17
+ .replace(/&#39;/g, "'")
18
+ .replace(/&nbsp;/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
+ };