Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
shivam.tay committed Oct 7, 2020
0 parents commit 222b0c6
Show file tree
Hide file tree
Showing 28 changed files with 2,167 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Created by .ignore support plugin (hsz.mobi)
node_modules/
.idea/
npm-debug.log*
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#Image Upload Service
This Image Upload Service uploads a given image of type jpeg/png with a description to AWS S3 and stores the image metadata to AWS MySQL RDS. The service is extendable to support Db migration as well. It handles all the edge cases and failures and displays user friendly message to the end user.
The service also handles logical inconsistency where it first tries to upload the image to S3, if it is successfully uploaded then it tries to insert the data into RDS. If RDS operation fails, then the uploaded image is deleted from S3 and user is notified that the upload was unsuccessful.
The backend is written in Javascript.

##System Dependencies
Ensure following dependencies are present before installing the application.
1. ```AWS S3``` bucket for storing the images. Ensure that ```access key``` provided to the application has the permission to do ```PutObject``` and ```DeleteObject``` commands on the bucket.
2. ```AWS Mysql RDS``` version 5.x or later. Ensure that username and password combination provided to the application has access to connect to the RDS and do create, drop & insert query on it.

##Installation of service

###Local environment
1. Make sure ```node 8.x or higher``` and ```npm 6.x``` is installed.
2. Find ```deployConfig.js``` file inside ```config``` folder and put the bucket name and RDS host in ```dev``` config. Rest details need not be changed.
```
awsBucketName:
mysqlDbConfig: {
host:
port:
}
```
3. Find ```aws-credentials``` file in the project directory and put the following fields in under ```image-upload-service-dev``` tag. The fields are self-explanatory.
```
aws_access_key_id =
aws_secret_access_key =
rds_image_db_user =
rds_image_db_password =
```
4. Checkout the Project directory in terminal and run ```npm install ```.
5. Run ```npm start``` after the npm packages are installed successfully.
6. The server will start and create the required database and table on its own in the provided ```AWS RDS```.
7. Open ```127.0.0.1``` in browser (The Latest Chrome version), and the application will be ready to use.
8. Application logs can be found inside ```logs``` folder.

###Production environment
1. Make sure ```node 8.x or higher``` and ```npm 6.x``` is installed.
2. Find ```deployConfig.js``` file inside ```config``` folder and put the bucket name and RDS host in ```prod``` config. Rest details need not be changed.
```
awsBucketName:
mysqlDbConfig: {
host:
port:
}
```
3. Find ```aws-credentials``` file in the project directory and put the following fields in under ```image-upload-service-prod``` tag. The fields are self-explanatory.
```
aws_access_key_id =
aws_secret_access_key =
rds_image_db_user =
rds_image_db_password =
```
4. Checkout the Project directory in terminal and run ```npm install ```.
5. Export env variable using command ```export NODE_ENV=prod```.
6. Run ```npm start``` after the npm packages are installed successfully.
7. The server will start and create the required database and table on its own in the provided ```AWS RDS```.
8. Application logs can be found inside ```logs``` folder.
62 changes: 62 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require("./logger");
var expressWinston = require('express-winston');
var winston = require('winston');
var projectEnv = require('./config/projectEnv').projectEnv;
var accessLogsfile = projectEnv.accessLogs.filename;

var indexRouter = require('./routes/index');

var app = express();

var allowCrossDomain = function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
};
app.use(allowCrossDomain);

app.use(expressWinston.logger({
transports: [
new winston.transports.DailyRotateFile({
name : 'tps-access-file',
datePattern: '.yyyy-MM-dd',
filename: accessLogsfile,
handleExceptions: true,
json: true,
colorize: false
})
],
meta: true, // optional: control whether you want to log the meta data about the request (default to true)
expressFormat: true, // Use the default Express/morgan request formatting, with the same colors. Enabling this will override any msg and colorStatus if true. Will only output colors on transports with colorize set to true
colorStatus: true // Color the status code, using the Express/morgan color palette (default green, 3XX cyan, 4XX yellow, 5XX red). Will not be recognized if expressFormat is true
}));

// view engine setup
app.set('views', path.join(__dirname, 'views'));

