Skip to content

Commit 4d2dd18

Browse files
committed
bookings api challenge 1 output
1 parent fad4d36 commit 4d2dd18

Some content is hidden

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

42 files changed

+16093
-3
lines changed

.gitignore

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
lerna-debug.log*
8+
9+
# Diagnostic reports (https://nodejs.org/api/report.html)
10+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11+
12+
# Runtime data
13+
pids
14+
*.pid
15+
*.seed
16+
*.pid.lock
17+
18+
# Directory for instrumented libs generated by jscoverage/JSCover
19+
lib-cov
20+
21+
# Coverage directory used by tools like istanbul
22+
coverage
23+
*.lcov
24+
25+
# nyc test coverage
26+
.nyc_output
27+
28+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29+
.grunt
30+
31+
# Bower dependency directory (https://bower.io/)
32+
bower_components
33+
34+
# node-waf configuration
35+
.lock-wscript
36+
37+
# Compiled binary addons (https://nodejs.org/api/addons.html)
38+
build/Release
39+
40+
# Dependency directories
41+
node_modules/
42+
jspm_packages/
43+
44+
# Snowpack dependency directory (https://snowpack.dev/)
45+
web_modules/
46+
47+
# TypeScript cache
48+
*.tsbuildinfo
49+
50+
# Optional npm cache directory
51+
.npm
52+
53+
# Optional eslint cache
54+
.eslintcache
55+
56+
# Microbundle cache
57+
.rpt2_cache/
58+
.rts2_cache_cjs/
59+
.rts2_cache_es/
60+
.rts2_cache_umd/
61+
62+
# Optional REPL history
63+
.node_repl_history
64+
65+
# Output of 'npm pack'
66+
*.tgz
67+
68+
# Yarn Integrity file
69+
.yarn-integrity
70+
71+
# dotenv environment variables file
72+
.env
73+
.env.test
74+
75+
# parcel-bundler cache (https://parceljs.org/)
76+
.cache
77+
.parcel-cache
78+
79+
# Next.js build output
80+
.next
81+
out
82+
83+
# Nuxt.js build / generate output
84+
.nuxt
85+
dist
86+
87+
# Gatsby files
88+
.cache/
89+
# Comment in the public line in if your project uses Gatsby and not Next.js
90+
# https://nextjs.org/blog/next-9-1#public-directory-support
91+
# public
92+
93+
# vuepress build output
94+
.vuepress/dist
95+
96+
# Serverless directories
97+
.serverless/
98+
99+
# FuseBox cache
100+
.fusebox/
101+
102+
# DynamoDB Local files
103+
.dynamodb/
104+
105+
# TernJS port file
106+
.tern-port
107+
108+
# Stores VSCode versions used for testing VSCode extensions
109+
.vscode-test
110+
111+
# yarn v2
112+
.yarn/cache
113+
.yarn/unplugged
114+
.yarn/build-state.yml
115+
.yarn/install-state.gz
116+
.pnp.*

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Topcoder Bookings API
2+
3+
## Dependencies
4+
5+
- nodejs https://nodejs.org/en/ (v12+)
6+
- Postgres
7+
8+
## Configuration
9+
10+
Configuration for the application is at `config/default.js`.
11+
The following parameters can be set in config files or in env variables:
12+
13+
- `LOG_LEVEL`: the log level, default is 'debug'
14+
- `PORT`: the server port, default is 3000
15+
- `BASE_PATH`: the server api base path
16+
- `AUTH_SECRET`: The authorization secret used during token verification.
17+
- `VALID_ISSUERS`: The valid issuer of tokens, a json array contains valid issuer.
18+
- `POSTGRES_URL`: Postgres database url.
19+
- `DB_SCHEMA_NAME`: string - postgres database target schema
20+
- `PROJECT_API_URL`: the project service url
21+
22+
23+
## Postgres Database Setup
24+
Go to https://www.postgresql.org/ download and install the Postgres.
25+
Modify `POSTGRES_URL` under `config/default.js` to meet your environment.
26+
Run `npm run init-db` to create table
27+
28+
## Local Deployment
29+
30+
- Install dependencies `npm install`
31+
- Run lint `npm run lint`
32+
- Run lint fix `npm run lint:fix`
33+
- Clear and init db `npm run init-db`
34+
- Start app `npm start`
35+
- App is running at `http://localhost:3000`
36+
37+
## Testing
38+
- Run mock-project-service app
39+
- Run `npm run test` to execute unit tests
40+
- Run `npm run cov` to execute unit tests and generate coverage report.
41+
42+
## Verification
43+
Refer to the verification document [Verification.md](Verification.md)

