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

Commit 89898ec

Browse files
merge search ui api with front end to create single app
1 parent 6139e9b commit 89898ec

File tree

166 files changed

+20922
-14622
lines changed

Some content is hidden

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

166 files changed

+20922
-14622
lines changed

.dockerignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
.idea
3+
**/.DS_Store
4+
.env
5+
coverage

.gitignore

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
.idea/
2-
.vscode/
3-
node_modules/
4-
build
5-
.DS_Store
6-
*.tgz
7-
my-app*
8-
template/src/__tests__/__snapshots__/
9-
lerna-debug.log
10-
npm-debug.log*
11-
yarn-debug.log*
12-
yarn-error.log*
13-
/.changelog
14-
.npm/
15-
yarn.lock
16-
.env.local
1+
node_modules
2+
.idea
3+
**/.DS_Store
4+
upload
5+
scripts/generate
6+
.nyc_output
7+
.env
8+
coverage
9+
docker/api.env

README.md

100644100755
Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,70 @@
1-
# U-Bahn user interface
1+
# UBahn App
22

3-
This code base represents the user interface for the U-Bahn project.
3+
## Install software
44

5-
## Local Deployment
5+
- node 12.x
6+
- npm 6.x
7+
- docker
8+
- S3
69

7-
Before you deploy, you need to configure the following in the application:
10+
## Deployment
811

9-
```text
10-
REACT_APP_API_URL => The endpoint from which the application retrieves the users (and groups and most of the data) as well as to which the updates are pushed to
12+
There are two apps involved - a front end build using create react app and a backend which is a nodejs api
1113

12-
REACT_APP_SEARCH_UI_API_URL => The endpoint from which the user can download the bulk upload template files as well as upload the bulk user upload file to
14+
When working locally, you will run the following commands (after setting the necessary environment variables):
1315

14-
REACT_BULK_UPLOAD_TEMPLATE_ID => The id of the database record which is associated with the bulk upload template file. You would need to query the endpoint under REACT_APP_SEARCH_UI_API_URL to get the id and then set it against this variable
16+
- npm install
17+
- cd client && npm install
18+
- cd .. && npm run dev
1519

16-
REACT_APP_EMSI_SKILLPROVIDER_ID => The skill provider id with name 'EMSI'. Denotes that the skills with an externalId are using EMSI as the skill provider.
20+
The front end proxies request (some of them) to localhost:3001, which is the backend api code base. This configuration is located in `client/package.json` file itself
1721

18-
REACT_APP_ATTRIBUTE_ID_LOCATION
19-
REACT_APP_ATTRIBUTE_ID_COMPANY
20-
REACT_APP_ATTRIBUTE_ID_TITLE
21-
REACT_APP_ATTRIBUTE_ID_ISAVAILABLE
22-
=> All 4 of the above are the ids of the attributes with name "location", "company", "title" and "isAvailable" respectively. These are used to filter attributes for display under a dedicated section named company attributes. Since these already have UI elements of their own, they are filtered from the list of company attributes
23-
REACT_APP_AUTH0_DOMAIN => The Auth0 login domain
24-
REACT_APP_AUTH0_CLIENTID => The Auth0 clientId
25-
```
22+
## Local database deployment
2623

27-
You can create a `.env.local` file and provide the above configuration. Note that there's more configuration that you can change and you can find this under `src/config.js`. The above configurations are the minimum ones, that you need to launch the app successfully.
24+
1. Navigate to docker-db run `docker-compose up -d`
25+
2. Follow *Configuration* section to update config values
26+
3. Run `npm i` and `npm run lint`
27+
4. Create table, `npm run create-tables`, this will create tables (if you need this)
28+
5. Startup server `npm run start`
2829

29-
Once the configuration is set, you can proceed to deploy
30+
## Configuration
3031

