From 627b5455c204f77fb481d5656e12021ff2e12505 Mon Sep 17 00:00:00 2001 From: Trong Nhan Date: Fri, 22 Apr 2016 07:38:19 +0800 Subject: [PATCH] [update] Add user panel --- .istanbul.yml | 1 + package.json | 1 + src/api/config/routes.js | 3 + src/api/controllers/ModelController.js | 12 ++- src/api/controllers/UserController.js | 17 ++++ src/api/models/Model.js | 4 +- src/webapp/actions/UserActions.js | 19 ++++- src/webapp/actions/types/UserActionTypes.js | 5 +- .../components/_containers/AdminContainer.js | 12 ++- .../_containers/AdminUsersContainer.js | 77 +++++++++++++++++++ .../components/_containers/AppContainer.js | 2 +- .../_containers/ModelEditContainer.js | 40 +++++++++- src/webapp/components/_containers/index.js | 1 + src/webapp/config/routes.js | 2 + src/webapp/reducers/UserReducer.js | 38 ++++++++- src/webapp/server/config/render.js | 3 +- .../styles/_containers/_AppContainer.scss | 7 +- tests/api/controllers/ModelController.spec.js | 5 +- tests/api/models/Model.spec.js | 4 +- 19 files changed, 226 insertions(+), 27 deletions(-) create mode 100644 src/webapp/components/_containers/AdminUsersContainer.js diff --git a/.istanbul.yml b/.istanbul.yml index 7435884..f00bc24 100644 --- a/.istanbul.yml +++ b/.istanbul.yml @@ -1,2 +1,3 @@ instrumentation: root: src + excludes: ['**/api/helpers/**/*.js', '**/api/controllers/**/*.js', '**/api/models/Model.js'] diff --git a/package.json b/package.json index 1592f92..6d742f1 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "redux-router": "1.0.0-beta7", "redux-thunk": "1.0.3", "sendgrid": "2.0.0", + "serialize-javascript": "^1.2.0", "serve-favicon": "2.3.0", "sharp": "0.12.2", "slick-carousel": "1.5.9", diff --git a/src/api/config/routes.js b/src/api/config/routes.js index 5bb3f5b..a8a0545 100644 --- a/src/api/config/routes.js +++ b/src/api/config/routes.js @@ -10,6 +10,7 @@ const snapshotUpload = multer({ storage: StorageHelper.getSnapshotStorage() }); export default (app) => { // Authentication app.get('/user', UserController.request.getUser); + app.get('/users', Authorisation.requireAdmin, UserController.request.getUsers); app.get('/user/me', Authorisation.checkUser, UserController.request.me); app.get('/user/userInfo', Authorisation.requireUser, UserController.request.info); app.get('/user/adminInfo', Authorisation.requireAdmin, UserController.request.info); @@ -20,6 +21,8 @@ export default (app) => { app.post('/user/resetPassword', UserController.request.resetPassword); app.post('/user/logout', Authorisation.requireUser, UserController.request.logout); + app.delete('/user/:userId', Authorisation.requireAdmin, UserController.request.deleteUser); + // Model app.get('/model', ModelController.request.getModels); app.get('/model/:modelId', ModelController.request.getModel); diff --git a/src/api/controllers/ModelController.js b/src/api/controllers/ModelController.js index 2b02918..625ddab 100644 --- a/src/api/controllers/ModelController.js +++ b/src/api/controllers/ModelController.js @@ -4,9 +4,15 @@ import JSZip from 'jszip'; import fs from 'fs'; import path from 'path'; -import { ClientError, Constants, StringHelper } from 'common'; -import { MongooseHelper, ResponseHelper, TextureHelper, ModelHelper } from 'api/helpers'; -import { Model, User } from 'api/models'; +import ClientError from 'common/errors/ClientError'; +import Constants from 'common/constants'; +import StringHelper from 'common/string'; +import MongooseHelper from 'api/helpers/mongoose'; +import ResponseHelper from 'api/helpers/response'; +import TextureHelper from 'api/helpers/texture'; +import ModelHelper from 'api/helpers/model'; +import Model from 'api/models/Model'; +import User from 'api/models/User'; const DEBUG_ENV = 'UserController'; diff --git a/src/api/controllers/UserController.js b/src/api/controllers/UserController.js index 5ca58f1..743871a 100644 --- a/src/api/controllers/UserController.js +++ b/src/api/controllers/UserController.js @@ -21,6 +21,10 @@ UserController.request.getUser = function (req, res) { ResponseHelper.handle(UserController.promise.getUser, req, res, DEBUG_ENV); }; +UserController.request.getUsers = function (req, res) { + ResponseHelper.handle(UserController.promise.getUsers, req, res, DEBUG_ENV); +}; + UserController.request.me = function (req, res) { ResponseHelper.handle(UserController.promise.me, req, res, DEBUG_ENV); }; @@ -66,6 +70,10 @@ UserController.request.changePassword = function (req, res) { ResponseHelper.handle(UserController.promise.changePassword, req, res, DEBUG_ENV); }; +UserController.request.deleteUser = function (req, res) { + ResponseHelper.handle(UserController.promise.deleteUser, req, res, DEBUG_ENV); +}; + // ---------------------------------------------------------------------------- // UserController.promise.getUser = function (req) { @@ -73,6 +81,10 @@ UserController.promise.getUser = function (req) { return User.findUser(query, options); }; +UserController.promise.getUsers = function () { + return Promise.resolve(User.find({}).exec()); +}; + UserController.promise.me = function (req, needAuthorized = false) { if (needAuthorized && !req.user) { return Promise.reject(new ClientError(Constants.ERROR_TOKEN_UNVERIFIED)); @@ -209,4 +221,9 @@ UserController.promise.changePassword = function (req) { }); }; +UserController.promise.deleteUser = function (req) { + const { userId } = req.params; + return Promise.resolve(User.remove({ _id: userId }).exec()); +}; + export default UserController; diff --git a/src/api/models/Model.js b/src/api/models/Model.js index c96f6fd..84b7fab 100644 --- a/src/api/models/Model.js +++ b/src/api/models/Model.js @@ -2,8 +2,8 @@ import mongoose, { Schema } from 'mongoose'; import _ from 'lodash'; import Promise from 'bluebird'; -import { MongooseHelper } from 'api/helpers'; -import { Constants } from 'common'; +import MongooseHelper from 'api/helpers/mongoose'; +import Constants from 'common/constants'; const ObjectId = Schema.Types.ObjectId; diff --git a/src/webapp/actions/UserActions.js b/src/webapp/actions/UserActions.js index f7fe68f..2fe28d7 100644 --- a/src/webapp/actions/UserActions.js +++ b/src/webapp/actions/UserActions.js @@ -1,4 +1,5 @@ import { + REQ_GET_USERS, REQ_GET_USER, REQ_GET_USER_ME, REQ_GET_USER_USER_INFO, @@ -6,7 +7,8 @@ import { REQ_POST_USER_REGISTER, REQ_POST_USER_LOGIN, REQ_POST_USER_LOGOUT, - REQ_POST_USER_RESET_PASSWORD + REQ_POST_USER_RESET_PASSWORD, + REQ_DEL_USER } from './types'; export default { @@ -21,6 +23,13 @@ export default { }; }, + getUsers() { + return { + type: REQ_GET_USERS, + promise: apiClient => apiClient.get('/userS') + }; + }, + me() { return { type: REQ_GET_USER_ME, @@ -86,5 +95,13 @@ export default { } }) }; + }, + + deleteUser(userId) { + return { + type: REQ_DEL_USER, + promise: apiClient => apiClient.delete(`/user/${userId}`), + payload: userId + }; } }; diff --git a/src/webapp/actions/types/UserActionTypes.js b/src/webapp/actions/types/UserActionTypes.js index 460313c..d4aba34 100644 --- a/src/webapp/actions/types/UserActionTypes.js +++ b/src/webapp/actions/types/UserActionTypes.js @@ -5,6 +5,7 @@ const normalTypes = { }; const promiseTypes = { + REQ_GET_USERS: null, REQ_GET_USER: null, REQ_GET_USER_ME: null, REQ_GET_USER_USER_INFO: null, @@ -13,7 +14,9 @@ const promiseTypes = { REQ_POST_USER_REGISTER: null, REQ_POST_USER_LOGIN: null, REQ_POST_USER_LOGOUT: null, - REQ_POST_USER_RESET_PASSWORD: null + REQ_POST_USER_RESET_PASSWORD: null, + + REQ_DEL_USER: null }; export default TypeHelper.combineTypes(normalTypes, promiseTypes); diff --git a/src/webapp/components/_containers/AdminContainer.js b/src/webapp/components/_containers/AdminContainer.js index 8da39f0..20ffb44 100644 --- a/src/webapp/components/_containers/AdminContainer.js +++ b/src/webapp/components/_containers/AdminContainer.js @@ -1,4 +1,5 @@ import React from 'react'; +import { Link } from 'react-router'; const CLASS_NAME = 'cb-ctn-admin'; @@ -11,12 +12,17 @@ class AdminContainer extends React.Component { const { children } = this.props; return ( -
+
- + +
+
+ + MONITOR USERS +
{ children } diff --git a/src/webapp/components/_containers/AdminUsersContainer.js b/src/webapp/components/_containers/AdminUsersContainer.js new file mode 100644 index 0000000..e1f0d79 --- /dev/null +++ b/src/webapp/components/_containers/AdminUsersContainer.js @@ -0,0 +1,77 @@ +import React from 'react'; +import Immutable from 'immutable'; +import { connect } from 'react-redux'; + +import { UserActions } from 'webapp/actions'; + +const CLASS_NAME = 'cb-ctn-admin-users'; + +class AdminUsersContainer extends React.Component { + static fetchData({ dispatch }) { + return dispatch(UserActions.getUsers()); + } + + static propTypes = { + users: React.PropTypes.instanceOf(Immutable.List), + dispatch: React.PropTypes.func.isRequired + }; + + _onDeleteButtonClick = (user) => { + const { dispatch } = this.props; + dispatch(UserActions.deleteUser(user.get('_id'))); + }; + + render() { + return ( +
+ { this.renderUsersTable() } +
+ ); + } + + renderUsersTable() { + const { users } = this.props; + + return ( + + + + + + + + + + + { + users.map(this.renderUserRow.bind(this)) + } + +
User IDUser NameUser EmailActions
+ ); + } + + renderUserRow(user, index) { + return ( + + { user.get('_id') } + { user.get('name') } + { user.get('email') } + + + + + ); + } +} + +export default connect(state => { + const userIds = state.UserStore.get('userIds'); + const users = state.UserStore.get('users'); + + return { + users: userIds.map(id => users.get(id)) + }; +})(AdminUsersContainer); diff --git a/src/webapp/components/_containers/AppContainer.js b/src/webapp/components/_containers/AppContainer.js index 67ac790..fa51c49 100644 --- a/src/webapp/components/_containers/AppContainer.js +++ b/src/webapp/components/_containers/AppContainer.js @@ -179,7 +179,7 @@ class AppContainer extends PureComponent { } return (
-
+
{ + e.preventDefault(); + const { deleteConfirm } = this.state; + this.setState({ deleteConfirm: !deleteConfirm }); + }; + _onWalkthroughDelete = (e) => { e.preventDefault(); const { dispatch, params } = this.props; const { selectedWalkthroughIndex } = this.state; + this.setState({ deleteConfirm: false }); + this.setState({ selectedWalkthroughIndex: undefined }); dispatch(WalkthroughActions.deleteWalkthrough(params.modelId, selectedWalkthroughIndex)); }; @@ -485,7 +494,7 @@ // ----------------- WALKTHROUGH RENDER----------------- // ----------------------------------------------------- _renderWalkthroughSection() { - const { selectedWalkthroughIndex } = this.state; + const { selectedWalkthroughIndex, deleteConfirm } = this.state; if (typeof selectedWalkthroughIndex === 'undefined') { return undefined; } @@ -520,7 +529,32 @@ Update Position { selectedWalkthroughIndex < walkthroughPoints.size - 1 && this._renderWalkthroughAnimationDropdown() } +

{ this._renderAnimationDurationField() } + { deleteConfirm && this._renderDeleteWarning() } +
+ ); + } + + _renderDeleteWarning() { + return ( +
+ + +

Delete Point Warning

+
+ + + Are you sure that you want to delete this point? + + + + + + +
); } diff --git a/src/webapp/components/_containers/index.js b/src/webapp/components/_containers/index.js index 8cabc23..325d460 100644 --- a/src/webapp/components/_containers/index.js +++ b/src/webapp/components/_containers/index.js @@ -1,6 +1,7 @@ export default { AdminContainer: require('./AdminContainer'), AdminModelsContainer: require('./AdminModelsContainer'), + AdminUsersContainer: require('./AdminUsersContainer'), AdminLoginContainer: require('./AdminLoginContainer'), AppContainer: require('./AppContainer'), AuthorisationContainer: require('./AuthorisationContainer'), diff --git a/src/webapp/config/routes.js b/src/webapp/config/routes.js index d66ed60..4de99c9 100644 --- a/src/webapp/config/routes.js +++ b/src/webapp/config/routes.js @@ -5,6 +5,7 @@ import { AppContainer, AdminContainer, AdminModelsContainer, + AdminUsersContainer, AdminLoginContainer, AuthorisationContainer, HomeContainer, @@ -33,6 +34,7 @@ export default ( + diff --git a/src/webapp/reducers/UserReducer.js b/src/webapp/reducers/UserReducer.js index 41196a5..69dc172 100644 --- a/src/webapp/reducers/UserReducer.js +++ b/src/webapp/reducers/UserReducer.js @@ -3,13 +3,15 @@ import Immutable from 'immutable'; import { Constants } from 'common'; import ReducerHelper from './ReducerHelper'; import { + REQ_GET_USERS, REQ_GET_USER, REQ_GET_USER_ME, REQ_GET_USER_USER_INFO, REQ_GET_USER_ADMIN_INFO, REQ_POST_USER_LOGIN, REQ_POST_USER_LOGOUT, - REQ_POST_USER_RESET_PASSWORD + REQ_POST_USER_RESET_PASSWORD, + REQ_DEL_USER } from 'webapp/actions/types'; const initialState = Immutable.fromJS({ @@ -17,11 +19,33 @@ const initialState = Immutable.fromJS({ ownUserId: null, // The current focus user ID currentUserId: null, + userIds: Immutable.List(), // Users Map users: Immutable.Map() }); export default ReducerHelper.createReducer(initialState, { + [REQ_GET_USERS]: function (state, { promiseState, res }) { + let nextState = state; + if (promiseState === Constants.PROMISE_STATE_SUCCESS) { + const users = res.body; + const userMap = Immutable.fromJS( + users.reduce((m, user) => { + m[user._id] = user; + return m; + }, {}) + ); + const userIds = Immutable.fromJS( + users.map(u => u._id) + ); + nextState = nextState + .update('users', m => m.merge(userMap)) + .set('userIds', userIds); + } + + return nextState; + }, + [REQ_GET_USER]: function (state, { promiseState, res }) { let nextState = state; if (promiseState === Constants.PROMISE_STATE_SUCCESS) { @@ -101,5 +125,15 @@ export default ReducerHelper.createReducer(initialState, { return nextState; }, - [REQ_POST_USER_RESET_PASSWORD]: state => state + [REQ_POST_USER_RESET_PASSWORD]: state => state, + [REQ_DEL_USER](state, { promiseState, payload }) { + let nextState = state; + if (promiseState === Constants.PROMISE_STATE_SUCCESS) { + nextState = nextState + .update('userIds', list => list.filter(m => m !== payload)) + .deleteIn(['models', payload]); + } + + return nextState; + } }); diff --git a/src/webapp/server/config/render.js b/src/webapp/server/config/render.js index cbb555d..4c23052 100644 --- a/src/webapp/server/config/render.js +++ b/src/webapp/server/config/render.js @@ -2,6 +2,7 @@ import React from 'react'; import ReactDOMServer from 'react-dom/server'; import { match } from 'redux-router/server'; import Promise from 'bluebird'; +import serialize from 'serialize-javascript'; import { Logger, ClientError } from 'common'; import appRender from 'webapp/config/render'; @@ -58,7 +59,7 @@ const render = function (req, res, next) { ); diff --git a/src/webapp/styles/_containers/_AppContainer.scss b/src/webapp/styles/_containers/_AppContainer.scss index 4fe8ec3..c561dca 100644 --- a/src/webapp/styles/_containers/_AppContainer.scss +++ b/src/webapp/styles/_containers/_AppContainer.scss @@ -14,7 +14,6 @@ // NavBar Brand .cb-ctn-app-navbar-brand { color: $cb-default; - padding: 28px 50px; } // NavBar Button @@ -33,9 +32,5 @@ // App Content .cb-ctn-app-content { height: 100%; - padding: 65px 0 0; + padding: 61px 0 0; } - -.cb-navbar-form { - padding: 1% 10%; -} \ No newline at end of file diff --git a/tests/api/controllers/ModelController.spec.js b/tests/api/controllers/ModelController.spec.js index 511e210..f9ec5ce 100644 --- a/tests/api/controllers/ModelController.spec.js +++ b/tests/api/controllers/ModelController.spec.js @@ -1,10 +1,11 @@ import { expect } from 'chai'; import sinon from 'sinon'; +import path from 'path'; import Promise from 'bluebird'; import ModelController from 'api/controllers/ModelController'; -import { Model, User } from 'api/models'; -import path from 'path'; +import Model from 'api/models/Model'; +import User from 'api/models/User'; describe('Model Controller', () => { let sandbox; diff --git a/tests/api/models/Model.spec.js b/tests/api/models/Model.spec.js index c74b734..aa7afea 100644 --- a/tests/api/models/Model.spec.js +++ b/tests/api/models/Model.spec.js @@ -2,8 +2,8 @@ import { expect } from 'chai'; import sinon from 'sinon'; import Model from 'api/models/Model'; -import { MongooseHelper } from 'api/helpers'; -import { Constants } from 'common'; +import MongooseHelper from 'api/helpers/mongoose'; +import Constants from 'common/constants'; describe('Model Schema', () => { let sandbox;