Verification.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Topcoder Bookings API
2+
3+
## Postman test
4+
- Refer `mock-project-service/ReadMe.md` to start the mock app
5+
- Refer `ReadMe.md` to start the app and postgres database
6+
- Run `npm run init-db` to init db before testing.
7+
- Import Postman collection and environment file in the `docs` folder to Postman and execute the scripts to validate the app from top to bottom.
8+
9+
10+
11+
## Unit test Coverage
12+
13+
14+
62 passing (176ms)
15+
16+
17+
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
18+
----------------------------|---------|----------|---------|---------|-------------------
19+
All files | 99.36 | 96 | 100 | 99.67 |
20+
config | 100 | 100 | 100 | 100 |
21+
default.js | 100 | 100 | 100 | 100 |
22+
test.js | 100 | 100 | 100 | 100 |
23+
src | 90.48 | 50 | 100 | 94.12 |
24+
bootstrap.js | 90.48 | 50 | 100 | 94.12 | 18
25+
src/common | 100 | 96.55 | 100 | 100 |
26+
errors.js | 100 | 50 | 100 | 100 | 23
27+
helper.js | 100 | 100 | 100 | 100 |
28+
src/models | 100 | 92.86 | 100 | 100 |
29+
Job.js | 100 | 100 | 100 | 100 |
30+
JobCandidate.js | 100 | 100 | 100 | 100 |
31+
ResourceBooking.js | 100 | 100 | 100 | 100 |
32+
index.js | 100 | 80 | 100 | 100 | 29
33+
src/services | 100 | 100 | 100 | 100 |
34+
JobCandidateService.js | 100 | 100 | 100 | 100 |
35+
JobService.js | 100 | 100 | 100 | 100 |
36+
ResourceBookingService.js | 100 | 100 | 100 | 100 |

app-constants.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* App constants
3+
*/
4+
5+
const UserRoles = {
6+
BookingManager: 'bookingmanager'
7+
}
8+
9+
module.exports = {
10+
UserRoles
11+
}

app-routes.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Configure all routes for express app
3+
*/
4+
5+
const _ = require('lodash')
6+
const config = require('config')
7+
const HttpStatus = require('http-status-codes')
8+
const helper = require('./src/common/helper')
9+
const routes = require('./src/routes')
10+
const constants = require('./app-constants')
11+
const authenticator = require('tc-core-library-js').middleware.jwtAuthenticator
12+
13+
/**
14+
* Configure all routes for express app
15+
* @param app the express app
16+
*/
17+
module.exports = (app) => {
18+
// Load all routes
19+
_.each(routes, (verbs, path) => {
20+
_.each(verbs, (def, verb) => {
21+
const controllerPath = `./src/controllers/${def.controller}`
22+
const method = require(controllerPath)[def.method]; // eslint-disable-line
23+
if (!method) {
24+
throw new Error(`${def.method} is undefined`)
25+
}
26+
27+
const actions = []
28+
actions.push((req, res, next) => {
29+
req.signature = `${def.controller}#${def.method}`
30+
next()
31+
})
32+
33+
// add Authenticator check if route has auth
34+
if (def.auth) {
35+
actions.push((req, res, next) => {
36+
authenticator(_.pick(config, ['AUTH_SECRET', 'VALID_ISSUERS']))(req, res, next)
37+
})
38+
39+
actions.push((req, res, next) => {
40+
req.authUser.jwtToken = req.headers.authorization
41+
if (_.includes(req.authUser.roles, constants.UserRoles.BookingManager)) {
42+
req.authUser.isBookingManager = true
43+
}
44+
next()
45+
})
46+
}
47+
48+
actions.push(method)
49+
const fullPath = config.get('BASE_PATH') + path
50+
app[verb](fullPath, helper.autoWrapExpress(actions))
51+
})
52+
})
53+
54+
// Check if the route is not found or HTTP method is not supported
55+
app.use('*', (req, res) => {
56+
let url = req.baseUrl
57+
if (url.indexOf(config.get('BASE_PATH')) === 0) {
58+
url = url.substring(config.get('BASE_PATH').length)
59+
}
60+
const route = routes[url]
61+
if (route) {
62+
res.status(HttpStatus.METHOD_NOT_ALLOWED).json({ message: 'The requested HTTP method is not supported.' })
63+
} else {
64+
res.status(HttpStatus.NOT_FOUND).json({ message: 'The requested resource cannot be found.' })
65+
}
66+
})
67+
}

