Skip to content

Commit

Permalink
changed filder structure to add src
Browse files Browse the repository at this point in the history
  • Loading branch information
shaunlumbcgov committed Jun 26, 2023
1 parent 3cde7fa commit e541ef2
Show file tree
Hide file tree
Showing 6 changed files with 356 additions and 0 deletions.
17 changes: 17 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ frontend/.env.development
frontend/.env.production
**/local.json


# Editor directories and files
.idea
.vscode
*.iml
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
*.pem

#ignore modules
node_modules
backend/src/config/local.json

# log files
.*-audit.json
*.log
logs
34 changes: 34 additions & 0 deletions backend/src/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

const config = require('./config/index');
const log = require('./components/logger');
const dotenv = require('dotenv');
const express = require("express");
const axios = require("axios");
const cors = require('cors');
const NodeCache = require("node-cache");
const apiRouter = express.Router();
const instituteRouter = require('./routes/institute-router');
const app = express();
app.use(cors());

app.use(/(\/api)?/, apiRouter);

//institute Router
apiRouter.use('/v1/institute', instituteRouter);


//Handle 500 error
app.use((err, _req, res, next) => {
res?.redirect(config?.get('server:frontend') + '/error?message=500_internal_error');
next();
});

// Handle 404 error
app.use((_req, res) => {
res.redirect(config?.get('server:frontend') + '/error?message=404_Page_Not_Found');
});

// Prevent unhandled errors from crashing application
process.on('unhandledRejection', err => {
});
module.exports = app;
115 changes: 115 additions & 0 deletions backend/src/components/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
'use strict';

const config = require('../config/index');
const { createLogger, format, transports } = require('winston');
require('winston-daily-rotate-file');
const { inspect } = require('util');
const { omit, pickBy } = require('lodash');
const stripAnsi = require('strip-ansi');
const safeStringify = require('fast-safe-stringify');


function isPrimitive(val) {
return val === null || (typeof val !== 'object' && typeof val !== 'function');
}

function formatWithInspect(val, colors = true) {
if (val instanceof Error) {
return '';
}

const shouldFormat = typeof val !== 'string';
const formattedVal = shouldFormat ? inspect(val, { depth: null, colors }) : (colors ? val : stripAnsi(val));

return isPrimitive(val) ? formattedVal : `\n${formattedVal}`;
}

/**
* Handles all the different log formats
* https://github.com/winstonjs/winston/issues/1427#issuecomment-535297716
* https://github.com/winstonjs/winston/issues/1427#issuecomment-583199496
* @param {*} colors
*/
function getDomainWinstonLoggerFormat(colors = true) {
const colorize = colors ? format.colorize() : null;
const loggingFormats = [
format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss.SSS'
}),
format.errors({ stack: true }),
colorize,
format.printf((info) => {
const stackTrace = info.stack ? `\n${info.stack}` : '';

// handle single object
if (!info.message) {
const obj = omit(info, ['level', 'timestamp', Symbol.for('level')]);
return `${info.timestamp} - ${info.level}: ${formatWithInspect(obj, colors)}${stackTrace}`;
}

const splatArgs = info[Symbol.for('splat')] || [];
const rest = splatArgs.map(data => formatWithInspect(data, colors)).join(' ');
const msg = formatWithInspect(info.message, colors);

return `${info.timestamp} - ${info.level}: ${msg} ${rest}${stackTrace}`;
}),
].filter(Boolean);
return format.combine(...loggingFormats);
}

/**
* Handles JSON formats
*/
function getDomainWinstonLoggerJsonFormat() {
const loggingFormats = [
format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss.SSS'
}),
format.errors({ stack: true }),
format.printf((info) => {
const stackTrace = info.stack || '';

let message;
// handle single object
if (!info.message) {
const obj = omit(info, ['level', 'timestamp', Symbol.for('level')]);
message = obj;
} else {
message = stripAnsi(info.message);
}

const splatArgs = info[Symbol.for('splat')] || [];
const detail = splatArgs.map(data => typeof data === 'string' ? stripAnsi(data) : data);

return safeStringify(pickBy({
timestamp: info.timestamp,
level: info.level,
message,
detail: detail.length > 0 ? detail : null,
stackTrace
}));
}),
];
return format.combine(...loggingFormats);
}

