diff --git a/src/common/api/user.js b/src/common/api/user.js index 584df24..2d64da1 100644 --- a/src/common/api/user.js +++ b/src/common/api/user.js @@ -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'), diff --git a/src/common/constants/Roles.js b/src/common/constants/Roles.js new file mode 100644 index 0000000..13cb996 --- /dev/null +++ b/src/common/constants/Roles.js @@ -0,0 +1,8 @@ +export default { + // end user + USER: 'USER', + // system administrator + ADMIN: 'ADMIN', + // root + ROOT: 'ROOT', +}; diff --git a/src/common/utils/composeEnterHooks.js b/src/common/utils/composeEnterHooks.js new file mode 100644 index 0000000..60138ea --- /dev/null +++ b/src/common/utils/composeEnterHooks.js @@ -0,0 +1,47 @@ +// ref: +// - +// - + +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); + }; + }, +}; diff --git a/src/common/utils/roleRequired.js b/src/common/utils/roleRequired.js new file mode 100644 index 0000000..7354797 --- /dev/null +++ b/src/common/utils/roleRequired.js @@ -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: '/', + }); + } +}; diff --git a/src/server/controllers/user.js b/src/server/controllers/user.js index d46992d..5abdfdb 100644 --- a/src/server/controllers/user.js +++ b/src/server/controllers/user.js @@ -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, diff --git a/src/server/middlewares/roleRequired.js b/src/server/middlewares/roleRequired.js new file mode 100644 index 0000000..7879885 --- /dev/null +++ b/src/server/middlewares/roleRequired.js @@ -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; diff --git a/src/server/models/User.js b/src/server/models/User.js index 11f77d1..c10e233 100644 --- a/src/server/models/User.js +++ b/src/server/models/User.js @@ -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; @@ -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, @@ -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); diff --git a/src/server/routes/api.js b/src/server/routes/api.js index 1f18ca1..c7d1255 100644 --- a/src/server/routes/api.js +++ b/src/server/routes/api.js @@ -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'; @@ -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);