Skip to content
Merged
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
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
+ **[pg](https://www.npmjs.com/package/pg)** - Non-blocking PostgreSQL client for node.js. Pure JavaScript and optional native libpq bindings
+ **[pg-hstore](https://www.npmjs.com/package/pg-hstore)** - A node package for serializing and deserializing JSON data to hstore format
+ **[sequelize](https://www.npmjs.com/package/sequelize)** - Sequelize is a promise-based Node.js ORM for Postgres, MySQL, SQLite and Microsoft SQL Server. It features solid transaction support, relations, read replication and more
+ **[sequelize-cli](https://www.npmjs.com/package/sequelize-cli)** - The Sequelize Command Line Interface (CLI)

### Development Dependencies
+ **[chai](https://www.npmjs.com/package/chai)** - Chai is a BDD / TDD assertion library for node and the browser that can be delightfully paired with any javascript testing framework.
Expand All @@ -40,8 +39,9 @@
+ **[gulp-exit](https://www.npmjs.com/package/gulp-exit)** - ensures that the task is terminated after finishing.
+ **[gulp-inject-modules](https://www.npmjs.com/package/gulp-inject-modules)** - Loads JavaScript files on-demand from a Gulp stream into Node's module loader.
+ **[gulp-istanbul](https://www.npmjs.com/package/gulp-istanbul)** - Istanbul unit test coverage plugin for gulp.
+ **[gulp-jasmine-node](https://www.npmjs.com/package/gulp-jasmine-node)** - This is a very basic implementation of a gulp task for jasmine-node
+ **[gulp-jasmine](https://www.npmjs.com/package/gulp-jasmine)** - This is a very basic implementation of a gulp task for jasmine
+ **[gulp-nodemon](https://www.npmjs.com/package/gulp-nodemon)** - it's gulp + nodemon + convenience
+ **[sequelize-cli](https://www.npmjs.com/package/sequelize-cli)** - The Sequelize Command Line Interface (CLI)
+ **[supertest](https://www.npmjs.com/package/supertest)** - HTTP assertions made easy via superagent.

## Installation and Setup
Expand All @@ -61,13 +61,15 @@
+ `$ npm start`

## Tests
+ The tests were written using Supertest, Chai, Chai-HTTP.
+ The tests were written using Supertest, Chai.
+ The test coverage is generated by `gulp-istanbul` package
+ To run tests, navigate to the project's root directory
+ Run the following commands.
+ `$ npm test`

## How to contribute
In order to contribute, certain guidelines and style guides must be followed.
More info is available on the repo's [wiki](https://github.com/codejockie/document-manager/wiki).
To contribute, fork this repo to your private repository and create a pull request based on the feature you want to add.

## Disclaimer
Expand All @@ -83,4 +85,4 @@ This app and its functions are limited by time constraint and is in no way at it
License included in the repository

## Author
John Kennedy - @codejockie
John Kennedy - [@codejockie](https://twitter.com/codejockie)
2 changes: 1 addition & 1 deletion gulpfile.babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ gulp.task('compile', () => gulp.src('server/**/*.js')
.pipe(gulp.dest('src')));

gulp.task('test', ['compile'], (done) => {
gulp.src(['src/routes/*.js'])
gulp.src(['src/controllers/*.js', 'src/helpers/*.js', 'src/middleware/*.js'])
.pipe(istanbul())
.pipe(istanbul.hookRequire())
.on('finish', () => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"start": "node src/server.js",
"test": "gulp test",
"dev": "nodemon -w server --exec babel-node -- server/server.js",
"devtest": "NODE_ENV=devtest mocha --timeout 90000 --compilers js:babel-register -R mocha-lcov-reporter -- ./tests/**/*.spec.js",
"devtest": "NODE_ENV=devtest mocha --timeout 90000 --compilers js:babel-register -- ./tests/**/*.spec.js",
"devmigrate": "sequelize db:migrate && npm run seed",
"localmigrate": "NODE_ENV=devtest sequelize db:migrate",
"testmigrate": "NODE_ENV=test sequelize db:migrate",
Expand Down
437 changes: 223 additions & 214 deletions public/index.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion server/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"devtest": {
"username": null,
"password": null,
"database": "testdm",
"database": "doc-man-test",
"host": "127.0.0.1",
"port": 5432,
"dialect": "postgres",
Expand Down
145 changes: 145 additions & 0 deletions server/controllers/documents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import models from '../models';
import { documentCreator, isAdmin, isUser } from '../helpers/helper';
import paginate from '../helpers/paginate';

const Document = models.Document;

export default {
create(req, res) {
Document.findOne({
where: {
title: req.body.title
}
})
.then((document) => {
if (document) {
return res.status(422).send({
message: 'A document exist with the same title',
});
}

Document.create({
title: req.body.title,
author: req.user.fullName,
content: req.body.content,
access: req.body.access,
userId: req.user.id,
roleId: req.user.roleId
})
.then(newDoc => res.status(201).send(documentCreator(newDoc)))
.catch(() => res.status(400).send({ message: "Access field must be any of 'public' or 'private'" }));
});
},
getAll(req, res) {
if (req.query.limit || req.query.offset) {
if (!Number.isInteger(Number(req.query.limit))
|| !Number.isInteger(Number(req.query.offset))) {
return res.status(400).send({
message: 'Limit and Offset params must be numbers'
});
}
}

Document.findAll()
.then((response) => {
const totalCount = response.length;
const offset = req.query.offset || 0;
const limit = req.query.limit || 10;

return Document.findAll({
where: {
$or: [{
userId: req.user.id
}, {
roleId: req.user.roleId
}, {
access: 'public'
}]
},
offset,
limit,
})
.then((documents) => {
if (documents.length === 0) {
return res.status(404).send({
message: 'No document found'
});
}

return res.status(200).send({
metaData: paginate(limit, offset, totalCount),
documents: documents.map(document => (documentCreator(document)))
});
});
});
},
getOne(req, res) {
Document.findById(req.params.id)
.then((document) => {
// Checks if the document owner's ID is
// equal to the logged in user's ID
if (!isUser(document.userId, req.user.id)) {
return res.status(401).send({
message: "Unauthorised user. You don't have permission to access this document"
});
}

return res.status(200).send(documentCreator(document));
});
},
update(req, res) {
Document.findById(req.params.id)
.then((document) => {
if (!isAdmin(req.user.id) && !isUser(document.userId, req.user.id)) {
return res.status(401).send({
message: "Unauthorised user. You don't have permission to update this document"
});
}

Document.findAll({
where: {
title: req.body.title
}
})
.then((existingDocument) => {
if (existingDocument.length !== 0
&& (existingDocument[0].dataValues.id !== parseInt(req.params.id, 10))) {
return res.status(422).send({
message: 'A document exist with the same title'
});
}


return document.update({
title: req.body.title || document.title,
content: req.body.content || document.content,
author: req.user.fullName || document.author,
access: req.body.access || document.access,
userId: document.userId,
roleId: document.roleId,
createdAt: req.body.createdAt || document.createdAt,
updatedAt: req.body.updatedAt || document.updatedAt
})
.then(() => res.status(201).send({
document: documentCreator(document)
}))
.catch(() => res.status(400).send({ message: "Access field must be any of 'public' or 'private'" }));
});
});
},
delete(req, res) {
Document.findById(req.params.id)
.then((document) => {
if (!isAdmin(req.user.id) && !isUser(document.userId, req.user.id)) {
return res.status(401).send({
message: "Unauthorised user. You don't have permission to delete this document"
});
}

return document.destroy()
.then(() => res.status(200).send({
message: 'Document deleted successfully'
}));
});
}
};
72 changes: 72 additions & 0 deletions server/controllers/roles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import models from '../models';
import { roleCreator } from '../helpers/helper';

const Role = models.Role;

export default {
create(req, res) {
Role.findOne({
where: {
name: req.body.name
}
})
.then((role) => {
if (role) {
return res.status(422).send({
message: 'Role name must be unique'
});
}

Role
.create({
name: req.body.name
})
.then(newRole => res.status(201).send(roleCreator(newRole)));
});
},
getAll(req, res) {
Role
.all()
.then(roles => res.status(200).send({
roles
}));
},
getOne(req, res) {
Role.findById(req.params.id)
.then(role => res.status(200).send(roleCreator(role)));
},
update(req, res) {
return Role
.findById(req.params.id)
.then((role) => {
Role.findAll({
where: {
name: req.body.name
}
})
.then((existingRole) => {
if (existingRole.length !== 0
&& (existingRole[0].dataValues.id !== parseInt(req.params.id, 10))) {
return res.status(422).send({
message: 'A role exist with same name'
});
}

return role.update({
name: req.body.name
})
.then(() => res.status(200).send(roleCreator(role)));
});
});
},
delete(req, res) {
Role.findById(req.params.id)
.then((role) => {
role
.destroy()
.then(() => res.status(200).send({
message: 'Role deleted successfully'
}));
});
}
};
69 changes: 69 additions & 0 deletions server/controllers/search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import models from '../models';
import { documentCreator } from '../helpers/helper';

const Document = models.Document;
const User = models.User;

export default {
searchUser(req, res) {
const query = req.query.q.trim();

User.findAll({
where: {
$or: [{
username: {
$iLike: `%${query}%`
}
}, {
email: {
$iLike: `%${query}%`
}
}]
}
})
.then((users) => {
if (users.length === 0) {
return res.status(200).send({
user: []
});
}

return res.status(200).send({
users: users.map(user => (
{
email: user.email,
username: user.username
}))
});
});
},
searchDocument(req, res) {
const query = req.query.q.trim();

Document.findAll({
where: {
$and: {
title: {
$iLike: `%${query}%`
},
$or: [
{
access: 'public',
},
]
}
},
order: [['createdAt', 'DESC']]
})
.then((documents) => {
if (documents.length === 0) {
return res.status(200).send({
document: []
});
}
return res.status(200).send({
documents: documents.map(document => documentCreator(document))
});
});
}
};
Loading