31-
The code base has been setup using [Create React App](https://github.com/facebook/create-react-app). Thus, to start the application locally, you need to first (and only once) run the following command:
32+
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:
3233

33-
```bash
34-
$ npm install
35-
// Will install the dependenceis
36-
```
34+
- LOG_LEVEL: the log level
35+
- PORT: the server port
36+
- API_VERSION: the API version
37+
- AUTH_SECRET: TC Authentication secret
38+
- VALID_ISSUERS: valid issuers for TC authentication
39+
- AMAZON.AWS_ACCESS_KEY_ID: The AWS access key
40+
- AMAZON.AWS_SECRET_ACCESS_KEY: The AWS secret key
41+
- AMAZON.AWS_REGION: The Amazon region to use when connecting. For local dynamodb you can set fake value.
42+
- AMAZON.IS_LOCAL_DB: Use local or AWS Amazon DynamoDB
43+
- AMAZON.DYNAMODB_URL: The local url, if using local Amazon DynamoDB
44+
- AMAZON.DYNAMODB_READ_CAPACITY_UNITS: the AWS DynamoDB read capacity units, if using local Amazon DynamoDB
45+
- AMAZON.DYNAMODB_WRITE_CAPACITY_UNITS: the AWS DynamoDB write capacity units, if using local Amazon DynamoDB
46+
- AMAZON.DYNAMODB_UPLOAD_TABLE: DynamoDB table name for upload
47+
- AMAZON.DYNAMODB_TEMPLATE_TABLE: DynamoDB table name for template
48+
- AUTH0_URL: Auth0 URL, used to get TC M2M token
49+
- AUTH0_AUDIENCE: Auth0 audience, used to get TC M2M token
50+
- TOKEN_CACHE_TIME: Auth0 token cache time, used to get TC M2M token
51+
- AUTH0_CLIENT_ID: Auth0 client id, used to get TC M2M token
52+
- AUTH0_CLIENT_SECRET: Auth0 client secret, used to get TC M2M token
53+
- AUTH0_PROXY_SERVER_URL: Proxy Auth0 URL, used to get TC M2M token
54+
- BUSAPI_URL: the bus api, default value is `https://api.topcoder-dev.com/v5`
55+
- KAFKA_ERROR_TOPIC: Kafka error topic, default value is 'common.error.reporting'
56+
- KAFKA_MESSAGE_ORIGINATOR: the Kafka message originator, default value is 'ubahn-search-ui-api'
57+
- UPLOAD_CREATE_TOPIC: the upload create Kafka topic, default value is 'ubahn.action.create'
58+
- TEMPLATE_FILE_MAX_SIZE: the template file restrict size, default value is '2MB'
59+
- TEMPLATE_FILE_MIMETYPE: the template file accept type, default value is 'application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
60+
- TEMPLATE_S3_BUCKET: the template s3 bucket name, default value is 'ubahn'
61+
- UPLOAD_S3_BUCKET: the upload s3 bucket name, default value is 'ubahn'
62+
- S3_OBJECT_URL_EXPIRY_TIME: the s3 url expiry time, default value is '1 hour'
63+
- EMSI_CLIENT_ID: emsi oAuth 2.0 client id, used to get emis oAuth 2.0 token
64+
- EMSI_CLIENT_SECRET: emsi oAuth 2.0 client secret, used to get emsi oAuth 2.0 token
65+
- EMSI_GRANT_TYPE: emsi oAuth 2.0 grant_type, used to get emsi oAuth 2.0 token, should always be the string 'client_credentials'
66+
- EMSI_SCOPE: emsi oAuth 2.0 scope, used to get emsi oAuth 2.0 token, default value is 'emsi_open'
67+
- 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'
68+
- EMSI_BASE_URL: emsi base url, used to get emsi skills, default value is 'https://skills.emsicloud.com/versions/latest'
3769

38-
followed by:
39-
40-
```bash
41-
$ npm start
42-
// Will start the application at http://localhost:3000
43-
```
70+
Also check out the client folder's README file for additional configurations to set for the front end

app-bootstrap.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* App bootstrap
3+
*/
4+
global.Promise = require('bluebird')
5+
const Joi = require('joi')
6+
7+
Joi.id = () => Joi.string().uuid().required()

app-constants.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* App constants
3+
*/
4+
const UserRoles = {
5+
admin: 'Admin',
6+
administrator: 'Administrator',
7+
topcoderUser: 'Topcoder User',
8+
copilot: 'Copilot'
9+
}
10+
11+
const Scopes = {
12+
CreateUpload: 'create:upload',
13+
GetUpload: 'read:upload',
14+
UpdateUpload: 'update:upload',
15+
AllUpload: 'all:upload',
16+
CreateTemplate: 'create:template',
17+
GetTemplate: 'read:template',
18+
AllTemplate: 'all:template',
19+
GetSkill: 'read:skill',
20+
AllSkill: 'all:skill'
21+
}
22+
23+
const AllAuthenticatedUsers = [UserRoles.admin, UserRoles.administrator, UserRoles.topcoderUser, UserRoles.copilot]
24+
25+
module.exports = {
26+
Scopes,
27+
AllAuthenticatedUsers
28+
}

app-routes.js

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/**
2+
* Configure all routes for express app
3+
*/
4+
const _ = require('lodash')
5+
const config = require('config')
6+
const HttpStatus = require('http-status-codes')
7+
const fs = require('fs')
8+
const path = require('path')
9+
const swaggerUi = require('swagger-ui-express')
10+
const jsyaml = require('js-yaml')
11+
const helper = require('./src/common/helper')
12+
const errors = require('./src/common/errors')
13+
const routes = require('./src/routes')
14+
const authenticator = require('tc-core-library-js').middleware.jwtAuthenticator
15+
16+
/**
17+
* Checks if the source matches the term.
18+
*
19+
* @param {Array} source the array in which to search for the term
20+
* @param {Array | String} term the term to search
21+
*/
22+
function checkIfExists (source, term) {
23+
let terms
24+
25+
if (!_.isArray(source)) {
26+
throw new Error('Source argument should be an array')
27+
}
28+
29+
source = source.map(s => s.toLowerCase())
30+
31+
if (_.isString(term)) {
32+
terms = term.split(' ')
33+
} else if (_.isArray(term)) {
34+
terms = term.map(t => t.toLowerCase())
35+
} else {
36+
throw new Error('Term argument should be either a string or an array')
37+
}
38+
39+
for (let i = 0; i < terms.length; i++) {
40+
if (source.includes(terms[i])) {
41+
return true
42+
}
43+
}
44+
45+
return false
46+
}
47+
48+
/**
49+
* Configure all routes for express app
50+
* @param app the express app
51+
*/
52+
module.exports = (app) => {
53+
// Load all routes
54+
_.each(routes, (verbs, path) => {
55+
_.each(verbs, (def, verb) => {
56+
const controllerPath = `./src/controllers/${def.controller}`
57+
const method = require(controllerPath)[def.method]; // eslint-disable-line
58+
if (!method) {
59+
throw new Error(`${def.method} is undefined`)
60+
}
61+
62+
const actions = []
63+
actions.push((req, res, next) => {
64+
req.signature = `${def.controller}#${def.method}`
65+
next()
66+
})
67+
68+
// Authentication and Authorization
69+
if (def.auth === 'jwt') {
70+
actions.push((req, res, next) => {
71+
authenticator(_.pick(config, ['AUTH_SECRET', 'VALID_ISSUERS']))(req, res, next)
72+
})
73+
74+
actions.push((req, res, next) => {
75+
if (!req.authUser) {
76+
return next(new errors.UnauthorizedError('Action is not allowed for invalid token'))
77+
}
78+
79+
if (req.authUser.roles) {
80+
// user
81+
if (def.access && !checkIfExists(def.access, req.authUser.roles)) {
82+
res.forbidden = true
83+
next(new errors.ForbiddenError('You are not allowed to perform this action!'))
84+
} else {
85+
next()
86+
}
87+
} else if (req.authUser.scopes) {
88+
// M2M
89+
if (def.scopes && !checkIfExists(def.scopes, req.authUser.scopes)) {
90+
res.forbidden = true
91+
next(new errors.ForbiddenError('You are not allowed to perform this action!'))
92+
} else {
93+
next()
94+
}
95+
} else if ((_.isArray(def.access) && def.access.length > 0) ||
96+
(_.isArray(def.scopes) && def.scopes.length > 0)) {
97+
next(new errors.UnauthorizedError('You are not authorized to perform this action'))
98+
} else {
99+
next()
100+
}
101+
})
102+
}
103+
104+
actions.push(method)
105+
if (def.upload) {
106+
app[verb](`${config.API_VERSION}${path}`, def.upload, helper.autoWrapExpress(actions))
107+
} else {
108+
app[verb](`${config.API_VERSION}${path}`, helper.autoWrapExpress(actions))
109+
}
110+
})
111+
})
112+
113+
const spec = fs.readFileSync(path.join(__dirname, 'docs/swagger.yaml'), 'utf8')
114+
const swaggerDoc = jsyaml.safeLoad(spec)
115+
116+
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDoc))
117+
118+
// Check if the route is not found or HTTP method is not supported
119+
app.use('*', (req, res) => {
120+
const route = routes[req.baseUrl]
121+
let status
122+
let message
123+
if (route) {
124+
status = HttpStatus.METHOD_NOT_ALLOWED
125+
message = 'The requested HTTP method is not supported.'
126+
} else {
127+
status = HttpStatus.NOT_FOUND
128+
message = 'The requested resource cannot be found.'
129+
}
130+
res.status(status).json({ message })
131+
})
132+
}

0 commit comments

Comments
 (0)