Skip to content
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
31 changes: 31 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Created by .gitignore support plugin (hsz.mobi)

### Node template
# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
# Commenting this out is preferred by some people, see
# https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules

# Users Environment Variables
.lock-wscript
32 changes: 32 additions & 0 deletions GulpFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
var gulp = require('gulp')
, mocha = require('gulp-spawn-mocha')
, join = require('path').join
, nodemon = require('gulp-nodemon')
;

var paths = {
scripts: ['src/**/*.js', 'test/**/*.coffee']
};

gulp.task('mocha.test', function () {
return gulp.src(['test/*.coffee'], {read: false}).pipe(mocha({
bin: 'node_modules/mocha/bin/mocha',
R: 'spec',
compilers: 'coffee:coffee-script',
c: true
})).on('error', console.warn.bind(console));
});

gulp.task('watch', function () {
gulp.watch( paths.scripts , ['mocha.test']);
});

gulp.task('serve', function(){
nodemon({ script: 'index.js', ext: 'js', ignore: ['ignored.js'] })
.on('restart', function () {
console.log('restarted!')
})
});

gulp.task('default', ['watch', 'mocha.test', 'serve']);

4 changes: 4 additions & 0 deletions config/default.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

Security:
saltWorkFactor: 10
tokenTTL: 604800 # 7 days
1 change: 1 addition & 0 deletions config/runtime.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
25 changes: 23 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,27 @@

Place any related documentation in this folder.

## Notes
## Completed Tasks
- User can register / login / logout
- User can CRUD notes
- Functional tests

## Enhancments
- Added Bearer Token support ( oAuth2 )
- Added CORS support ( required by API's )
- Added Logs and Audit reports

## Incomplete Tasks
- API Documentation, would normally use auto-gen documents plugin. Routes defined in `src/server.js`

## Requirements
NodeJS >= 10.*
NPM Packages - `npm install -g gulp mocha chai`
MongoDb

## Run Tests
`$ gulp test mocha.test`

## Start Local Server
`$ gulp serve`

Use this area to communicate any thought processes, ideas, or challenges you encountered. If you had more time, what would you enhance or do differently?
51 changes: 51 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
var CONFIG = require('config')
, mongoose = require( 'mongoose' )
, restify = require('restify');

// Database
// ----------------------------------------------------------------------------
if( !mongoose.connection.readyState ){
mongoose.connect( 'mongodb://localhost/cpb-api-dev', function( err ){
if( err ){
console.log('Unable to connect to Mongo!', err );
process.exit(1);
}
});
}

// Logs
// ----------------------------------------------------------------------------
var Logger = require( 'bunyan' );
global.log = new Logger( {
name : 'cpb-api',
streams: [
{
level : 'info',
// path : 'logs/api-debug.log' // log ERROR and above to a file
stream: process.stdout // log INFO and above to stdout
},
{
level: 'error',
path : 'logs/api-error.log' // log ERROR and above to a file
},
{
level: 'debug',
path : 'logs/debug.log' // log ERROR and above to a file
}
]
});

// Server
// ----------------------------------------------------------------------------
global.server = require('./src/server');

server.on('after', restify.auditLogger({
log: Logger.createLogger({
name: 'audit',
stream: process.stdout
})
}));

server.listen( 8000, function() {
console.log( '%s listening at %s', server.name, server.url );
});
41 changes: 41 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "cpb-api",
"description": "",
"version": "0.0.1",
"main": "index.js",
"dependencies": {
"async": "^0.8.0",
"bcrypt": "^0.7.8",
"bunyan": "0.22.1",
"config": "^0.4.35",
"js-yaml": "^3.0.2",
"lodash": "~2.4.1",
"mongoose": "~3.8",
"mongoose-timestamp": "^0.2.1",
"moment": "2.5.1",
"restify": "~2.8"
},
"devDependencies": {
"chai": "1.9.0",
"chai-json-schema": "^1.1.0",
"coffee-script": "~1.6.3",
"gulp": "^3.6.2",
"gulp-coffee": "^1.4.3",
"gulp-mocha": "^0.4.1",
"gulp-nodemon": "^1.0.3",
"gulp-spawn": "^0.3.0",
"gulp-spawn-mocha": "^0.1.4",
"gulp-util": "^2.2.14",
"microtime": "^0.5.1",
"mocha": "~1.17.1",
"sinon": "^1.9.1",
"sinon-chai": "^2.5.0"
},
"scripts": {
"start": "node ./index.js",
"test": "mocha -R spec --compilers coffee:coffee-script test/*.coffee"
},
"repository": {
"type": "git"
}
}
59 changes: 59 additions & 0 deletions src/controllers/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
var mongoose = require('mongoose')
, restify = require('restify')
, _ = require('lodash')
, C = require('config').OAuth
, User = mongoose.model('User')
, AccessToken = mongoose.model('AccessToken')
;

// ### Register
exports.register = function (req, res, next) {

var user = new User( req.params );
user.validate( function( err ){
if( err ){ next( new restify.PreconditionFailedError );}

user.save( function(err, user) {
next.ifError(err);

AccessToken.create({ _userId: user._id }, function (err, token) {
next.ifError(err);
res.send(token);
});

});
});
};

// ### Login
exports.login = function (req, res, next) {

User.findOne( { email: req.params.login }, function( err, user ){
next.ifError( err );
if(_.isEmpty( user )){ return next( new restify.InvalidCredentialsError()); }

user.verifyPassword( req.params.password, function(err, isMatch ){
next.ifError( err );

if( !isMatch ){
return next( new restify.InvalidCredentialsError());
}

AccessToken.create({ _userId: user._id }, function( err, token ){
next.ifError( err );
res.send( token );
});

})
});
};

// ## Logout User and Delete Token
exports.logout = function( req, res, next ){
AccessToken.findByIdAndRemove( req.userToken._id , function( err ) {
next.ifError( err );
res.send( 204 );
return next();
});
};

64 changes: 64 additions & 0 deletions src/controllers/user-notes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
var mongoose = require( 'mongoose' )
, _ = require('lodash')
, CONFIG = require('config')
, async = require('async')
, UserNote = mongoose.model( 'UserNote' )
;

// ### Get Note by ID
exports.findOne = function( req, res, next ) {
UserNote.findById( req.params.id )
.exec( function( err, note ) {
next.ifError( err );
res.send( note );
return next();
});
};

// ### Add Note
exports.insert = function( req, res, next ) {
var note = new UserNote( req.body );
note.set('userId', req.userId);
note.save( function( err, note ) {
next.ifError( err );
res.send( 201, note );
return next();
});
};

// ### Update Note
exports.update = function( req, res, next ) {
UserNote.findByIdAndUpdate( req.params.id, { $set: req.body }, function( err, note ){
next.ifError( err );
res.send( 202, note );
return next();
});
};

// ### Delete Place
exports.remove = function( req, res, next ) {
UserNote.findByIdAndRemove( req.params.id , function( err ) {
next.ifError( err );
res.send( 204 );
return next();
} );
};

// ### Query Notes
exports.findAll = function( req, res, next ) {
UserNote
.aggregate()
.match( { userId: res.userId } )
.sort({ createdAt: -1 })
.project("_id")
.limit( 100 )
.exec( function( err, noteIds ){
next.ifError( err );
UserNote.find( { _id : { $in : _.pluck( noteIds, '_id') } })
.sort({ createdAt: -1 })
.exec( function( err, notes ){
res.send( notes );
return next();
});
});
};
Loading