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

Commit 6449d60

Browse files
author
Vikas Agarwal
committed
Initial code with permission model changes incorporated
1 parent 14b850e commit 6449d60

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+10974
-2
lines changed

README.md

100644100755
Lines changed: 157 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,157 @@
1-
# skills-api
2-
V5 Skills API
1+
# Skills API
2+
3+
* [Prerequisites](#prerequisites)
4+
* [Configuration](#configuration)
5+
* [Local deployment](#local-deployment)
6+
* [Migrations](#migrations)
7+
* [Local Deployment with Docker](#local-deployment-with-docker)
8+
* [NPM Commands](#npm-commands)
9+
* [JWT Authentication](#jwt-authentication)
10+
* [Documentation](#documentation)
11+
12+
## Prerequisites
13+
14+
- node 12.x+
15+
- npm 6.x+
16+
- docker
17+
- elasticsearch 7.7+
18+
- PostgreSQL
19+
20+
## Configuration
21+
22+
Configuration for the application is at `config/default.js` and `config/production.js`. The following parameters can be set in config files or in env variables:
23+
24+
- LOG_LEVEL: the log level
25+
- PORT: the server port
26+
- AUTH_SECRET: TC Authentication secret
27+
- VALID_ISSUERS: valid issuers for TC authentication
28+
- PAGE_SIZE: the default pagination limit
29+
- MAX_PAGE_SIZE: the maximum pagination size
30+
- API_VERSION: the API version
31+
- DB_NAME: the database name
32+
- DB_USERNAME: the database username
33+
- DB_PASSWORD: the database password
34+
- DB_HOST: the database host
35+
- DB_PORT: the database port
36+
- ES_HOST: Elasticsearch host
37+
- ES_REFRESH: Should elastic search refresh. Default is 'true'. Values can be 'true', 'wait_for', 'false'
38+
- ELASTICCLOUD_ID: The elastic cloud id, if your elasticsearch instance is hosted on elastic cloud. DO NOT provide a value for ES_HOST if you are using this
39+
- ELASTICCLOUD_USERNAME: The elastic cloud username for basic authentication. Provide this only if your elasticsearch instance is hosted on elastic cloud
40+
- ELASTICCLOUD_PASSWORD: The elastic cloud password for basic authentication. Provide this only if your elasticsearch instance is hosted on elastic cloud
41+
- ES.DOCUMENTS: Elasticsearch index, type and id mapping for resources.
42+
- SKILL_INDEX: The Elastic search index for skill. Default is `skill`
43+
- SKILL_ENRICH_POLICYNAME: The enrich policy for skill. Default is `skill-policy`
44+
- TAXONOMY_INDEX: The Elastic search index for taxonomy. Default is `taxonomy`
45+
- TAXONOMY_PIPELINE_ID: The pipeline id for enrichment with taxonomy. Default is `taxonomy-pipeline`
46+
- TAXONOMY_ENRICH_POLICYNAME: The enrich policy for taxonomy. Default is `taxonomy-policy`
47+
- MAX_BATCH_SIZE: Restrict number of records in memory during bulk insert (Used by the db to es migration script)
48+
- MAX_BULK_SIZE: The Bulk Indexing Maximum Limits. Default is `100` (Used by the db to es migration script)
49+
50+
51+
## Local deployment
52+
53+
Setup your Postgresql DB and Elasticsearch instance and ensure that they are up and running.
54+
55+
- Follow *Configuration* section to update config values, like database, ES host etc ..
56+
- Goto *skills-api*, run `npm i`
57+
- Create database using `npm run create-db`.
58+
- Run the migrations - `npm run migrations up`. This will create the tables.
59+
- Then run `npm run insert-data` and insert mock data into the database.
60+
- Run `npm run migrate-db-to-es` to sync data with ES.
61+
- Startup server `npm run start`
62+
63+
## Migrations
64+
65+
Migrations are located under the `./scripts/db/` folder. Run `npm run migrations up` and `npm run migrations down` to execute the migrations or remove the earlier ones
66+
67+
## Local Deployment with Docker
68+
69+
- Navigate to the directory `docker-pgsql-es` folder. Rename `sample.env` to `.env` and change any values if required.
70+
- Run `docker-compose up -d` to have docker instances of pgsql and elasticsearch to use with the api
71+
72+
- Create database using `npm run create-db`.
73+
- Run the migrations - `npm run migrations up`. This will create the tables.
74+
- Then run `npm run insert-data` and insert mock data into the database.
75+
- Run `npm run migrate-db-to-es` to sync data with ES.
76+
77+
- Navigate to the directory `docker`
78+
79+
- Rename the file `sample.env` to `.env`
80+
81+
- Set the required DB configurations and ElasticSearch host in the file `.env`
82+
83+
- Once that is done, run the following command
84+
85+
```bash
86+
docker-compose up
87+
```
88+
89+
- When you are running the application for the first time, It will take some time initially to download the image and install the dependencies
90+
91+
## NPM Commands
92+
93+
| Command                    | Description |
94+
|--------------------|--|
95+
| `npm run start` | Start app |
96+
| `npm run start:dev` | Start app on any changes (useful during development). |
97+
| `npm run lint` | Check for for lint errors. |
98+
| `npm run lint:fix` | Check for for lint errors and fix error automatically when possible. |
99+
| `npm run create-db` | Create the database |
100+
| `npm run insert-data` | Insert data into the database |
101+
| `npm run migrate-db-to-es` | Migrate data into elastic search from database |
102+
| `npm run delete-data` | Delete the data from the database |
103+
| `npm run migrations up` | Run up migration |
104+
| `npm run migrations down` | Run down migration |
105+
| `npm run generate:doc:permissions` | Generate [permissions.html](docs/permissions.html) |
106+
| `npm run generate:doc:permissions:dev` | Generate [permissions.html](docs/permissions.html) on any changes (useful during development). |
107+
108+
## JWT Authentication
109+
Authentication is handled via Authorization (Bearer) token header field. Token is a JWT token.
110+
111+
Here is a sample user token that is valid for a very long time for a user with administrator role.
112+
113+
```
114+
<provide_in_forums>
115+
116+
# here is the payload data decoded from the token
117+
{
118+
"roles": [
119+
"Topcoder User",
120+
"administrator"
121+
],
122+
"iss": "https://api.topcoder.com",
123+
"handle": "tc-Admin",
124+
"exp": 1685571460,
125+
"userId": "23166768",
126+
"iat": 1585570860,
127+
"email": "[email protected]",
128+
"jti": "0f1ef1d3-2b33-4900-bb43-48f2285f9630"
129+
}
130+
```
131+
132+
and this is a sample M2M token with scopes `all:connect_project`, `all:projects` and `write:projects`.
133+
134+
```
135+
<provided_in_forums>
136+
137+
# here is the payload data decoded from the token
138+
{
139+
"iss": "https://topcoder-dev.auth0.com/",
140+
"sub": "enjw1810eDz3XTwSO2Rn2Y9cQTrspn3B@clients",
141+
"aud": "https://m2m.topcoder-dev.com/",
142+
"iat": 1550906388,
143+
"exp": 2147483648,
144+
"azp": "enjw1810eDz3XTwSO2Rn2Y9cQTrspn3B",
145+
"scope": "all:connect_project all:projects write:projects",
146+
"gty": "client-credentials"
147+
}
148+
```
149+
150+
These tokens have been signed with the secret `CLIENT_SECRET`. This secret should match the `AUTH_SECRET` entry in `config/default.js`. You can modify the payload of these tokens to generate tokens with different roles or different scopes using https://jwt.io
151+
152+
**Note** Please check with `src/constants.js` for all available user roles and M2M scopes.
153+
154+
## Documentation
155+
156+
- [permissions.html](docs/permissions.html) - the list of all permissions in Skills API.
157+
- [swagger.yaml](docs/swagger.yaml) - the Swagger API Definition.

app.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* The application entry point
3+
*/
4+
5+
require('./src/bootstrap')
6+
const config = require('config')
7+
const express = require('express')
8+
const cross = require('cors')
9+
const bodyParser = require('body-parser')
10+
const _ = require('lodash')
11+
const http = require('http')
12+
const swaggerUi = require('swagger-ui-express')
13+
const jsyaml = require('js-yaml')
14+
const fs = require('fs')
15+
const path = require('path')
16+
const logger = require('./src/common/logger')
17+
const errorMiddleware = require('./src/common/error.middleware')
18+
const routes = require('./src/route')
19+
const { permissions, jwtAuthenticator } = require('tc-core-library-js').middleware
20+
const app = express()
21+
const httpServer = http.Server(app)
22+
const models = require('./src/models')
23+
const initPermissions = require('./src/permissions')
24+
25+
app.set('port', config.PORT)
26+
app.use(bodyParser.json())
27+
app.use(bodyParser.urlencoded({ extended: true }))
28+
app.use(cross())
29+
const apiRouter = express.Router({})
30+
31+
// load all routes
32+
_.each(routes, (verbs, url) => {
33+
_.each(verbs, (def, verb) => {
34+
if (!def.method) {
35+
throw new Error(`${verb.toUpperCase()} ${url} method is undefined`)
36+
}
37+
if (def.auth && def.auth !== 'jwt') {
38+
throw new Error(`auth type "${def.auth}" is not supported`)
39+
}
40+
41+
const actions = []
42+
// Authentication
43+
if (def.auth) {
44+
actions.push((req, res, next) => {
45+
jwtAuthenticator(_.pick(config, ['AUTH_SECRET', 'VALID_ISSUERS']))(req, res, next)
46+
})
47+
}
48+
// Authorization
49+
if (def.permission) {
50+
actions.push(permissions(def.permission))
51+
}
52+
// main middleware
53+
actions.push(async (req, res, next) => {
54+
try {
55+
await def.method(req, res, next)
56+
} catch (e) {
57+
next(e)
58+
}
59+
})
60+
61+
logger.info(`Endpoint discovered : ${verb.toLocaleUpperCase()} /${config.API_VERSION}${url}`)
62+
apiRouter[verb](`/${config.API_VERSION}${url}`, actions)
63+
})
64+
})
65+
app.use('/', apiRouter)
66+
const spec = fs.readFileSync(path.join(__dirname, 'docs/swagger.yaml'), 'utf8')
67+
const swaggerDoc = jsyaml.safeLoad(spec)
68+
69+
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDoc))
70+
71+
app.use(errorMiddleware())
72+
app.use('*', (req, res) => {
73+
const pathKey = req.baseUrl.substring(config.API_VERSION.length + 1)
74+
const route = routes[pathKey]
75+
if (route) {
76+
res.status(405).json({ message: 'The requested method is not supported.' })
77+
} else {
78+
res.status(404).json({ message: 'The requested resource cannot found.' })
79+
}
80+
});
81+
82+
(async () => {
83+
await models.init()
84+
initPermissions() // initialize permission policies
85+
httpServer.listen(app.get('port'), () => {
86+
logger.info(`Express server listening on port ${app.get('port')}`)
87+
})
88+
})()

config/default.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* the default config
3+
*/
4+
5+
module.exports = {
6+
LOG_LEVEL: process.env.LOG_LEVEL || 'debug',
7+
PORT: process.env.PORT || 3001,
8+
9+
AUTH_SECRET: process.env.AUTH_SECRET || 'CLIENT_SECRET',
10+
VALID_ISSUERS: process.env.VALID_ISSUERS ? process.env.VALID_ISSUERS.replace(/\\"/g, '')
11+
: '["https://topcoder-dev.auth0.com/", "https://api.topcoder.com"]',
12+
13+
PAGE_SIZE: process.env.PAGE_SIZE || 20,
14+
MAX_PAGE_SIZE: parseInt(process.env.MAX_PAGE_SIZE) || 100,
15+
API_VERSION: process.env.API_VERSION || 'api/1.0',
16+
17+
DB_NAME: process.env.DB_NAME || 'skills-db',
18+
DB_USERNAME: process.env.DB_USER || 'postgres',
19+
DB_PASSWORD: process.env.DB_PASSWORD || 'password',
20+
DB_HOST: process.env.DB_HOST || 'localhost',
21+
DB_PORT: process.env.DB_PORT || 5432,
22+
23+
// ElasticSearch
24+
ES: {
25+
HOST: process.env.ES_HOST || 'http://localhost:9200',
26+
ES_REFRESH: process.env.ES_REFRESH || 'true',
27+
28+
ELASTICCLOUD: {
29+
id: process.env.ELASTICCLOUD_ID,
30+
username: process.env.ELASTICCLOUD_USERNAME,
31+
password: process.env.ELASTICCLOUD_PASSWORD
32+
},
33+
34+
// es mapping: _index, _type, _id
35+
DOCUMENTS: {
36+
skill: {
37+
index: process.env.SKILL_INDEX || 'skill',
38+
type: '_doc',
39+
enrichPolicyName: process.env.SKILL_ENRICH_POLICYNAME || 'skill-policy'
40+
},
41+
taxonomy: {
42+
index: process.env.TAXONOMY_INDEX || 'taxonomy',
43+
type: '_doc',
44+
pipelineId: process.env.TAXONOMY_PIPELINE_ID || 'taxonomy-pipeline',
45+
enrichPolicyName: process.env.TAXONOMY_ENRICH_POLICYNAME || 'taxonomy-policy'
46+
}
47+
},
48+
MAX_BATCH_SIZE: parseInt(process.env.MAX_BATCH_SIZE, 10) || 10000,
49+
MAX_BULK_SIZE: parseInt(process.env.MAX_BULK_SIZE, 10) || 100
50+
}
51+
}

config/production.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* The production configuration file.
3+
*/
4+
5+
module.exports = {
6+
LOG_LEVEL: process.env.LOG_LEVEL || 'info'
7+
}

docker-pgsql-es/docker-compose.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
version: '3'
2+
services:
3+
postgres:
4+
image: "postgres:12.4"
5+
volumes:
6+
- database-data:/var/lib/postgresql/data/
7+
ports:
8+
- ${DB_PORT}:${DB_PORT}
9+
environment:
10+
POSTGRES_PASSWORD: ${DB_PASSWORD}
11+
POSTGRES_USER: ${DB_USERNAME}
12+
POSTGRES_DB: ${DB_NAME}
13+
esearch:
14+
image: elasticsearch:7.7.1
15+
container_name: skills-data-processor-es_es
16+
ports:
17+
- ${ES_PORT}:${ES_PORT}
18+
environment:
19+
- discovery.type=single-node
20+
21+
volumes:
22+
database-data:
23+

docker-pgsql-es/sample.env

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
DB_NAME=skills-db
2+
DB_USERNAME=postgres
3+
DB_PASSWORD=password
4+
DB_PORT=5432
5+
ES_PORT=9200

docker/Dockerfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Use the base image with Node.js 12
2+
FROM node:12
3+
4+
# Set working directory for future use
5+
WORKDIR /skills_api
6+
7+
# Copy the current directory into the Docker image
8+
COPY . /skills_api
9+
10+
# Install the dependencies from package.json
11+
RUN npm install
12+
13+
# Expose port
14+
EXPOSE ${PORT}
15+
16+
# start api
17+
CMD npm start

docker/docker-compose.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: '3'
2+
services:
3+
skills_api:
4+
image: skills_api:latest
5+
build:
6+
context: ../
7+
dockerfile: docker/Dockerfile
8+
env_file:
9+
- .env
10+
ports:
11+
- ${PORT}:${PORT}
12+

docker/sample.env

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
DB_NAME=skills-db
2+
DB_USERNAME=postgres
3+
DB_PASSWORD=password
4+
DB_HOST=host.docker.internal
5+
DB_PORT=5432
6+
7+
ES_HOST=http://host.docker.internal:9200
8+
PORT=3001

0 commit comments

Comments
 (0)