require authentication for todos,

use in-memody db for tests
This commit is contained in:
2018-05-30 21:42:30 +03:00
parent fd142c8710
commit db46f1a2b4
16 changed files with 601 additions and 118 deletions

View File

@@ -2,4 +2,5 @@
"editor.tabSize": 2,
"prettier.eslintIntegration": true,
"editor.insertSpaces": true,
"jest.pathToJest": "npm test --"
}

15
app.js
View File

@@ -6,24 +6,27 @@ const cors = require('cors');
const config = require('./config');
const db = require('./config/db');
require('./models/TodoList');
require('./models/User');
require('./models/Todo');
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(cors());
app.use(morgan('dev'));
require('./models/User');
require('./models/TodoList');
require('./models/Todo');
const passport = require('./config/passport');
app.use(passport.initialize());
app.use('/lists', require('./routes/lists'));
app.use('/todos', require('./routes/todos'));
app.use('/users', require('./routes/users'));
const auth = require('./routes/auth');
app.use('/lists', auth.required, require('./routes/lists'));
app.use('/todos', auth.required, require('./routes/todos'));
// 404 route
app.use((req, res) => {
res.status(404);

View File

@@ -1,10 +1,10 @@
const mongoose = require('mongoose');
const config = require('./');
async function connect() {
const { host, port, name } = config.db;
const connectionString = `mongodb://${host}:${port}/${name}`;
async function connect() {
await mongoose.connect(connectionString);
}

View File

@@ -11,15 +11,8 @@ const dev = {
},
secret: process.env.DEV_SECRET || 'devsecret',
};
const test = {
app: {
port: process.env.TEST_APP_PORT || 4001,
},
db: {
host: process.env.TEST_DB_HOST || 'localhost',
port: process.env.TEST_DB_PORT || 27017,
name: process.env.TEST_DB_NAME || 'todolistTest',
},
secret: process.env.TEST_SECRET || 'testsecret',
};

View File

@@ -1,6 +1,5 @@
const mongoose = require('mongoose');
const TodoList = mongoose.model('TodoList');
const { Schema } = mongoose;
const TodoSchema = Schema({
@@ -9,17 +8,26 @@ const TodoSchema = Schema({
required: true,
},
list: { type: Schema.Types.ObjectId, ref: 'TodoList', required: true },
user: { type: Schema.Types.ObjectId, ref: 'User', required: true },
completed: { type: Boolean, default: false },
});
TodoSchema.pre('save', async function () {
const list = await TodoList.findById(this.list);
const user = await this.model('User').findById(this.user);
user.todos.push(this._id);
await user.save();
const list = await this.model('TodoList').findById(this.list);
list.todos.push(this._id);
await list.save();
});
TodoSchema.pre('remove', async function () {
const list = await TodoList.findById(this.list);
const user = await this.model('User').findById(this.user);
user.todos.splice(user.todos.indexOf(this._id), 1);
await user.save();
const list = await this.model('TodoList').findById(this.list);
list.todos.splice(list.todos.indexOf(this._id), 1);
await list.save();
});
@@ -29,6 +37,7 @@ TodoSchema.methods.toJson = function () {
id: this._id.toString(),
text: this.text,
list: this.list.toString(),
user: this.user.toString(),
completed: this.completed,
};
};

View File

@@ -8,18 +8,27 @@ const TodoListSchema = Schema({
required: true,
},
todos: [{ type: Schema.Types.ObjectId, ref: 'Todo' }],
user: { type: Schema.Types.ObjectId, ref: 'User', required: true },
});
TodoListSchema.pre('save', async function () {
const user = await this.model('User').findById(this.user);
user.lists.push(this._id);
await user.save();
});
TodoListSchema.pre('remove', async function () {
this.todos.forEach(async (todo) => {
await todo.remove();
});
const user = await this.model('User').findById(this.user);
user.lists.splice(user.todos.indexOf(this._id), 1);
await user.save();
await this.model('Todo').remove({ list: this._id });
});
TodoListSchema.methods.toJson = function () {
const todos = this.populated('todos') ? this.todos.map(todo => todo.toJson()) : this.todos;
return {
id: this._id.toString(),
user: this.user.toString(),
name: this.name,
todos,
};

View File

@@ -1,17 +1,33 @@
const mongoose = require('mongoose');
const passportLocalMongoose = require('passport-local-mongoose');
const jwt = require('jsonwebtoken');
const uniqueValidator = require('mongoose-unique-validator');
const { secret } = require('../config');
const { Schema } = mongoose;
const UserSchema = Schema({ username: { type: String, required: true } });
const UserSchema = Schema({
username: {
type: String,
required: true,
unique: true,
validate: /^\S*$/,
},
lists: [{ type: Schema.Types.ObjectId, ref: 'TodoList' }],
todos: [{ type: Schema.Types.ObjectId, ref: 'Todo' }],
});
UserSchema.plugin(passportLocalMongoose);
UserSchema.plugin(uniqueValidator);
UserSchema.pre('remove', async function () {
await this.model('TodoList').remove({ user: this._id });
await this.model('Todo').remove({ user: this._id });
});
UserSchema.methods.generateJwt = function () {
return jwt.sign({ id: this._id, username: this.username }, secret, { expiresIn: '1y' });
return jwt.sign({ id: this._id, username: this.username }, secret, { expiresIn: '6m' });
};
UserSchema.methods.toAuthJson = function () {

394
package-lock.json generated
View File

@@ -1033,6 +1033,12 @@
}
}
},
"base64-js": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
"integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=",
"dev": true
},
"basic-auth": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz",
@@ -1057,6 +1063,16 @@
"integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=",
"dev": true
},
"bl": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"dev": true,
"requires": {
"readable-stream": "^2.3.5",
"safe-buffer": "^5.1.1"
}
},
"bluebird": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz",
@@ -1169,11 +1185,50 @@
"resolved": "https://registry.npmjs.org/bson/-/bson-1.0.6.tgz",
"integrity": "sha512-D8zmlb46xfuK2gGvKmUjIklQEouN2nQ0LEHHeZ/NoHM2LDiMk2EYzZ5Ntw/Urk+bgMDosOZxaRzXxvhI5TcAVQ=="
},
"buffer": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-3.6.0.tgz",
"integrity": "sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=",
"dev": true,
"requires": {
"base64-js": "0.0.8",
"ieee754": "^1.1.4",
"isarray": "^1.0.0"
}
},
"buffer-alloc": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
"integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
"dev": true,
"requires": {
"buffer-alloc-unsafe": "^1.1.0",
"buffer-fill": "^1.0.0"
}
},
"buffer-alloc-unsafe": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
"dev": true
},
"buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
"dev": true
},
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
},
"buffer-fill": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
"dev": true
},
"buffer-from": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz",
@@ -1554,6 +1609,15 @@
"delayed-stream": "~1.0.0"
}
},
"commander": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
"integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
"dev": true,
"requires": {
"graceful-readlink": ">= 1.0.0"
}
},
"compare-versions": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.2.1.tgz",
@@ -1764,6 +1828,95 @@
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"dev": true
},
"decompress": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.0.tgz",
"integrity": "sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=",
"dev": true,
"requires": {
"decompress-tar": "^4.0.0",
"decompress-tarbz2": "^4.0.0",
"decompress-targz": "^4.0.0",
"decompress-unzip": "^4.0.1",
"graceful-fs": "^4.1.10",
"make-dir": "^1.0.0",
"pify": "^2.3.0",
"strip-dirs": "^2.0.0"
}
},
"decompress-tar": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz",
"integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==",
"dev": true,
"requires": {
"file-type": "^5.2.0",
"is-stream": "^1.1.0",
"tar-stream": "^1.5.2"
}
},
"decompress-tarbz2": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz",
"integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==",
"dev": true,
"requires": {
"decompress-tar": "^4.1.0",
"file-type": "^6.1.0",
"is-stream": "^1.1.0",
"seek-bzip": "^1.0.5",
"unbzip2-stream": "^1.0.9"
},
"dependencies": {
"file-type": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz",
"integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==",
"dev": true
}
}
},
"decompress-targz": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz",
"integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==",
"dev": true,
"requires": {
"decompress-tar": "^4.1.1",
"file-type": "^5.2.0",
"is-stream": "^1.1.0"
}
},
"decompress-unzip": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz",
"integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=",
"dev": true,
"requires": {
"file-type": "^3.8.0",
"get-stream": "^2.2.0",
"pify": "^2.3.0",
"yauzl": "^2.4.2"
},
"dependencies": {
"file-type": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=",
"dev": true
},
"get-stream": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
"integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
"dev": true,
"requires": {
"object-assign": "^4.0.1",
"pinkie-promise": "^2.0.0"
}
}
}
},
"deep-extend": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz",
@@ -1983,6 +2136,15 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"end-of-stream": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
"integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
"dev": true,
"requires": {
"once": "^1.4.0"
}
},
"error-ex": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
@@ -2574,6 +2736,15 @@
"bser": "^2.0.0"
}
},
"fd-slicer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
"integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
"dev": true,
"requires": {
"pend": "~1.2.0"
}
},
"figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
@@ -2593,6 +2764,12 @@
"object-assign": "^4.0.1"
}
},
"file-type": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
"integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=",
"dev": true
},
"filename-regex": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
@@ -2734,6 +2911,23 @@
"integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=",
"dev": true
},
"fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"dev": true
},
"fs-extra": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
"integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -3292,6 +3486,12 @@
"integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=",
"dev": true
},
"get-port": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
"integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=",
"dev": true
},
"get-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
@@ -3304,6 +3504,26 @@
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
"dev": true
},
"getos": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/getos/-/getos-3.1.0.tgz",
"integrity": "sha512-i9vrxtDu5DlLVFcrbqUqGWYlZN/zZ4pGMICCAcZoYsX3JA54nYp8r5EThw5K+m2q3wszkx4Th746JstspB0H4Q==",
"dev": true,
"requires": {
"async": "2.4.0"
},
"dependencies": {
"async": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/async/-/async-2.4.0.tgz",
"integrity": "sha1-SZAgDxjqW4N8LMT4wDGmmFw4VhE=",
"dev": true,
"requires": {
"lodash": "^4.14.0"
}
}
}
},
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
@@ -3400,6 +3620,12 @@
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
"dev": true
},
"graceful-readlink": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
"dev": true
},
"growly": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
@@ -3608,6 +3834,12 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
"ieee754": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.11.tgz",
"integrity": "sha512-VhDzCKN7K8ufStx/CLj5/PDTMgph+qwN5Pkd5i0sGnVwk56zJ0lkT8Qzi1xqWLS0Wp29DgDtNeS7v8/wMoZeHg==",
"dev": true
},
"ignore": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.8.tgz",
@@ -3880,6 +4112,12 @@
"is-path-inside": "^1.0.0"
}
},
"is-natural-number": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz",
"integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=",
"dev": true
},
"is-npm": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz",
@@ -4653,6 +4891,15 @@
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
"dev": true
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6"
}
},
"jsonify": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
@@ -4800,11 +5047,25 @@
}
}
},
"lockfile": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz",
"integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==",
"dev": true,
"requires": {
"signal-exit": "^3.0.2"
}
},
"lodash": {
"version": "4.17.10",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
},
"lodash.foreach": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
"integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM="
},
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
@@ -4940,6 +5201,15 @@
"integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=",
"dev": true
},
"md5-file": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/md5-file/-/md5-file-3.2.3.tgz",
"integrity": "sha512-3Tkp1piAHaworfcCgH0jKbTvj1jWWFgbvh2cXaNCgHwyTCBxxvD1Y04rmfpvdPm1P4oXMOpm6+2H7sr7v9v8Fw==",
"dev": true,
"requires": {
"buffer-alloc": "^1.1.0"
}
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -5086,6 +5356,38 @@
"require_optional": "^1.0.1"
}
},
"mongodb-memory-server": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-1.7.4.tgz",
"integrity": "sha512-8CNbBV80cBradJEuyAl+Vw45Wf0MJYTplDphGE9z3qqaQVRYG25PhZ2Uk+kyVVF6HqF4obe+meCMpoOoeXwJ8w==",
"dev": true,
"requires": {
"babel-runtime": "^6.26.0",
"debug": "^3.1.0",
"decompress": "^4.2.0",
"fs-extra": "^5.0.0",
"get-port": "^3.2.0",
"getos": "^3.1.0",
"lockfile": "^1.0.4",
"md5-file": "^3.2.3",
"mkdirp": "^0.5.1",
"request": "^2.85.0",
"request-promise": "^4.2.2",
"tmp": "^0.0.33",
"uuid": "^3.2.1"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
}
}
},
"mongoose": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.1.2.tgz",
@@ -5116,6 +5418,15 @@
"resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz",
"integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ=="
},
"mongoose-unique-validator": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mongoose-unique-validator/-/mongoose-unique-validator-2.0.1.tgz",
"integrity": "sha512-Eqq7lZMy0nPSojG8UyDZvlBie1aBZJXk68GDMBXXQH0TAi0hZHf76nCrwuipReNK1jLkjyKzV7eIZotja5eEBw==",
"requires": {
"lodash.foreach": "^4.1.0",
"lodash.get": "^4.0.2"
}
},
"morgan": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz",
@@ -5697,6 +6008,12 @@
"through": "~2.3"
}
},
"pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
"dev": true
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@@ -6098,6 +6415,18 @@
"uuid": "^3.1.0"
}
},
"request-promise": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz",
"integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=",
"dev": true,
"requires": {
"bluebird": "^3.5.0",
"request-promise-core": "1.1.1",
"stealthy-require": "^1.1.0",
"tough-cookie": ">=2.3.3"
}
},
"request-promise-core": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz",
@@ -6588,6 +6917,15 @@
"resolved": "https://registry.npmjs.org/scmp/-/scmp-2.0.0.tgz",
"integrity": "sha1-JHEQ7yLM+JexOj8KvdtSeCOTzWo="
},
"seek-bzip": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz",
"integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=",
"dev": true,
"requires": {
"commander": "~2.8.1"
}
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
@@ -7058,6 +7396,15 @@
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
"dev": true
},
"strip-dirs": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz",
"integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==",
"dev": true,
"requires": {
"is-natural-number": "^4.0.1"
}
},
"strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
@@ -7135,6 +7482,21 @@
"string-width": "^2.1.1"
}
},
"tar-stream": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.1.tgz",
"integrity": "sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==",
"dev": true,
"requires": {
"bl": "^1.0.0",
"buffer-alloc": "^1.1.0",
"end-of-stream": "^1.0.0",
"fs-constants": "^1.0.0",
"readable-stream": "^2.3.0",
"to-buffer": "^1.1.0",
"xtend": "^4.0.0"
}
},
"term-size": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz",
@@ -7526,6 +7888,12 @@
"integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=",
"dev": true
},
"to-buffer": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz",
"integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==",
"dev": true
},
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -7689,6 +8057,16 @@
"dev": true,
"optional": true
},
"unbzip2-stream": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz",
"integrity": "sha512-izD3jxT8xkzwtXRUZjtmRwKnZoeECrfZ8ra/ketwOcusbZEp4mjULMnJOCfTDZBgGQAAY1AJ/IgxcwkavcX9Og==",
"dev": true,
"requires": {
"buffer": "^3.0.1",
"through": "^2.3.6"
}
},
"undefsafe": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz",
@@ -7742,6 +8120,12 @@
"crypto-random-string": "^1.0.0"
}
},
"universalify": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz",
"integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=",
"dev": true
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -8185,6 +8569,16 @@
"dev": true
}
}
},
"yauzl": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.1.tgz",
"integrity": "sha1-qBmB6nCleUYTOIPwKcWCGok1mn8=",
"dev": true,
"requires": {
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.0.1"
}
}
}
}