app.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* The application entry point
3+
*/
4+
5+
require('./src/bootstrap')
6+
const _ = require('lodash')
7+
const config = require('config')
8+
const express = require('express')
9+
const cors = require('cors')
10+
const HttpStatus = require('http-status-codes')
11+
const logger = require('./src/common/logger')
12+
const interceptor = require('express-interceptor')
13+
14+
// setup express app
15+
const app = express()
16+
17+
app.use(cors())
18+
app.use(express.json())
19+
app.use(express.urlencoded({ extended: true }))
20+
app.set('port', config.PORT)
21+
22+
// intercept the response body from jwtAuthenticator
23+
app.use(interceptor((req, res) => {
24+
return {
25+
isInterceptable: () => {
26+
return res.statusCode === 403
27+
},
28+
29+
intercept: (body, send) => {
30+
let obj
31+
if (body.length > 0) {
32+
try {
33+
obj = JSON.parse(body)
34+
} catch (e) {
35+
logger.error('Invalid response body.')
36+
}
37+
}
38+
if (obj && _.get(obj, 'result.content.message')) {
39+
const ret = { message: obj.result.content.message }
40+
res.statusCode = 401
41+
send(JSON.stringify(ret))
42+
} else {
43+
send(body)
44+
}
45+
}
46+
}
47+
}))
48+
49+
// Register routes
50+
require('./app-routes')(app)
51+
52+
// The error handler
53+
// eslint-disable-next-line no-unused-vars
54+
app.use((err, req, res, next) => {
55+
logger.logFullError(err, req.signature || `${req.method} ${req.url}`)
56+
const errorResponse = {}
57+
const status = err.isJoi ? HttpStatus.BAD_REQUEST : (err.status || err.httpStatus || HttpStatus.INTERNAL_SERVER_ERROR)
58+
59+
if (_.isArray(err.details)) {
60+
if (err.isJoi) {
61+
_.map(err.details, (e) => {
62+
if (e.message) {
63+
if (_.isUndefined(errorResponse.message)) {
64+
errorResponse.message = e.message
65+
} else {
66+
errorResponse.message += `, ${e.message}`
67+
}
68+
}
69+
})
70+
}
71+
}
72+
73+
if (err.response) {
74+
// extract error message from V3 API
75+
errorResponse.message = _.get(err, 'response.body.result.content')
76+
}
77+
78+
if (_.isUndefined(errorResponse.message)) {
79+
if (err.message && (err.httpStatus || status !== HttpStatus.INTERNAL_SERVER_ERROR)) {
80+
errorResponse.message = err.message
81+
} else {
82+
errorResponse.message = 'Internal server error'
83+
}
84+
}
85+
86+
res.status(status).json(errorResponse)
87+
})
88+
89+
const server = app.listen(app.get('port'), () => {
90+
logger.info(`Express server listening on port ${app.get('port')}`)
91+
})
92+
93+
if (process.env.NODE_ENV === 'test') {
94+
module.exports = server
95+
}

config/default.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module.exports = {
2+
LOG_LEVEL: process.env.LOG_LEVEL || 'debug',
3+
PORT: process.env.PORT || 3000,
4+
BASE_PATH: process.env.BASE_PATH || '/api/v5',
5+
6+
AUTH_SECRET: process.env.AUTH_SECRET || 'mysecret',
7+
VALID_ISSUERS: process.env.VALID_ISSUERS || '["https://api.topcoder-dev.com", "https://api.topcoder.com", "https://topcoder-dev.auth0.com/"]',
8+
9+
POSTGRES_URL: process.env.POSTGRES_URL || 'postgres://postgres:postgres@localhost:5432/postgres',
10+
DB_SCHEMA_NAME: process.env.DB_SCHEMA_NAME || 'bookings',
11+
PROJECT_API_URL: process.env.PROJECT_API_URL || 'http://localhost:4000'
12+
}

config/test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
LOG_LEVEL: process.env.LOG_LEVEL || 'info'
3+
}

0 commit comments

Comments
 (0)