app.use(express.json({ limit: '1mb'}));
app.use(express.urlencoded({ extended: false, limit: '1mb'}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);

// error handler
app.use(function(err, req, res, next) {
var errorObj={};
errorObj['err_name'] = "APP_FAILURE";
errorObj['err_stk'] = err.stack;
logger.error(JSON.stringify(errorObj));
if(err.message === 'request entity too large') {
res.status(400).send(err);
} else {
res.status(500).send(err);
res.end();
}
});

module.exports = app;
13 changes: 13 additions & 0 deletions aws-credentials
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#aws credentials for dev environment
[image-upload-service-dev]
aws_access_key_id =
aws_secret_access_key =
rds_image_db_user =
rds_image_db_password =

#aws credentials for prod environment
[image-upload-service-prod]
aws_access_key_id =
aws_secret_access_key =
rds_image_db_user =
rds_image_db_password =
96 changes: 96 additions & 0 deletions bin/www
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env node

/**
* Module dependencies.
*/

var Q = require('q');
var app = require('../app');
var debug = require('debug')('alerts-framework:server');
var http = require('http');
var projectEnv = require('../config/projectEnv').projectEnv;
const logger = require('../logger');

/**
* Get port from environment and store in Express.
*/

var port = normalizePort(process.env.PORT || projectEnv.httpPort);
app.set('port', port);

/**
* Create HTTP server.
*/

var server = http.createServer(app);
var defer = Q.defer();

var startServer = function () {
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
return defer.promise;
};

/**
* Normalize a port into a number, string, or false.
*/

function normalizePort(val) {
var port = parseInt(val, 10);

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

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

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
switch (error.code) {
case 'EACCES':
logger.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
logger.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}

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

function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
logger.info('Listening on ' + bind);
defer.resolve();
}

exports.startServer = startServer;
49 changes: 49 additions & 0 deletions config/deployConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
exports.envConfig = {
"dev": {
logPath:'./logs/',
logs: {
transports: [
{name: 'image-upload-service-error-file', level: 'error', filename: './logs/image-upload-service-error-logs.log'},
{name: 'image-upload-service-info-file', level: 'info', filename: './logs/image-upload-service-info-logs.log'}
],
console: true
},
// Bucket name for dev environment
awsBucketName: '',
// AWS mysql RDS details for dev environment
mysqlDbConfig: {
host: '',
port: 3306,
database: 'imagesmetadatadbdev',
tableName: 'image_metadata',
},
awsCredProfile: 'image-upload-service-dev',
awsCredFilePath: __dirname.replace(/config$/,'') + 'aws-credentials',
accessLogs: {filename: './logs/image-upload-service-access-logs.log'},
httpPort: 80
},

"prod": {
logPath:'./logs/',
logs: {
transports: [
{name: 'image-upload-service-error-file', level: 'error', filename: './logs/image-upload-service-error-logs.log'},
{name: 'image-upload-service-info-file', level: 'info', filename: './logs/image-upload-service-info-logs.log'}
],
console: true
},
// Bucket name for production setup
awsBucketName: '',
// AWS mysql RDS details for production
mysqlDbConfig: {
host: '',
port: 3306,
database: 'imagesmetadatadbprod',
tableName: 'image_metadata',
},
awsCredProfile: 'image-upload-service-prod',
awsCredFilePath: __dirname.replace(/config$/,'') + 'aws-credentials',
accessLogs: {filename: './logs/image-upload-service-access-logs.log'},
httpPort: 80
},
};
12 changes: 12 additions & 0 deletions config/projectEnv.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
var config = require('./deployConfig').envConfig;
var env = process.env.NODE_ENV;

if(env == undefined){
env = "dev"
}

console.log("env :" + env);
var projectEnv = config[env];

exports.projectEnv = projectEnv;
exports.env = env;
8 changes: 8 additions & 0 deletions image-upload-service.iml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
79 changes: 79 additions & 0 deletions init/init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
const async = require('async');
const Q = require('q');
const logger = require('../logger');
const server = require('../bin/www');
const initDbConnection = require('../persistence/dbConnection');
const initMysqlDbAndTables = require('./initMysqlDbAndTables');

const initFunctions = [
{promiseMethod: initDbConnection},
{promiseMethod: initMysqlDbAndTables},
{promiseMethod: server.startServer}
];

const executePromises = function (params) {
const defer = Q.defer();
try {
async.eachSeries(params.promiseArray, function (currentPromiseObj, callback) {
try {
params.logger.info({'msg':'inprocess',promiseMethod:currentPromiseObj.promiseMethod.name});
if (currentPromiseObj.sync) {
currentPromiseObj.promiseMethod(currentPromiseObj.params);
params.logger.info({'msg':"success",promiseMethod:currentPromiseObj.promiseMethod.name});
callback();
}
else {
currentPromiseObj.promiseMethod(currentPromiseObj.params).then(function () {
params.logger.info({'msg':"success",promiseMethod:currentPromiseObj.promiseMethod.name});
callback();
}, function (err) {
params.logger.error({err_name:'PROMISE_METHOD_ERROR',promiseMethod:currentPromiseObj.promiseMethod.name,err_stk:err.stack});
if(currentPromiseObj.skipErrors)
callback();
else
callback(err);
})
}
}
catch (ex) {
params.logger.error({err_name:'PROMISE_METHOD_EXCEPTION',promiseMethod:currentPromiseObj.promiseMethod.name,err_stk:ex.stack});
if(currentPromiseObj.skipErrors)
callback();
else
callback(ex);
}
}, function (err) {
if (err) {
params.logger.error({err_name:'PROMISE_ARRAY_ERROR',err_stk:err.stack});
defer.reject(err);
}
else
defer.resolve();
})
}
catch (ex) {
params.logger.error({err_name:'PROMISE_ARRAY_EXCEPTION',err_stk:ex.stack});
defer.reject(ex);
}
return defer.promise;
};

var init = function () {
try {
executePromises({
promiseArray: initFunctions,
sequence: true,
logger: logger
}).then(function () {
logger.info({msg: 'INIT_SUCCESS'});
logger.info({msg: 'Application running on http://127.0.0.1/'});
}, function (err) {
logger.error({err_name: 'INIT_FAILURE', err_stk: err.stack});
});
}
catch (ex) {
logger.error({err_name: 'INIT_FAILURE', err_stk: ex.stack});
}
};

init();
Loading

0 comments on commit 222b0c6

Please sign in to comment.