View File

@@ -18,6 +18,7 @@
"express-jwt": "^5.3.1",
"jsonwebtoken": "^8.2.1",
"mongoose": "^5.1.1",
"mongoose-unique-validator": "^2.0.1",
"morgan": "^1.9.0",
"passport": "^0.4.0",
"passport-local": "^1.0.0",
@@ -30,6 +31,7 @@
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-jest": "^21.15.2",
"jest": "^22.4.4",
"mongodb-memory-server": "^1.7.4",
"nodemon": "^1.17.5",
"supertest": "^3.1.0"
},

View File

@@ -6,12 +6,14 @@ const router = express.Router();
const TodoList = mongoose.model('TodoList');
const asyncHelper = require('../asyncHelper');
const auth = require('./auth');
const { NotFoundError } = require('../errors');
// index
router.get(
'/',
asyncHelper(async (req, res) => {
const lists = await TodoList.find({})
const lists = await TodoList.find({ user: req.user.id })
.populate('todos')
.exec();
res.json({ success: true, data: lists.map(list => list.toJson()) });
@@ -23,7 +25,7 @@ router.post(
'/',
asyncHelper(async (req, res) => {
const { name } = req.body;
const newList = new TodoList({ name });
const newList = new TodoList({ name, user: req.user.id });
await newList.save();
res.json({ success: true, data: newList.toJson() });
}),
@@ -34,7 +36,7 @@ router.delete(
'/:listId',
asyncHelper(async (req, res) => {
const { listId } = req.params;
const list = await TodoList.findById(listId)
const list = await TodoList.find({ _id: listId, user: req.user.id })
.populate('todos')
.exec();
await list.remove();
@@ -48,15 +50,13 @@ router.patch(
asyncHelper(async (req, res) => {
const { listId } = req.params;
const { name } = req.body;
const patch = {};
if (name !== undefined) {
patch.name = name;
const list = await TodoList.find({ _id: listId, user: req.user.id });
if (!list) {
throw new NotFoundError("can't find list");
}
if (name !== undefined) {
list.name = name;
}
const list = await TodoList.findByIdAndUpdate(
{ _id: listId },
{ $set: patch },
{ new: true },
).exec();
await list.save();
res.json({ success: true, data: list.toJson() });
}),

View File

@@ -13,7 +13,7 @@ router.get(
'/',
asyncHelper(async (req, res) => {
const { listId } = res.locals || req.body;
const todos = await Todo.find({ list: listId }).exec();
const todos = await Todo.find({ list: listId, user: req.user.id }).exec();
res.json({ success: true, data: todos.map(todo => todo.toJson()) });
}),
);
@@ -24,7 +24,7 @@ router.post(
asyncHelper(async (req, res) => {
const { listId } = res.locals || req.body;
const { text } = req.body;
const todo = new Todo({ text, list: listId });
const todo = new Todo({ text, list: listId, user: req.user.id });
await todo.save();
res.json({ success: true, data: todo.toJson() });
}),
@@ -36,16 +36,15 @@ router.patch(
asyncHelper(async (req, res) => {
const { todoId } = req.params;
const { text, completed } = req.body;
const patch = {};
const todo = await Todo.find({ _id: todoId, user: req.user.id });
if (!todo) {
throw new NotFoundError("can't find todo");
}
if (text !== undefined) {
patch.text = text;
todo.text = text;
}
if (completed !== undefined) {
patch.completed = completed;
}
const todo = await Todo.findByIdAndUpdate(todoId, { $set: patch }, { new: true }).exec();
if (!todo) {
throw new NotFoundError(`can't find todo with id ${todoId}`);
todo.completed = completed;
}
res.json({ success: true, data: todo.toJson() });
}),
@@ -56,7 +55,7 @@ router.delete(
'/:todoId',
asyncHelper(async (req, res) => {
const { todoId } = req.params;
const todo = await Todo.findById(todoId).exec();
const todo = await Todo.find({ _id: todoId, user: req.user.id }).exec();
if (!todo) {
throw new NotFoundError(`can't find todo with id ${todoId}`);
}

View File

@@ -31,7 +31,11 @@ router.patch(
if (username !== undefined) {
patch.username = username;
}
const user = await User.findByIdAndUpdate(req.user.id, { $set: patch }, { new: true }).exec();
const user = await User.findOneAndUpdate(
{ _id: req.user.id },
{ $set: patch },
{ runValidators: true, context: 'query', new: true },
).exec();
if (!user) {
throw new NotFoundError(`can't find user with username ${req.user.username}`);
}

View File

@@ -3,65 +3,57 @@ const server = require('../../app.js');
const request = require('supertest');
const mongoose = require('mongoose');
const db = require('../../config/db');
const TodoList = mongoose.model('TodoList');
const Todo = mongoose.model('Todo');
const TodoList = mongoose.model('TodoList');
const User = mongoose.model('User');
let lists;
let listsPopulated;
let todos;
jest.setTimeout(60000);
const MongoDBMemoryServer = require('mongodb-memory-server').default;
const { seed, clean } = require('./utils');
let user;
let token;
let list;
let todo;
let mongoServer;
beforeAll(async () => {
mongoServer = new MongoDBMemoryServer();
const mongoUri = await mongoServer.getConnectionString();
await mongoose.connect(mongoUri);
});
beforeEach(async () => {
await db.connect();
// seed lists and todos
const list1 = new TodoList({ name: 'List1' });
const todo1 = new Todo({ text: 'Todo1', list: list1._id });
const todo2 = new Todo({ text: 'Todo2', list: list1._id });
await list1.save();
await todo1.save();
await todo2.save();
lists = await TodoList.find({}).exec();
listsPopulated = await TodoList.find({})
.populate('todos')
.exec();
todos = await Todo.find({}).exec();
({
user, token, list, todo,
} = await seed());
});
afterEach(async () => {
await TodoList.remove({}).exec();
await Todo.remove({}).exec();
await db.disconnect();
await clean();
});
afterAll(async () => {
await mongoose.disconnect();
await mongoServer.stop();
await server.close();
});
describe('test lists', () => {
test('should index lists', async () => {
const response = await request(server)
test('should not index lists without authentication', async () => {
await request(server)
.get('/lists')
.set('Accept', 'application/json')
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8');
expect(response.body.success).toBe(true);
expect(response.body.data).toBeInstanceOf(Array);
expect(response.body.data).toEqual(listsPopulated.map(list => list.toJson()));
.expect(401);
});
test('should create list', async () => {
const response = await request(server)
test('should not create list without authentication', async () => {
await request(server)
.post('/lists')
.send({
name: 'List2',
})
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8');
expect(response.body.success).toBeTruthy();
expect(await TodoList.findOne({ name: 'List2' })).toBeTruthy();
.expect(401);
});
});

View File

@@ -3,26 +3,43 @@ const server = require('../../app.js');
const request = require('supertest');
const mongoose = require('mongoose');
const Todo = require('../../models/Todo');
const TodoList = require('../../models/TodoList');
const Todo = mongoose.model('Todo');
const TodoList = mongoose.model('TodoList');
const User = mongoose.model('User');
const db = require('../../config/db');
jest.setTimeout(60000);
const MongoDBMemoryServer = require('mongodb-memory-server').default;
const { seed, clean } = require('./utils');
let user;
let token;
let list;
let todo;
let mongoServer;
beforeAll(async () => {
mongoServer = new MongoDBMemoryServer();
const mongoUri = await mongoServer.getConnectionString();
await mongoose.connect(mongoUri);
});
beforeEach(async () => {
await db.connect();
({
user, token, list, todo,
} = await seed());
});
afterEach(async () => {
await db.disconnect();
await clean();
});
afterAll(async () => {
await TodoList.remove({}).exec();
await Todo.remove({}).exec();
await mongoose.disconnect();
await mongoServer.stop();
await server.close();
});
describe('Test not found', () => {
describe('test not found', () => {
test('respond not found with json', async () => {
const response = await request(server)
.get('/')

View File

@@ -4,29 +4,40 @@ const request = require('supertest');
const mongoose = require('mongoose');
const jwt = require('jsonwebtoken');
const db = require('../../config/db');
const { secret } = require('../../config');
const Todo = mongoose.model('Todo');
const TodoList = mongoose.model('TodoList');
const User = mongoose.model('User');
jest.setTimeout(60000);
const MongoDBMemoryServer = require('mongodb-memory-server').default;
const { seed, clean } = require('./utils');
const { secret } = require('../../config');
let user;
let token;
let list;
let todo;
let mongoServer;
beforeAll(async () => {
mongoServer = new MongoDBMemoryServer();
const mongoUri = await mongoServer.getConnectionString();
await mongoose.connect(mongoUri);
});
beforeEach(async () => {
await db.connect();
user = new User({ username: 'User' });
await user.setPassword('password');
await user.save();
token = user.generateJwt();
({
user, token, list, todo,
} = await seed());
});
afterEach(async () => {
await User.remove({}).exec();
await db.disconnect();
await clean();
});
afterAll(async () => {
await mongoose.disconnect();
await mongoServer.stop();
await server.close();
});
@@ -78,8 +89,8 @@ describe('test users', () => {
const response = await request(server)
.post('/users/login')
.send({
username: 'User',
password: 'password',
username: 'User1',
password: 'password1',
})
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
@@ -87,7 +98,7 @@ describe('test users', () => {
.expect('Content-Type', 'application/json; charset=utf-8');
expect(response.body.success).toBeTruthy();
const tokenDecoded = jwt.verify(response.body.data.jwt, secret);
expect(tokenDecoded.username).toEqual('User');
expect(tokenDecoded.username).toEqual('User1');
});
test('should not login user with no name', async () => {
await request(server)
@@ -148,7 +159,7 @@ describe('test users', () => {
.set('Accept', 'application/json')
.expect(401);
expect(response.body.success).toBeFalsy();
expect(await User.findOne({ username: 'User' }).exec()).toBeTruthy();
expect(await User.findOne({ username: 'User1' }).exec()).toBeTruthy();
});
test('should delete user', async () => {
const response = await request(server)
@@ -159,6 +170,6 @@ describe('test users', () => {
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8');
expect(response.body.success).toBeTruthy();
expect(await User.findOne({ username: 'User' }).exec()).toBeFalsy();
expect(await User.findOne({ username: 'User1' }).exec()).toBeFalsy();
});
});

View File

@@ -0,0 +1,33 @@
const mongoose = require('mongoose');
const User = mongoose.model('User');
const Todo = mongoose.model('Todo');
const TodoList = mongoose.model('TodoList');
async function seed() {
const user = new User({ username: 'User1' });
await user.setPassword('password1');
await user.save();
const token = user.generateJwt();
const list = new TodoList({ name: 'List1', user: user._id });
const todo = new Todo({ text: 'Todo1', list: list._id, user: user._id });
await list.save();
await todo.save();
return {
user,
token,
list,
todo,
};
}
async function clean() {
await TodoList.remove({}).exec();
await Todo.remove({}).exec();
await User.remove({}).exec();
}
module.exports = { seed, clean };