Skip to content

Commit

Permalink
Merge branch 'feature/user-role' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
gocreating committed Oct 22, 2016
2 parents 49f085f + 6eb2155 commit 1613ea6
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/common/api/user.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export default (apiEngine) => ({
list: ({ page }) => apiEngine.get('/api/users', { params: { page } }),
register: (user) => apiEngine.post('/api/users', { data: user }),
login: (user) => apiEngine.post('/api/users/login', { data: user }),
logout: () => apiEngine.get('/api/users/logout'),
Expand Down
8 changes: 8 additions & 0 deletions src/common/constants/Roles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
// end user
USER: 'USER',
// system administrator
ADMIN: 'ADMIN',
// root
ROOT: 'ROOT',
};
47 changes: 47 additions & 0 deletions src/common/utils/composeEnterHooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// ref:
// - <https://github.com/ReactTraining/react-router/issues/3103>
// - <https://github.com/baronswindle/react-router-compose-hooks/blob/master/index.js>

export default {
parallel(...hooks) {
let callbacksRequired = hooks.reduce((totalCallbacks, hook) => {
if (hook.length >= 3) {
totalCallbacks++;
}
return totalCallbacks;
}, 0);

return function onEnter(nextState, replace, executeTransition) {
let callbacksInvoked = 0;
hooks.forEach((hook) => {
hook.call(this, nextState, replace, () => {
if (++callbacksInvoked === callbacksRequired) {
executeTransition();
}
});
});
if (!callbacksRequired) {
executeTransition();
}
};
},

series(...hooks) {
return function onEnter(nextState, replace, executeTransition) {
(function executeHooksSynchronously(remainingHooks) {
if (!remainingHooks.length) {
return executeTransition();
}
let nextHook = remainingHooks[0];
if (nextHook.length >= 3) {
nextHook.call(this, nextState, replace, () => {
executeHooksSynchronously(remainingHooks.slice(1));
});
} else {
nextHook.call(this, nextState, replace);
executeHooksSynchronously(remainingHooks.slice(1));
}
})(hooks);
};
},
};
15 changes: 15 additions & 0 deletions src/common/utils/roleRequired.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default (store) => (requiredRoles) => (nextState, replace) => {
let { user } = store.getState().cookies;
user = (user && JSON.parse(user)) || {};

if (!((
requiredRoles instanceof Array &&
requiredRoles.indexOf(user.role) >= 0
) || (
user.role === requiredRoles
))) {
replace({
pathname: '/',
});
}
};
16 changes: 16 additions & 0 deletions src/server/controllers/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@ import User from '../models/User';
import filterAttribute from '../utils/filterAttribute';

export default {
list(req, res) {
User.paginate({ page: req.query.page }, handleDbError(res)((page) => {
User
.find({})
.sort({ createdAt: 'desc' })
.limit(page.limit)
.skip(page.skip)
.exec(handleDbError(res)((users) => {
res.json({
users: users,
page: page,
});
}));
}));
},

create(req, res) {
const user = User({
name: req.body.name,
Expand Down
16 changes: 16 additions & 0 deletions src/server/middlewares/roleRequired.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Errors from '../../common/constants/Errors';

const roleRequired = (requiredRoles) => (req, res, next) => {
if ((
requiredRoles instanceof Array &&
requiredRoles.indexOf(req.user.role) >= 0
) || (
req.user.role === requiredRoles
)) {
next();
} else {
return res.errors([Errors.PERMISSION_DENIED]);
}
};

export default roleRequired;
9 changes: 9 additions & 0 deletions src/server/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import crypto from 'crypto';
import mongoose from 'mongoose';
import jwt from 'jsonwebtoken';
import configs from '../../../configs/project/server';
import Roles from '../../common/constants/Roles';
import paginatePlugin from './plugins/paginate';

const hashPassword = (rawPassword = '') => {
let recursiveLevel = 5;
Expand Down Expand Up @@ -32,6 +34,11 @@ let UserSchema = new mongoose.Schema({
required: true,
set: hashPassword,
},
role: {
type: String,
enum: Object.keys(Roles).map(r => Roles[r]),
default: Roles.USER,
},
avatarURL: String,
}, {
versionKey: false,
Expand All @@ -41,6 +48,8 @@ let UserSchema = new mongoose.Schema({
},
});

UserSchema.plugin(paginatePlugin);

UserSchema.path('email.value').validate(function(value, cb) {
User.findOne({ 'email.value': value }, (err, user) => {
cb(!err && !user);
Expand Down
7 changes: 7 additions & 0 deletions src/server/routes/api.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import configs from '../../../configs/project/server';
import Roles from '../../common/constants/Roles';
import bodyParser from '../middlewares/bodyParser';
import authRequired from '../middlewares/authRequired';
import roleRequired from '../middlewares/roleRequired';
import fileUpload from '../middlewares/fileUpload';
import userController from '../controllers/user';
import formValidationController from '../controllers/formValidation';
Expand All @@ -9,6 +11,11 @@ import todoController from '../controllers/todo';

export default ({ app }) => {
// user
app.get('/api/users',
authRequired,
roleRequired([Roles.ADMIN]),
userController.list
);
app.post('/api/users', bodyParser.json, userController.create);
app.post('/api/users/login', bodyParser.json, userController.login);
app.get('/api/users/logout', userController.logout);
Expand Down

0 comments on commit 1613ea6

Please sign in to comment.