const logger = createLogger({
level: config.get('server:logLevel'),
format: getDomainWinstonLoggerJsonFormat(),
transports: [
new transports.DailyRotateFile({
filename: 'app-%DATE%.log',
dirname: './logs',
datePattern: 'YYYY-MM-DD',
maxSize: '5m',
maxFiles: 1,
zippedArchive: true,
})
]
});

logger.add(new transports.Console({
format: getDomainWinstonLoggerFormat(true)
}));

module.exports = logger;
41 changes: 41 additions & 0 deletions backend/src/config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use strict';
const nconf = require('nconf');
const dotenv = require('dotenv');
const path = require('path');
dotenv.config();

const env = process.env.NODE_ENV || 'local';
nconf.argv()
.env()
.file({ file: path.join(__dirname, `${env}.json`) });

//injects environment variables into the json file
nconf.overrides({
environment: env,

server: {
logLevel: process.env.LOG_LEVEL,
morganFormat: 'dev',
port: 8080
}
});
nconf.defaults({
environment: env,
server: {
frontend: process.env.SERVER_FRONTEND,
backend: process.env.SERVER_FRONTEND + '/api',
logLevel: process.env.LOG_LEVEL,
morganFormat: 'dev',
port: 8080,
session: {
maxAge: +process.env.SESSION_MAX_AGE
},
instituteAPIURL: process.env.INSTITUTE_API_URL,
},
oidc: {
clientId: process.env.SOAM_CLIENT_ID,
clientSecret: process.env.SOAM_CLIENT_SECRET,
discovery: process.env.SOAM_DISCOVERY
},
});
module.exports = nconf;
62 changes: 62 additions & 0 deletions backend/src/routes/institute-router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@

const express = require('express');
const router = express.Router();
const log = require('../components/logger');
const config = require('../config/index');
const NodeCache = require("node-cache");
const jsonwebtoken = require("jsonwebtoken");
const axios = require("axios");

const cache = new NodeCache({ stdTTL: config.get("server:instituteAPITokenExpiry") });
const clientId = config.get("oidc:clientId");
const clientSecret = config.get("oidc:clientSecret")
const tokenEndpoint = config.get("oidc:tokenEndpoint")

const data = {
grant_type: "client_credentials",
client_id: clientId,
client_secret: clientSecret,
};

//Batch Routes
router.get('/*',checkToken, getInstituteAPI);

async function getNewToken(req, res, next) {
await axios
.post(tokenEndpoint, data, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
})
.then((response) => {
let accessToken = response.data.access_token;
cache.set("token", accessToken);
})
.catch((error) => {
console.error("Error:", error.response.data);
});
}
async function checkToken(req, res, next) {
try{
if(await !cache.has("token") ){
await getNewToken();
}
next();
}catch(error){
console.log(error)
}
}
async function getInstituteAPI(req, res) {
const memToken = await cache.get("token");
const url = `${config.get('server:instituteAPIURL')}/institute` + req.url;
axios
.get(url, { headers: { Authorization: `Bearer ${memToken}` } })
.then((response) => {
res.json(response.data);
log.info(req.url);
})
.catch((e) => {
log.error('getData Error', e.response ? e.response.status : e.message);
});
}
module.exports = router;
87 changes: 87 additions & 0 deletions backend/src/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use strict';

const config = require('./config/index');
const http = require('http');
const log = require('./components/logger');
const dotenv = require('dotenv');
dotenv.config();

const app = require('./app');

/**
* Get port from environment and store in Express.
*/
const port = config.get('server:port');

const server = http.createServer(app);

/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
const portNum = parseInt(val, 10);

if (isNaN(portNum)) {
// named pipe
return val;
}

if (portNum >= 0) {
// port number
return portNum;
}

return false;
}

/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}

var bind = typeof port === 'string' ?
'Pipe ' + port :
'Port ' + port;

// handle specific listen errors with friendly messages

}

/**
* Event listener for HTTP server "listening" event.
*/

function onListening() {
const addr = server.address();
const bind = typeof addr === 'string' ?
'pipe ' + addr :
'port ' + addr.port;
log.info('Listening on ' + bind);
}
process.on('SIGINT',() => {
server.close(() => {
});
});

process.on('SIGTERM', () => {
server.close(() => {
});
});

//exports are purely for testing
module.exports = {
normalizePort,
onError,
onListening,
server
};

0 comments on commit e541ef2

Please sign in to comment.