Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tprost #140

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .bowerrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"directory": "public/lib"
}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
npm-debug.log
*.sqlite
public/lib
dist
uploads
6 changes: 6 additions & 0 deletions .sequelizerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
var path = require('path')

module.exports = {
'config': path.resolve('models', 'config', 'config.js'),
'migrations-path': path.resolve('models', 'migrations')
}
13 changes: 13 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,16 @@ Evaluation of your submission will be based on the following criteria.
1. What design decisions did you make when designing your models/entities? Why (i.e. were they explained?)
1. Did you separate any concerns in your application? Why or why not?
1. Does your solution use appropriate datatypes for the problem as described?

# Instructions on how to build/run this application

1. Install Node.js
2. Install sqlite3
3. Run `npm install` in the root of this project
4. Run `npm start` and visit `locahost:3000`

# What I'm proud of

I like the structure of the project because it is relatively organized and is broken down into individual working pieces, which could be unit tested.

I also liked that I used client-side JavaScript to submit the CSV file, as opposed to a vanilla HTML5 form, because it keeps the API decoupled from the frontend.
50 changes: 50 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
var express = require('express');
var path = require('path');
var http = require('http');
var expressNunjucks = require('express-nunjucks');

var app = express();

// some config
require('./config/express')(app);
var config = require('./config');

// views
app.locals.js = require('./config/public.js').js();
const isDev = app.get('env') === 'development';
app.set('views', __dirname + '/views');
const njk = expressNunjucks(app, {
watch: isDev,
noCache: isDev
});
app.set('views', path.join(__dirname, 'views'));

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

app.use('/', routes);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});

// error handler
// no stacktraces leaked to user unless in development environment
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: (app.get('env') === 'development') ? err : {}
});
});

var server = http.createServer(app);

server.listen(config.port, function(){
console.log('Express server listening on %d, in %s mode', config.port, app.get('env'));
});

module.exports = app;
22 changes: 22 additions & 0 deletions config/express.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

var express = require('express');
var bodyParser = require('body-parser');
var path = require('path');
var config = require('./index.js');

module.exports = function(app) {
app.use(bodyParser.json());

var env = app.get('env');

if ('production' === env || 'test' === env) {
app.use(express.static(path.join(config.root, 'dist')));
}

if ('development' === env) {
app.use(express.static(path.join(config.root, 'dist')));
app.use('/public', express.static(path.join(config.root, 'public')));
}

};
28 changes: 28 additions & 0 deletions config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

var path = require('path');
var _ = require('lodash');

function requiredProcessEnv(name) {
if(!process.env[name]) {
throw new Error('You must set the ' + name + ' environment variable');
}
return process.env[name];
}

var all = {
env: process.env.NODE_ENV,

root: path.normalize(__dirname + '/..'),

port: process.env.PORT || 3000

};

// Export the config object based on the NODE_ENV
// ==============================================
// module.exports = _.merge(
// all,
// require('./' + process.env.NODE_ENV + '.js') || {});

module.exports = all;
36 changes: 36 additions & 0 deletions config/public.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
var config = require('./index.js');
var _ = require('lodash');
var glob = require('glob');
var path = require('path');

var assets = {
lib: {
"angular": "lib/angular/angular.js",
"angular-route": "lib/angular-route/angular-route.js"
},
app: {
"modules": "js/**/*.module.js",
"components": "js/**/!(*.spec|*.mock).js"
},
tests: {
"mocking": "lib/angular-mocks/angular-mocks.js",
"tests": "js/**/*.spec.js"
}
};

module.exports = {
globs: function() {
return assets;
},
js: function() {
var files = [];
_.each([assets.lib, assets.app], function(value, key) {
_.forIn(value, function(value, key) {
var globToGlob = path.join('public', value);
var filesToAdd = glob.sync(globToGlob);
files = files.concat(filesToAdd);
});
});
return _.uniq(files);
}
};
79 changes: 79 additions & 0 deletions controllers/expenses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
var models = require('../models');
var Expense = models.Expense;
var Request = models.Request;

var parser = require('../middleware/parser.js');
var monthify = require('../middleware/monthifier.js').monthify;

var Q = require('q');
var fs = require('fs');
var _ = require('lodash');

function fileToString(file) {
var deferred = Q.defer();
fs.readFile(file.path, function(error, data) {
if (error) {
deferred.reject(new Error(error));
}
deferred.resolve(data.toString());
});
return deferred.promise;
};

// given an array of expenses, create a Request
// and then create and save Expense objects
function addExpenses(expenses) {
return Request.create().then(function(request) {
_.each(expenses, function(expense) {
expense.request_id = request.dataValues.id;
});
return Expense.bulkCreate(expenses).then(function() {
return Expense.findAll({
where: {
request_id: request.dataValues.id
}
});
});
});
};

module.exports = {


addExpenses: function(req, res) {
if (req.file) {
// convert the submitted file to a string
fileToString(req.file)
.then(function(str) {
// parse the string to array of expense objects
return parser.parse(str);
})
.then(function(expenses) {
// save the expense objects
return addExpenses(expenses);
})
.then(function(expenses) {
return monthify(expenses).then(function(months) {
return {
expenses: expenses,
months: months
};
});
})
.then(function(data) {
// return expenses on success
res.json(data);
})
.catch(function(error) {
// if there are any errors provide an error message
res.status(400).json({
error: error.toString()
});
});
} else {
res.status(400).json({
error: "No CSV file was provided."
});
}
}
};
Loading