diff --git a/bower.json b/bower.json index 73a8b52..fe8d017 100644 --- a/bower.json +++ b/bower.json @@ -27,6 +27,9 @@ "angular": "^1.5.6", "angular-ui-router": "^0.3.0", "Materialize": "materialize#^0.97.6", - "angular-resource": "^1.5.6" + "angular-resource": "^1.5.6", + "angular-materialize": "^0.1.8", + "materialize-colorpicker": "*", + "prism": "^1.5.1" } } diff --git a/features/auth/router/auth.js b/features/auth/router/auth.js index bf9cf7d..83bf752 100644 --- a/features/auth/router/auth.js +++ b/features/auth/router/auth.js @@ -1,3 +1,4 @@ +'use strict'; const Router = require('express').Router; module.exports = (User, strategy) => diff --git a/features/auth/strategies/local.js b/features/auth/strategies/local.js index c901d95..6170245 100644 --- a/features/auth/strategies/local.js +++ b/features/auth/strategies/local.js @@ -56,16 +56,20 @@ module.exports = class LocalAuthenticationStrategy { logIn(req, res, user); })(req, res, next); - this.signup = (req, res) => + this.signup = (req, res) => { + const createUser = new CreateUser(User); + new FindUser(User) .findOne({ username: req.body.username }) .then(user => { if (user) throw "Username exists"; return req.body; }) - .then(new CreateUser(User).create) + .then(createUser.create.bind(createUser)) + .then(UserDto.new) .then(logIn.bind(null, req, res)) - .catch(err => res.status(400).send(err)); + .catch(err => console.log(err) || res.status(400).send(err)); + }; } unauthenticated(req, res, next) { diff --git a/features/todo/model/todo.js b/features/todo/model/todo.js index 51efabf..e374f09 100644 --- a/features/todo/model/todo.js +++ b/features/todo/model/todo.js @@ -1,5 +1,4 @@ 'use strict'; - const mongoose = require('mongoose'); const Schema = mongoose.Schema; @@ -8,13 +7,13 @@ const TodoSchema = new Schema({ type: String, required: true }, - + desc: { type: String, required: false, default: '' }, - + color: { type: String, required: false, @@ -25,6 +24,11 @@ const TodoSchema = new Schema({ }, message: '{VALUE} is not a valid HEX color' } + }, + + owner: { + ref: 'User', + type: Schema.Types.ObjectId, } }); diff --git a/features/todo/router/todo.js b/features/todo/router/todo.js index 737d211..4b2b37e 100644 --- a/features/todo/router/todo.js +++ b/features/todo/router/todo.js @@ -1,3 +1,4 @@ +const _ = require('lodash'); const TodoDto = require('../model/dto'); const Router = require('express').Router; const FindTodo = require('../services/findTodo'); @@ -6,29 +7,29 @@ const UpdateTodo = require('../services/updateTodo'); const DeleteTodo = require('../services/deleteTodo'); const PromisedResponse = require('../../../utils/promisedResponse'); -module.exports = (Todo) => +module.exports = (Todo, strategy) => new Router() - .get('/', PromisedResponse(() => + .get('/', strategy.authenticated, PromisedResponse((req) => new FindTodo(Todo) - .find({ }) + .find({ owner: req.user._id }) .then(TodoDto.newList))) - - .get('/:id', PromisedResponse(req => + + .get('/:id', strategy.authenticated, PromisedResponse(req => new FindTodo(Todo) - .findOne({ _id: req.params.id }) + .findOne({ _id: req.params.id, owner: req.user._id }) .then(TodoDto.new))) - - .put('/:id', PromisedResponse(req => + + .put('/:id', strategy.authenticated, PromisedResponse(req => new UpdateTodo(Todo) .update(req.params.id, req.body) .then(TodoDto.new))) - - .post('/', PromisedResponse(req => + + .post('/', strategy.authenticated, PromisedResponse(req => new CreateTodo(Todo) - .create(req.body) + .create(_.assign({ owner: req.user._id }, req.body)) .then(TodoDto.new))) - - .delete('/:id', PromisedResponse(req => + + .delete('/:id', strategy.authenticated, PromisedResponse(req => new DeleteTodo(Todo) .delete(req.params.id) .then(TodoDto.new))); diff --git a/features/todo/services/createTodo.js b/features/todo/services/createTodo.js index a8f3f28..b080064 100644 --- a/features/todo/services/createTodo.js +++ b/features/todo/services/createTodo.js @@ -3,16 +3,18 @@ const Q = require('q'); const Joi = require('joi'); const validateSchema = require('../../../utils/validateSchema'); +const objectIdSchema = require('../../../utils/objectIdSchema'); const todoSchema = Joi.object().keys({ - name: Joi.string().required().label('todo.name') + name: Joi.string().required().label('todo.name'), + owner: objectIdSchema.required().label('todo.owner') }).label('todo'); module.exports = class CreateTodo { constructor(Todo) { Object.assign(this, { Todo }); } - + create(todo) { return Q.when(todo) .then(validateSchema(todoSchema, todo)) diff --git a/features/todo/services/findTodo.js b/features/todo/services/findTodo.js index 3fe359f..90fd29c 100644 --- a/features/todo/services/findTodo.js +++ b/features/todo/services/findTodo.js @@ -9,6 +9,7 @@ const predicateSchema = Joi.object().keys({ _id: objectIdSchema.optional().label('todo._id'), name: Joi.string().optional().label('todo.name'), desc: Joi.string().optional().label('todo.desc'), + owner: objectIdSchema.optional().label('todo.owner'), color: Joi.string().regex(/#[a-f0-9]{6}/i).optional().label('todo.color') }).required().label('predicate'); @@ -16,7 +17,7 @@ module.exports = class FindTodo { constructor(Todo) { Object.assign(this, { Todo }); } - + findOne(predicate) { return Q.when() .then(validateSchema(predicateSchema, predicate)) @@ -24,7 +25,7 @@ module.exports = class FindTodo { return Q.ninvoke(this.Todo, 'findOne', predicate); }); } - + find(predicate) { return Q.when() .then(validateSchema(predicateSchema, predicate)) diff --git a/features/user/router/user.js b/features/user/router/user.js index cfd2e60..6bc6f13 100644 --- a/features/user/router/user.js +++ b/features/user/router/user.js @@ -4,14 +4,14 @@ const FindUser = require('../services/findUser'); const UpdateUser = require('../services/updateUser'); const PromisedResponse = require('../../../utils/promisedResponse'); -module.exports = (User) => +module.exports = (User, strategy) => new Router() - .get('/:id', PromisedResponse(req => + .get('/:id', strategy.authenticated, PromisedResponse(req => new FindUser(User) .findOne({ _id: req.params.id }) .then(UserDto.new))) - .put('/:id', PromisedResponse(req => + .put('/:id', strategy.authenticated, PromisedResponse(req => new UpdateUser(User) .update(req.params.id, req.body) .then(UserDto.new))); diff --git a/package.json b/package.json index 8439746..a62f105 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "express-session": "^1.13.0", "http": "0.0.0", "joi": "^8.4.0", + "lodash": "^4.13.1", "md5": "^2.1.0", "mongoose": "^4.4.19", "passport": "^0.3.2", diff --git a/public/app.js b/public/app.js index 8e9f8ec..d7881da 100644 --- a/public/app.js +++ b/public/app.js @@ -1,6 +1,18 @@ - angular.module('todoApp', [ 'ui.router', - 'ngResource' - ]); \ No newline at end of file + 'ngResource', + 'ui.materialize' + ]).run(['$rootScope', 'Auth', '$state', AppRun]); + +function AppRun($rootScope, Auth, $state) { + $rootScope.$on('$stateChangeStart', function (event, toState, toStateParams) { + Auth.me(function (doc) { + if(toState.name == 'login'){ + $state.go('app.home') + } + }, function (err) { + $state.go('login') + }) + }) +} \ No newline at end of file diff --git a/public/app.state.js b/public/app.state.js index 650ff41..fc02567 100644 --- a/public/app.state.js +++ b/public/app.state.js @@ -8,7 +8,7 @@ function AppStateConfig($stateProvider, $urlRouterProvider){ views : { 'header' : { templateUrl : 'layout/header.html', - controller : 'HeaderController' + controller : 'HeaderController as ctrl' }, 'content' : { template : '
' @@ -18,7 +18,8 @@ function AppStateConfig($stateProvider, $urlRouterProvider){ $urlRouterProvider.otherwise(function ($injector, $location) { var $state = $injector.get('$state'); - $state.go('app.home'); }); + + } \ No newline at end of file diff --git a/public/factory/auth.factory.js b/public/factory/auth.factory.js new file mode 100644 index 0000000..9ac227c --- /dev/null +++ b/public/factory/auth.factory.js @@ -0,0 +1,24 @@ +angular.module('todoApp') + .factory('Auth', ['$resource', AuthFactory]); + +function AuthFactory($resource) { + return $resource('/auth', {}, + { + signup: { + method : 'POST', + url : '/auth/signup' + }, + me : { + method: 'GET', + url : '/auth/me' + }, + signin : { + method: 'POST', + url : '/auth/signin', + }, + signout: { + method: 'POST', + url: '/auth/signout' + } + }); +} \ No newline at end of file diff --git a/public/factory/todo.colors.js b/public/factory/todo.colors.js deleted file mode 100644 index a6a3da0..0000000 --- a/public/factory/todo.colors.js +++ /dev/null @@ -1,20 +0,0 @@ -angular.module('todoApp') - .value('Colors', - [ - { - name: 'Blue', - colorCode: '#0000FF' - }, - { - name: 'Red', - colorCode: '#FF0000' - }, - { - name: 'Yellow', - colorCode: '#FFFF00' - }, - { - name: 'Blue Grey', - colorCode: '#607D8B' - }, - ]); \ No newline at end of file diff --git a/public/factory/user.factory.js b/public/factory/user.factory.js new file mode 100644 index 0000000..1a4d295 --- /dev/null +++ b/public/factory/user.factory.js @@ -0,0 +1,6 @@ +angular.module('todoApp') + .factory('User', ['$resource', User]); + +function User($resource){ + return $resource('/api/user/:id'); +} \ No newline at end of file diff --git a/public/index.html b/public/index.html index da402f4..b060c9e 100644 --- a/public/index.html +++ b/public/index.html @@ -4,8 +4,11 @@ todo app - + + + + @@ -17,25 +20,38 @@ - - + + + + + + + + + + + + + + - + - + + \ No newline at end of file diff --git a/public/layout/header.controller.js b/public/layout/header.controller.js index eebe385..7aec0db 100644 --- a/public/layout/header.controller.js +++ b/public/layout/header.controller.js @@ -1,12 +1,21 @@ angular.module('todoApp') -.controller('HeaderController', ['$scope', HeaderController]); +.controller('HeaderController', ['$scope', 'Auth', '$state', HeaderController]); -function HeaderController($scope){ +function HeaderController($scope, Auth, $state){ var self = this; self.showSideNav = showSideNav; - - $(".button-collapse").sideNav(); + self.logout = logout; + + showSideNav(); + + function logout() { + Auth.signout({}, function(doc){ + }, function(){ + $state.go('login'); + }) + } function showSideNav(){ + $(".button-collapse").sideNav(); } } \ No newline at end of file diff --git a/public/layout/header.html b/public/layout/header.html index 27b4735..bca765f 100644 --- a/public/layout/header.html +++ b/public/layout/header.html @@ -6,6 +6,9 @@
  • Home
  • Tasks to Do
  • + @@ -26,5 +27,9 @@

    TODO's List

    + - \ No newline at end of file + + + + diff --git a/public/sections/todo/todo.state.js b/public/sections/todo/todo.state.js index 8db6fbb..4566e19 100644 --- a/public/sections/todo/todo.state.js +++ b/public/sections/todo/todo.state.js @@ -8,7 +8,6 @@ function TodoStateConfig($stateProvider) { parent : 'app', templateUrl : 'sections/todo/todo.html', controller : 'TodoController as ctrl' - }); } \ No newline at end of file diff --git a/public/styles/todo.css b/public/styles/todo.css index 76df58f..b09db62 100644 --- a/public/styles/todo.css +++ b/public/styles/todo.css @@ -6,6 +6,9 @@ padding-top: 5px; } +.modal-editcolor-content{ + height: 200px +} .modal-editcolor{ - width : 300px; + width: 300px } \ No newline at end of file diff --git a/public/utils.js b/public/utils.js new file mode 100644 index 0000000..36d01c0 --- /dev/null +++ b/public/utils.js @@ -0,0 +1,3 @@ +function handleError(err) { + Materialize.toast('There was an Error: ' + err.data, 4000); +} \ No newline at end of file diff --git a/specs/todo/createTodo-spec.js b/specs/todo/createTodo-spec.js index ec80a7f..f9c74e5 100644 --- a/specs/todo/createTodo-spec.js +++ b/specs/todo/createTodo-spec.js @@ -1,49 +1,58 @@ const chai = require('chai'); const DatabaseMock = require('../helpers/databaseMock'); const Todo = require('../../features/todo/model/todo'); +//const User = require('../../features/user/model/user'); const CreateTodo = require('../../features/todo/services/createTodo'); describe('create todo', () => { const db = new DatabaseMock(); - + before(() => { chai.should(); db.connect(); }); - + after((done) => { db.clearDb(function() { db.disconnect(); done(); }); }); - + it('should ask for the dependencies it needs', () => { CreateTodo.length.should.equal(1); (() => new CreateTodo(Todo)).should.not.throw(); }); - - it('should gracefully fail if required fields are not provided', () => { + + it('should gracefully fail if name is not provided', () => { return new CreateTodo(Todo) - .create({ }) + .create({ owner: new Array(24).fill('0').join('') }) .catch(err => { err.message.should.contain('"todo.name" is required'); }); }); - + + it('should gracefully fail if owner is not provided', () => { + return new CreateTodo(Todo) + .create({ name: 'Some name' }) + .catch(err => { + err.message.should.contain('"todo.owner" is required'); + }); + }); + it('should gracefully fail if required fields are invalid', () => { return new CreateTodo(Todo) - .create({ name: 5 }) + .create({ name: 5, owner: '' }) .catch(err => { err.message.should.contain('"todo.name" must be a string'); }); }); - + it('should perform the creation when .create is called', () => { const name = 'Write a book'; - + return new CreateTodo(Todo) - .create({ name }) + .create({ name, owner: new Array(24).fill('0').join('') }) .then(todo => { todo._id.should.not.equal(undefined); todo.name.should.equal(name); diff --git a/specs/todo/deleteTodo-spec.js b/specs/todo/deleteTodo-spec.js index 17d3bf6..29c4c8e 100644 --- a/specs/todo/deleteTodo-spec.js +++ b/specs/todo/deleteTodo-spec.js @@ -6,34 +6,34 @@ const CreateTodo = require('../../features/todo/services/createTodo'); describe('delete todo', () => { let todo = {}; - + const db = new DatabaseMock(); - + before((done) => { chai.should(); db.connect(); - + const createTodo =new CreateTodo(Todo); - - createTodo.create({ name: "test" }) + + createTodo.create({ name: "test", owner: new Array(24).fill(0).join('') }) .then(newTodo => { todo = newTodo; done(); }); }); - + after((done) => { db.clearDb(function() { db.disconnect(); done(); }); }); - + it('should ask for the dependencies it needs', () => { DeleteTodo.length.should.equal(1); (() => new DeleteTodo(Todo)).should.not.throw(); }); - + it('should gracefully fail if invalid todo_id is provided', () => { return new DeleteTodo(Todo) .delete(15) @@ -41,17 +41,17 @@ describe('delete todo', () => { err.message.should.contain('"todo._id" must be a string'); }); }); - + it('should gracefully fail if non 24-byte todo_id is provided', () => { const oId = new Array(23).fill("0").join(''); - + return new DeleteTodo(Todo) .delete(oId) .catch(err => { err.message.should.contain('"todo._id" length must be 24 characters long'); }); }); - + it('should gracefully fail if todo_id is not provided', () => { return new DeleteTodo(Todo) .delete() @@ -59,7 +59,7 @@ describe('delete todo', () => { err.message.should.contain('"todo._id" is required'); }); }); - + it('should delete a todo if .delete is called', () => { return new DeleteTodo(Todo) .delete(todo._id) diff --git a/specs/todo/findTodo-spec.js b/specs/todo/findTodo-spec.js index a97ff85..ca4c056 100644 --- a/specs/todo/findTodo-spec.js +++ b/specs/todo/findTodo-spec.js @@ -9,37 +9,37 @@ describe('find todo', () => { let firstTodo = {}; let secondTodo = {}; let should; - + const db = new DatabaseMock(); - + before((done) => { should = chai.should(); db.connect(); - + const createTodo =new CreateTodo(Todo); - - createTodo.create({ name: 'first!' }) + + createTodo.create({ name: 'first!', owner: new Array(24).fill(0).join('') }) .then(newTodo => { - return createTodo.create({ name: 'second!' }) + return createTodo.create({ name: 'second!', owner: new Array(24).fill(0).join('') }) .then(newerTodo => { _todos = [firstTodo, secondTodo] = [newTodo, newerTodo]; done(); }); }); }); - + after((done) => { db.clearDb(function() { db.disconnect(); done(); }); }); - + it('should ask for the dependencies it needs', () => { FindTodo.length.should.equal(1); (() => new FindTodo(Todo)).should.not.throw(); }); - + describe('findOne', () => { it('should gracefully fail if invalid predicate is provided', () => { return new FindTodo(Todo) @@ -48,7 +48,7 @@ describe('find todo', () => { err.message.should.contain('"predicate" is required'); }); }); - + it('should retrieve first todo if empty predicate is provided', () => { return new FindTodo(Todo) .findOne({ }) @@ -56,7 +56,7 @@ describe('find todo', () => { String(todo._id).should.equal(String(firstTodo._id)); }); }); - + it('should retrieve first todo that matches a given predicate', () => { return new FindTodo(Todo) .findOne({ name: secondTodo.name }) @@ -64,7 +64,7 @@ describe('find todo', () => { String(todo._id).should.equal(String(secondTodo._id)); }); }); - + it('should return null if nothing was found', () => { return new FindTodo(Todo) .findOne({ name: 'nope' }) @@ -73,7 +73,7 @@ describe('find todo', () => { }); }); }); - + describe('find', () => { it('should gracefully fail if invalid predicate is provided', () => { return new FindTodo(Todo) @@ -82,7 +82,7 @@ describe('find todo', () => { err.message.should.contain('"predicate" is required'); }); }); - + it('should retrieve all todos if empty predicate is provided', () => { return new FindTodo(Todo) .find({ }) @@ -90,7 +90,7 @@ describe('find todo', () => { todos.length.should.equal(_todos.length); }); }); - + it('should retrieve all todos matching a given predicate', () => { return new FindTodo(Todo) .find({ name: firstTodo.name }) @@ -98,7 +98,7 @@ describe('find todo', () => { todos.length.should.equal(1); }); }); - + it('should return an empty object if nothing was found', () => { return new FindTodo(Todo) .find({ name: 'nope' }) diff --git a/specs/todo/updateTodo-spec.js b/specs/todo/updateTodo-spec.js index f43ca28..9cb4b0d 100644 --- a/specs/todo/updateTodo-spec.js +++ b/specs/todo/updateTodo-spec.js @@ -6,34 +6,34 @@ const CreateTodo = require('../../features/todo/services/createTodo'); describe('update todo', () => { let todo = {}; - + const db = new DatabaseMock(); - + before((done) => { chai.should(); db.connect(); - + const createTodo =new CreateTodo(Todo); - - createTodo.create({ name: "new todo" }) + + createTodo.create({ name: "new todo", owner: new Array(24).fill(0).join('') }) .then(newTodo => { todo = newTodo; done(); }); }); - + after((done) => { db.clearDb(function() { db.disconnect(); done(); }); }); - + it('should ask for the dependencies it needs', () => { UpdateTodo.length.should.equal(1); (() => new UpdateTodo(Todo)).should.not.throw(); }); - + it('should fail gracefully if todo_id is not provided', () => { return new UpdateTodo(Todo) .update(undefined, { }) @@ -41,17 +41,17 @@ describe('update todo', () => { err.message.should.contain('"todo._id" is required'); }); }); - + it('should fail gracefully if invalid todo_id is provided', () => { const oid = new Array(23).fill("0").join(''); - + return new UpdateTodo(Todo) .update(oid, { }) .catch(err => { err.message.should.contain('"todo._id" length must be 24 characters long'); }); }); - + it('should fail gracefully if nothing is prrovided', () => { return new UpdateTodo(Todo) .update(todo._id, { }) @@ -59,10 +59,10 @@ describe('update todo', () => { err.message.should.contain('"todo" must have at least 1 children'); }); }); - + it('should update todo if .update is called', () => { const newTodo = { name: 'new name', desc: 'new desc', color: '#000000' }; - + return new UpdateTodo(Todo) .update(todo._id, newTodo) .then(updatedTodo => {