const loggerFactory = require('@alias/logger'); const logger = loggerFactory('growi:routes:apiv3:user-group'); const express = require('express'); const router = express.Router(); const { body, query } = require('express-validator/check'); const { isEmail } = require('validator'); const ErrorV3 = require('../../models/vo/error-apiv3'); const PAGE_ITEMS = 50; const validator = {}; /** * @swagger * tags: * name: Users */ /** * @swagger * * components: * schemas: * User: * description: User * type: object * properties: * _id: * type: string * description: user ID * example: 5ae5fccfc5577b0004dbd8ab * lang: * type: string * description: language * example: 'en-US' * status: * type: integer * description: status * example: 0 * admin: * type: boolean * description: whether the admin * example: false * email: * type: string * description: E-Mail address * example: alice@aaa.aaa * username: * type: string * description: username * example: alice * name: * type: string * description: full name * example: Alice * createdAt: * type: string * description: date created at * example: 2010-01-01T00:00:00.000Z */ module.exports = (crowi) => { const loginRequiredStrictly = require('../../middleware/login-required')(crowi); const adminRequired = require('../../middleware/admin-required')(crowi); const csrf = require('../../middleware/csrf')(crowi); const { User, Page, ExternalAccount, } = crowi.models; const { ApiV3FormValidator } = crowi.middlewares; /** * @swagger * * paths: * /users: * get: * tags: [Users] * operationId: listUsers * summary: /users * description: Get users * responses: * 200: * description: users are fetched * content: * application/json: * schema: * properties: * paginateResult: * $ref: '#/components/schemas/PaginateResult' */ router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => { const page = parseInt(req.query.page) || 1; try { const paginateResult = await User.paginate( { status: { $ne: User.STATUS_DELETED } }, { sort: { status: 1, username: 1, createdAt: 1 }, page, limit: PAGE_ITEMS, }, ); return res.apiv3({ paginateResult }); } catch (err) { const msg = 'Error occurred in fetching user group list'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'user-group-list-fetch-failed'), 500); } }); const statusNo = { registered: User.STATUS_REGISTERED, active: User.STATUS_ACTIVE, suspended: User.STATUS_SUSPENDED, invited: User.STATUS_INVITED, }; /* const sortOrderArray = ['asc', 'desc']; const sortArray = ['status', 'username', 'name', 'email', 'createdAt']; */ validator.statusList = [ // validate status list status array match to statusNo body('statusList').custom((value) => { const error = []; value.forEach((status) => { if (!Object.keys(statusNo)) { error.push(status); } }); return (error.length === 0); }), // validate sortOrder : asc or desc body('sortOrder').isIn(['asc', 'desc']), // validate sort : what column you will sort body('sort').isIn(['status', 'username', 'name', 'email', 'createdAt']), query('page').isInt({ min: 1 }), ]; router.get('/search-user-status/', validator.statusList, ApiV3FormValidator, async(req, res) => { const page = parseInt(req.query.page) || 1; // status const { statusList } = req.body; const statusNoList = statusList.map(element => statusNo[element]); // Search from input const inputWord = req.body.inputWord || ''; const searchWord = new RegExp(`${inputWord}`); const orColumns = ['name', 'username', 'email']; const orOutput = {}; orColumns.forEach((element) => { orOutput[element] = { $in: searchWord }; }); // Sort const { sort } = req.body; const { sortOrder } = req.body; const sortOutput = {}; sortOutput[sort] = (sortOrder === 'desc') ? -1 : 1; try { const paginateResult = await User.paginate( { $and: [ { status: { $in: statusNoList } }, { $or: [orOutput] }, ], }, { sort: sortOutput, page, limit: PAGE_ITEMS, }, ); return res.apiv3({ paginateResult }); } catch (err) { const msg = 'Error occurred in fetching user group list'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'user-group-list-fetch-failed'), 500); } }); validator.inviteEmail = [ // isEmail prevents line breaks, so use isString body('shapedEmailList').custom((value) => { const array = value.filter((value) => { return isEmail(value) }); if (array.length === 0) { throw new Error('At least one valid email address is required'); } return array; }), ]; /** * @swagger * * paths: * /users/invite: * post: * tags: [Users] * operationId: inviteUser * summary: /users/invite * description: Create new users and send Emails * parameters: * - name: shapedEmailList * in: query * description: Invitation emailList * schema: * type: object * - name: sendEmail * in: query * description: Whether to send mail * schema: * type: boolean * responses: * 200: * description: Inviting user success * content: * application/json: * schema: * properties: * createdUserList: * type: object * description: Users successfully created * existingEmailList: * type: object * description: Users email that already exists */ router.post('/invite', loginRequiredStrictly, adminRequired, csrf, validator.inviteEmail, ApiV3FormValidator, async(req, res) => { try { const invitedUserList = await User.createUsersByInvitation(req.body.shapedEmailList, req.body.sendEmail); return res.apiv3({ invitedUserList }); } catch (err) { logger.error('Error', err); return res.apiv3Err(new ErrorV3(err)); } }); /** * @swagger * * paths: * /users/{id}/giveAdmin: * put: * tags: [Users] * operationId: giveAdminUser * summary: /users/{id}/giveAdmin * description: Give user admin * parameters: * - name: id * in: path * required: true * description: id of user for admin * schema: * type: string * responses: * 200: * description: Give user admin success * content: * application/json: * schema: * properties: * userData: * type: object * description: data of admin user */ router.put('/:id/giveAdmin', loginRequiredStrictly, adminRequired, csrf, async(req, res) => { const { id } = req.params; try { const userData = await User.findById(id); await userData.makeAdmin(); return res.apiv3({ userData }); } catch (err) { logger.error('Error', err); return res.apiv3Err(new ErrorV3(err)); } }); /** * @swagger * * paths: * /users/{id}/removeAdmin: * put: * tags: [Users] * operationId: removeAdminUser * summary: /users/{id}/removeAdmin * description: Remove user admin * parameters: * - name: id * in: path * required: true * description: id of user for removing admin * schema: * type: string * responses: * 200: * description: Remove user admin success * content: * application/json: * schema: * properties: * userData: * type: object * description: data of removed admin user */ router.put('/:id/removeAdmin', loginRequiredStrictly, adminRequired, csrf, async(req, res) => { const { id } = req.params; try { const userData = await User.findById(id); await userData.removeFromAdmin(); return res.apiv3({ userData }); } catch (err) { logger.error('Error', err); return res.apiv3Err(new ErrorV3(err)); } }); /** * @swagger * * paths: * /users/{id}/activate: * put: * tags: [Users] * operationId: activateUser * summary: /users/{id}/activate * description: Activate user * parameters: * - name: id * in: path * required: true * description: id of activate user * schema: * type: string * responses: * 200: * description: Activationg user success * content: * application/json: * schema: * properties: * userData: * type: object * description: data of activate user */ router.put('/:id/activate', loginRequiredStrictly, adminRequired, csrf, async(req, res) => { // check user upper limit const isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit(); if (isUserCountExceedsUpperLimit) { const msg = 'Unable to activate because user has reached limit'; logger.error('Error', msg); return res.apiv3Err(new ErrorV3(msg)); } const { id } = req.params; try { const userData = await User.findById(id); await userData.statusActivate(); return res.apiv3({ userData }); } catch (err) { logger.error('Error', err); return res.apiv3Err(new ErrorV3(err)); } }); /** * @swagger * * paths: * /users/{id}/deactivate: * put: * tags: [Users] * operationId: deactivateUser * summary: /users/{id}/deactivate * description: Deactivate user * parameters: * - name: id * in: path * required: true * description: id of deactivate user * schema: * type: string * responses: * 200: * description: Deactivationg user success * content: * application/json: * schema: * properties: * userData: * type: object * description: data of deactivate user */ router.put('/:id/deactivate', loginRequiredStrictly, adminRequired, csrf, async(req, res) => { const { id } = req.params; try { const userData = await User.findById(id); await userData.statusSuspend(); return res.apiv3({ userData }); } catch (err) { logger.error('Error', err); return res.apiv3Err(new ErrorV3(err)); } }); /** * @swagger * * paths: * /users/{id}/remove: * delete: * tags: [Users] * operationId: removeUser * summary: /users/{id}/remove * description: Delete user * parameters: * - name: id * in: path * required: true * description: id of delete user * schema: * type: string * responses: * 200: * description: Deleting user success * content: * application/json: * schema: * properties: * userData: * type: object * description: data of delete user */ router.delete('/:id/remove', loginRequiredStrictly, adminRequired, csrf, async(req, res) => { const { id } = req.params; try { const userData = await User.findById(id); await userData.statusDelete(); await ExternalAccount.remove({ user: userData }); await Page.removeByPath(`/user/${userData.username}`); return res.apiv3({ userData }); } catch (err) { logger.error('Error', err); return res.apiv3Err(new ErrorV3(err)); } }); /** * @swagger * * paths: * /users/external-accounts: * get: * tags: [Users] * operationId: listExternalAccountsUsers * summary: /users/external-accounts * description: Get external-account * responses: * 200: * description: external-account are fetched * content: * application/json: * schema: * properties: * paginateResult: * $ref: '#/components/schemas/PaginateResult' */ router.get('/external-accounts/', loginRequiredStrictly, adminRequired, async(req, res) => { const page = parseInt(req.query.page) || 1; try { const paginateResult = await ExternalAccount.findAllWithPagination({ page }); return res.apiv3({ paginateResult }); } catch (err) { const msg = 'Error occurred in fetching external-account list '; logger.error(msg, err); return res.apiv3Err(new ErrorV3(msg + err.message, 'external-account-list-fetch-failed'), 500); } }); /** * @swagger * * paths: * /users/external-accounts/{id}/remove: * delete: * tags: [Users] * operationId: removeExternalAccountUser * summary: /users/external-accounts/{id}/remove * description: Delete ExternalAccount * parameters: * - name: id * in: path * required: true * description: id of ExternalAccount * schema: * type: string * responses: * 200: * description: External Account is removed * content: * application/json: * schema: * properties: * externalAccount: * type: object * description: A result of `ExtenralAccount.findByIdAndRemove` */ router.delete('/external-accounts/:id/remove', loginRequiredStrictly, adminRequired, ApiV3FormValidator, async(req, res) => { const { id } = req.params; try { const externalAccount = await ExternalAccount.findByIdAndRemove(id); return res.apiv3({ externalAccount }); } catch (err) { const msg = 'Error occurred in deleting a external account '; logger.error(msg, err); return res.apiv3Err(new ErrorV3(msg + err.message, 'extenral-account-delete-failed')); } }); return router; };