Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 83771ba

Browse files
Proxy EMSI skills search endpoint
1 parent 23c73ab commit 83771ba

File tree

10 files changed

+214
-12
lines changed

10 files changed

+214
-12
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,9 @@ Configuration for the application is at `config/default.js` and `config/producti
4848
- TEMPLATE_S3_BUCKET: the template s3 bucket name, default value is 'ubahn'
4949
- UPLOAD_S3_BUCKET: the upload s3 bucket name, default value is 'ubahn'
5050
- S3_OBJECT_URL_EXPIRY_TIME: the s3 url expiry time, default value is '1 hour'
51+
- EMSI_CLIENT_ID: emsi oAuth 2.0 client id, used to get emis oAuth 2.0 token
52+
- EMSI_CLIENT_SECRET: emsi oAuth 2.0 client secret, used to get emsi oAuth 2.0 token
53+
- EMSI_GRANT_TYPE: emsi oAuth 2.0 grant_type, used to get emsi oAuth 2.0 token, should always be the string 'client_credentials'
54+
- EMSI_SCOPE: emsi oAuth 2.0 scope, used to get emsi oAuth 2.0 token, default value is 'emsi_open'
55+
- EMSI_AUTH_URL: emsi oAuth 2.0 auth url, used to get emsi oAuth 2.0 token, default value is 'https://auth.emsicloud.com/connect/token'
56+
- EMSI_BASE_URL: emsi base url, used to get emsi skills, default value is 'https://skills.emsicloud.com/versions/latest'

app-constants.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ const Scopes = {
1515
AllUpload: 'all:upload',
1616
CreateTemplate: 'create:template',
1717
GetTemplate: 'get:template',
18-
AllTemplate: 'all:template'
18+
AllTemplate: 'all:template',
19+
GetSkill: 'get:skill',
20+
AllSkill: 'all:skill'
1921
}
2022

2123
const AllAuthenticatedUsers = [UserRoles.admin, UserRoles.administrator, UserRoles.topcoderUser, UserRoles.copilot]

config/default.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,14 @@ module.exports = {
4242
TEMPLATE_FILE_MIMETYPE: process.env.TEMPLATE_FILE_MIMETYPE || 'application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
4343
TEMPLATE_S3_BUCKET: process.env.TEMPLATE_S3_BUCKET || 'ubahn',
4444
UPLOAD_S3_BUCKET: process.env.UPLOAD_S3_BUCKET || 'ubahn',
45-
S3_OBJECT_URL_EXPIRY_TIME: process.env.S3_OBJECT_URL_EXPIRY_TIME || 60 * 60
45+
S3_OBJECT_URL_EXPIRY_TIME: process.env.S3_OBJECT_URL_EXPIRY_TIME || 60 * 60,
46+
47+
EMSI: {
48+
CLIENT_ID: process.env.EMSI_CLIENT_ID,
49+
CLIENT_SECRET: process.env.EMSI_CLIENT_SECRET,
50+
GRANT_TYPE: process.env.EMSI_GRANT_TYPE || 'client_credentials',
51+
SCOPE: process.env.EMSI_SCOPE || 'emsi_open',
52+
AUTH_URL: process.env.EMSI_AUTH_URL || 'https://auth.emsicloud.com/connect/token',
53+
BASE_URL: process.env.EMSI_BASE_URL || 'https://skills.emsicloud.com/versions/latest'
54+
}
4655
}

docs/swagger.yaml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ tags:
1212
description: "Template files to serve as reference for multitude of tasks"
1313
- name: "uploads"
1414
description: "Upload files for batch processing"
15+
- name: "skills"
16+
description: "Skills endpoints"
17+
externalDocs:
18+
description: "Proxy to EMSI skills"
19+
url: "https://api.emsidata.com/apis/skills-classification#get-search-for-skills"
1520
schemes:
1621
- "https"
1722
- "http"
@@ -185,6 +190,37 @@ paths:
185190
security:
186191
- api_key: []
187192
x-swagger-router-controller: "Uploads"
193+
/skills:
194+
get:
195+
tags:
196+
- "skills"
197+
summary: "Search for skills by name using the q query parameter"
198+
description: "Responds with the skills search result. Proxy api to EMSI"
199+
operationId: "getSkills"
200+
produces:
201+
- "application/json"
202+
parameters:
203+
- name: "q"
204+
in: "query"
205+
description: "The skill to search for. All special characters must be URL encoded, eg. \"C++ Progr\" should be encoded \"C%2B%2B%20Progr\""
206+
required: false
207+
type: "string"
208+
responses:
209+
"200":
210+
description: "Success"
211+
schema:
212+
$ref: "#/definitions/Skill"
213+
"401":
214+
$ref: "#/definitions/Unauthorized"
215+
"403":
216+
$ref: "#/definitions/Forbidden"
217+
"404":
218+
$ref: "#/definitions/NotFound"
219+
"500":
220+
$ref: "#/definitions/ServerError"
221+
security:
222+
- api_key: []
223+
x-swagger-router-controller: "Skills"
188224
securityDefinitions:
189225
api_key:
190226
type: "apiKey"
@@ -228,6 +264,26 @@ definitions:
228264
description: "If status is `failed`, then this field will contain information\
229265
\ on why the processing failed"
230266
- $ref: "#/definitions/AuditFields"
267+
Skill:
268+
allOf:
269+
- type: "object"
270+
properties:
271+
skills:
272+
type: "array"
273+
items:
274+
properties:
275+
type:
276+
type: string
277+
description: The skill type
278+
example: 'Hard Skill'
279+
id:
280+
type: string
281+
description: The skill id
282+
example: 'KS1200364C9C1LK3V5Q1'
283+
name:
284+
type: string
285+
description: The skill name
286+
example: 'C (Programming Language)'
231287
AuditFields:
232288
type: "object"
233289
required:

package-lock.json

Lines changed: 50 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"dependencies": {
1818
"@hapi/joi": "^16.1.8",
1919
"aws-sdk": "^2.627.0",
20+
"axios": "^0.19.2",
2021
"bluebird": "^3.5.1",
2122
"body-parser": "^1.19.0",
2223
"config": "^3.2.4",
@@ -29,6 +30,7 @@
2930
"js-yaml": "^3.14.0",
3031
"lodash": "^4.17.15",
3132
"multer": "^1.4.2",
33+
"node-cache": "^5.1.0",
3234
"swagger-ui-express": "^4.1.4",
3335
"tc-bus-api-wrapper": "topcoder-platform/tc-bus-api-wrapper.git#feature/auth0-proxy-server",
3436
"tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.4",

src/common/helper.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ const _ = require('lodash')
55
const config = require('config')
66
const AWS = require('aws-sdk')
77
const path = require('path')
8+
const axios = require('axios')
9+
const querystring = require('querystring')
10+
const NodeCache = require('node-cache')
811
const models = require('../models')
912
const errors = require('./errors')
1013
const logger = require('./logger')
@@ -284,6 +287,40 @@ async function postEvent (topic, payload) {
284287
await busApiClient.postEvent(message)
285288
}
286289

290+
// cache the emsi token
291+
const tokenCache = new NodeCache()
292+
293+
/**
294+
* Get emsi token
295+
* @returns {string} the emsi token
296+
*/
297+
async function getEmsiToken () {
298+
let token = tokenCache.get('emsi_token')
299+
if (!token) {
300+
const res = await axios.post(config.EMSI.AUTH_URL, querystring.stringify({
301+
client_id: config.EMSI.CLIENT_ID,
302+
client_secret: config.EMSI.CLIENT_SECRET,
303+
grant_type: config.EMSI.GRANT_TYPE,
304+
scope: config.EMSI.SCOPE
305+
}))
306+
token = res.data.access_token
307+
tokenCache.set('emsi_token', token, res.data.expires_in)
308+
}
309+
return token
310+
}
311+
312+
/**
313+
* Get data from emsi
314+
* @param {String} path the emsi endpoint path
315+
* @param {String} params get params
316+
* @returns {Object} response data
317+
*/
318+
async function getEmsiObject (path, params) {
319+
const token = await getEmsiToken()
320+
const res = await axios.get(`${config.EMSI.BASE_URL}${path}`, { params, headers: { authorization: `Bearer ${token}` } })
321+
return res.data
322+
}
323+
287324
module.exports = {
288325
wrapExpress,
289326
autoWrapExpress,
@@ -296,5 +333,6 @@ module.exports = {
296333
generateS3Url,
297334
scan,
298335
validateDuplicate,
299-
postEvent
336+
postEvent,
337+
getEmsiObject
300338
}

src/controllers/SkillController.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Controller for Skill endpoints
3+
*/
4+
const service = require('../services/SkillService')
5+
6+
/**
7+
* Get skills
8+
* @param {Object} req the request
9+
* @param {Object} res the response
10+
*/
11+
async function getEntity (req, res) {
12+
const result = await service.getEntity(req.query.q)
13+
res.send(result)
14+
}
15+
16+
module.exports = {
17+
getEntity
18+
}

src/routes.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ module.exports = {
6666
scopes: [constants.Scopes.GetTemplate, constants.Scopes.AllTemplate]
6767
}
6868
},
69+
'/skills': {
70+
get: {
71+
controller: 'SkillController',
72+
method: 'getEntity',
73+
auth: 'jwt',
74+
access: constants.AllAuthenticatedUsers,
75+
scopes: [constants.Scopes.GetSkill, constants.Scopes.AllSkill]
76+
}
77+
},
6978
'/health': {
7079
get: {
7180
controller: 'HealthCheckController',

src/services/SkillService.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* This service provides operations of skills.
3+
*/
4+
const helper = require('../common/helper')
5+
const logger = require('../common/logger')
6+
7+
/**
8+
* Get skills by query param q.
9+
* @param {String} q the query param
10+
* @returns {Object} the Object with skills
11+
*/
12+
async function getEntity (q) {
13+
const res = await helper.getEmsiObject('/skills', { q })
14+
return res
15+
}
16+
17+
module.exports = {
18+
getEntity
19+
}
20+
21+
logger.buildService(module.exports)

0 commit comments

Comments
 (0)