user-group.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. const loggerFactory = require('@alias/logger');
  2. const logger = loggerFactory('growi:routes:apiv3:user-group'); // eslint-disable-line no-unused-vars
  3. const express = require('express');
  4. const router = express.Router();
  5. const { body, param, query } = require('express-validator');
  6. const { sanitizeQuery } = require('express-validator');
  7. const mongoose = require('mongoose');
  8. const ErrorV3 = require('../../models/vo/error-apiv3');
  9. const { serializeUserSecurely } = require('../../models/serializers/user-serializer');
  10. const { toPagingLimit, toPagingOffset } = require('../../util/express-validator/sanitizer');
  11. const validator = {};
  12. const { ObjectId } = mongoose.Types;
  13. /**
  14. * @swagger
  15. * tags:
  16. * name: UserGroup
  17. */
  18. module.exports = (crowi) => {
  19. const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  20. const adminRequired = require('../../middlewares/admin-required')(crowi);
  21. const csrf = require('../../middlewares/csrf')(crowi);
  22. const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
  23. const {
  24. UserGroup,
  25. UserGroupRelation,
  26. User,
  27. Page,
  28. } = crowi.models;
  29. /**
  30. * @swagger
  31. *
  32. * paths:
  33. * /user-groups:
  34. * get:
  35. * tags: [UserGroup]
  36. * operationId: getUserGroup
  37. * summary: /user-groups
  38. * description: Get usergroups
  39. * responses:
  40. * 200:
  41. * description: usergroups are fetched
  42. * content:
  43. * application/json:
  44. * schema:
  45. * properties:
  46. * userGroups:
  47. * type: object
  48. * description: a result of `UserGroup.find`
  49. */
  50. router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
  51. // TODO: filter with querystring
  52. try {
  53. const page = parseInt(req.query.page) || 1;
  54. const result = await UserGroup.findUserGroupsWithPagination({ page });
  55. const { docs: userGroups, totalDocs: totalUserGroups, limit: pagingLimit } = result;
  56. return res.apiv3({ userGroups, totalUserGroups, pagingLimit });
  57. }
  58. catch (err) {
  59. const msg = 'Error occurred in fetching user group list';
  60. logger.error('Error', err);
  61. return res.apiv3Err(new ErrorV3(msg, 'user-group-list-fetch-failed'));
  62. }
  63. });
  64. validator.create = [
  65. body('name', 'Group name is required').trim().exists({ checkFalsy: true }),
  66. ];
  67. /**
  68. * @swagger
  69. *
  70. * paths:
  71. * /user-groups:
  72. * post:
  73. * tags: [UserGroup]
  74. * operationId: createUserGroup
  75. * summary: /user-groups
  76. * description: Adds userGroup
  77. * requestBody:
  78. * required: true
  79. * content:
  80. * application/json:
  81. * schema:
  82. * properties:
  83. * name:
  84. * type: string
  85. * description: name of the userGroup trying to be added
  86. * responses:
  87. * 200:
  88. * description: userGroup is added
  89. * content:
  90. * application/json:
  91. * schema:
  92. * properties:
  93. * userGroup:
  94. * type: object
  95. * description: A result of `UserGroup.createGroupByName`
  96. */
  97. router.post('/', loginRequiredStrictly, adminRequired, csrf, validator.create, apiV3FormValidator, async(req, res) => {
  98. const { name } = req.body;
  99. try {
  100. const userGroupName = crowi.xss.process(name);
  101. const userGroup = await UserGroup.createGroupByName(userGroupName);
  102. return res.apiv3({ userGroup }, 201);
  103. }
  104. catch (err) {
  105. const msg = 'Error occurred in creating a user group';
  106. logger.error(msg, err);
  107. return res.apiv3Err(new ErrorV3(msg, 'user-group-create-failed'));
  108. }
  109. });
  110. validator.delete = [
  111. param('id').trim().exists({ checkFalsy: true }),
  112. query('actionName').trim().exists({ checkFalsy: true }),
  113. query('transferToUserGroupId').trim(),
  114. ];
  115. /**
  116. * @swagger
  117. *
  118. * paths:
  119. * /user-groups/{id}:
  120. * delete:
  121. * tags: [UserGroup]
  122. * operationId: deleteUserGroup
  123. * summary: /user-groups/{id}
  124. * description: Deletes userGroup
  125. * parameters:
  126. * - name: id
  127. * in: path
  128. * required: true
  129. * description: id of userGroup
  130. * schema:
  131. * type: string
  132. * - name: actionName
  133. * in: query
  134. * description: name of action
  135. * schema:
  136. * type: string
  137. * - name: transferToUserGroupId
  138. * in: query
  139. * description: userGroup id that will be transferred to
  140. * schema:
  141. * type: string
  142. * responses:
  143. * 200:
  144. * description: userGroup is removed
  145. * content:
  146. * application/json:
  147. * schema:
  148. * properties:
  149. * userGroups:
  150. * type: object
  151. * description: A result of `UserGroup.removeCompletelyById`
  152. */
  153. router.delete('/:id', loginRequiredStrictly, adminRequired, csrf, validator.delete, apiV3FormValidator, async(req, res) => {
  154. const { id: deleteGroupId } = req.params;
  155. const { actionName, transferToUserGroupId } = req.query;
  156. try {
  157. const userGroup = await UserGroup.removeCompletelyById(deleteGroupId, actionName, transferToUserGroupId);
  158. return res.apiv3({ userGroup });
  159. }
  160. catch (err) {
  161. const msg = 'Error occurred in deleting a user group';
  162. logger.error(msg, err);
  163. return res.apiv3Err(new ErrorV3(msg, 'user-group-delete-failed'));
  164. }
  165. });
  166. // return one group with the id
  167. // router.get('/:id', async(req, res) => {
  168. // });
  169. validator.update = [
  170. body('name', 'Group name is required').trim().exists({ checkFalsy: true }),
  171. ];
  172. /**
  173. * @swagger
  174. *
  175. * paths:
  176. * /user-groups/{id}:
  177. * put:
  178. * tags: [UserGroup]
  179. * operationId: updateUserGroups
  180. * summary: /user-groups/{id}
  181. * description: Update userGroup
  182. * parameters:
  183. * - name: id
  184. * in: path
  185. * required: true
  186. * description: id of userGroup
  187. * schema:
  188. * type: string
  189. * responses:
  190. * 200:
  191. * description: userGroup is updated
  192. * content:
  193. * application/json:
  194. * schema:
  195. * properties:
  196. * userGroup:
  197. * type: object
  198. * description: A result of `UserGroup.updateName`
  199. */
  200. router.put('/:id', loginRequiredStrictly, adminRequired, csrf, validator.update, apiV3FormValidator, async(req, res) => {
  201. const { id } = req.params;
  202. const { name } = req.body;
  203. try {
  204. const userGroup = await UserGroup.findById(id);
  205. if (userGroup == null) {
  206. throw new Error('The group does not exist');
  207. }
  208. // check if the new group name is available
  209. const isRegisterableName = await UserGroup.isRegisterableName(name);
  210. if (!isRegisterableName) {
  211. throw new Error('The group name is already taken');
  212. }
  213. await userGroup.updateName(name);
  214. res.apiv3({ userGroup });
  215. }
  216. catch (err) {
  217. const msg = 'Error occurred in updating a user group name';
  218. logger.error(msg, err);
  219. return res.apiv3Err(new ErrorV3(msg, 'user-group-update-failed'));
  220. }
  221. });
  222. validator.users = {};
  223. /**
  224. * @swagger
  225. *
  226. * paths:
  227. * /user-groups/{id}/users:
  228. * get:
  229. * tags: [UserGroup]
  230. * operationId: getUsersUserGroups
  231. * summary: /user-groups/{id}/users
  232. * description: Get users related to the userGroup
  233. * parameters:
  234. * - name: id
  235. * in: path
  236. * required: true
  237. * description: id of userGroup
  238. * schema:
  239. * type: string
  240. * responses:
  241. * 200:
  242. * description: users are fetched
  243. * content:
  244. * application/json:
  245. * schema:
  246. * properties:
  247. * users:
  248. * type: array
  249. * items:
  250. * type: object
  251. * description: user objects
  252. */
  253. router.get('/:id/users', loginRequiredStrictly, adminRequired, async(req, res) => {
  254. const { id } = req.params;
  255. try {
  256. const userGroup = await UserGroup.findById(id);
  257. const userGroupRelations = await UserGroupRelation.findAllRelationForUserGroup(userGroup);
  258. const users = userGroupRelations.map((userGroupRelation) => {
  259. return serializeUserSecurely(userGroupRelation.relatedUser);
  260. });
  261. return res.apiv3({ users });
  262. }
  263. catch (err) {
  264. const msg = `Error occurred in fetching users for group: ${id}`;
  265. logger.error(msg, err);
  266. return res.apiv3Err(new ErrorV3(msg, 'user-group-user-list-fetch-failed'));
  267. }
  268. });
  269. /**
  270. * @swagger
  271. *
  272. * paths:
  273. * /user-groups/{id}/unrelated-users:
  274. * get:
  275. * tags: [UserGroup]
  276. * operationId: getUnrelatedUsersUserGroups
  277. * summary: /user-groups/{id}/unrelated-users
  278. * description: Get users unrelated to the userGroup
  279. * parameters:
  280. * - name: id
  281. * in: path
  282. * required: true
  283. * description: id of userGroup
  284. * schema:
  285. * type: string
  286. * responses:
  287. * 200:
  288. * description: users are fetched
  289. * content:
  290. * application/json:
  291. * schema:
  292. * properties:
  293. * users:
  294. * type: array
  295. * items:
  296. * type: object
  297. * description: user objects
  298. */
  299. router.get('/:id/unrelated-users', loginRequiredStrictly, adminRequired, async(req, res) => {
  300. const { id } = req.params;
  301. const {
  302. searchWord, searchType, isAlsoNameSearched, isAlsoMailSearched,
  303. } = req.query;
  304. const queryOptions = {
  305. searchWord, searchType, isAlsoNameSearched, isAlsoMailSearched,
  306. };
  307. try {
  308. const userGroup = await UserGroup.findById(id);
  309. const users = await UserGroupRelation.findUserByNotRelatedGroup(userGroup, queryOptions);
  310. const serializedUsers = users.map((user) => {
  311. return serializeUserSecurely(user);
  312. });
  313. return res.apiv3({ users: serializedUsers });
  314. }
  315. catch (err) {
  316. const msg = `Error occurred in fetching unrelated users for group: ${id}`;
  317. logger.error(msg, err);
  318. return res.apiv3Err(new ErrorV3(msg, 'user-group-unrelated-user-list-fetch-failed'));
  319. }
  320. });
  321. validator.users.post = [
  322. param('id').trim().exists({ checkFalsy: true }),
  323. param('username').trim().exists({ checkFalsy: true }),
  324. ];
  325. /**
  326. * @swagger
  327. *
  328. * paths:
  329. * /user-groups/{id}/users:
  330. * post:
  331. * tags: [UserGroup]
  332. * operationId: addUserUserGroups
  333. * summary: /user-groups/{id}/users
  334. * description: Add a user to the userGroup
  335. * parameters:
  336. * - name: id
  337. * in: path
  338. * required: true
  339. * description: id of userGroup
  340. * schema:
  341. * type: string
  342. * responses:
  343. * 200:
  344. * description: a user is added
  345. * content:
  346. * application/json:
  347. * schema:
  348. * type: object
  349. * properties:
  350. * user:
  351. * type: object
  352. * description: the user added to the group
  353. * userGroup:
  354. * type: object
  355. * description: the group to which a user was added
  356. * userGroupRelation:
  357. * type: object
  358. * description: the associative entity between user and userGroup
  359. */
  360. router.post('/:id/users/:username', loginRequiredStrictly, adminRequired, validator.users.post, apiV3FormValidator, async(req, res) => {
  361. const { id, username } = req.params;
  362. try {
  363. const [userGroup, user] = await Promise.all([
  364. UserGroup.findById(id),
  365. User.findUserByUsername(username),
  366. ]);
  367. // check for duplicate users in groups
  368. const isRelatedUserForGroup = await UserGroupRelation.isRelatedUserForGroup(userGroup, user);
  369. if (isRelatedUserForGroup) {
  370. logger.warn('The user is already joined');
  371. return res.apiv3();
  372. }
  373. const userGroupRelation = await UserGroupRelation.createRelation(userGroup, user);
  374. const serializedUser = serializeUserSecurely(user);
  375. return res.apiv3({ user: serializedUser, userGroup, userGroupRelation });
  376. }
  377. catch (err) {
  378. const msg = `Error occurred in adding the user "${username}" to group "${id}"`;
  379. logger.error(msg, err);
  380. return res.apiv3Err(new ErrorV3(msg, 'user-group-add-user-failed'));
  381. }
  382. });
  383. validator.users.delete = [
  384. param('id').trim().exists({ checkFalsy: true }),
  385. param('username').trim().exists({ checkFalsy: true }),
  386. ];
  387. /**
  388. * @swagger
  389. *
  390. * paths:
  391. * /user-groups/{id}/users:
  392. * delete:
  393. * tags: [UserGroup]
  394. * operationId: deleteUsersUserGroups
  395. * summary: /user-groups/{id}/users
  396. * description: remove a user from the userGroup
  397. * parameters:
  398. * - name: id
  399. * in: path
  400. * required: true
  401. * description: id of userGroup
  402. * schema:
  403. * type: string
  404. * responses:
  405. * 200:
  406. * description: a user was removed
  407. * content:
  408. * application/json:
  409. * schema:
  410. * type: object
  411. * properties:
  412. * user:
  413. * type: object
  414. * description: the user removed from the group
  415. * userGroup:
  416. * type: object
  417. * description: the group from which a user was removed
  418. * userGroupRelation:
  419. * type: object
  420. * description: the associative entity between user and userGroup
  421. */
  422. router.delete('/:id/users/:username', loginRequiredStrictly, adminRequired, validator.users.delete, apiV3FormValidator, async(req, res) => {
  423. const { id, username } = req.params;
  424. try {
  425. const [userGroup, user] = await Promise.all([
  426. UserGroup.findById(id),
  427. User.findUserByUsername(username),
  428. ]);
  429. const userGroupRelation = await UserGroupRelation.findOneAndDelete({ relatedUser: new ObjectId(user._id), relatedGroup: new ObjectId(userGroup._id) });
  430. const serializedUser = serializeUserSecurely(user);
  431. return res.apiv3({ user: serializedUser, userGroup, userGroupRelation });
  432. }
  433. catch (err) {
  434. const msg = `Error occurred in removing the user "${username}" from group "${id}"`;
  435. logger.error(msg, err);
  436. return res.apiv3Err(new ErrorV3(msg, 'user-group-remove-user-failed'));
  437. }
  438. });
  439. validator.userGroupRelations = {};
  440. /**
  441. * @swagger
  442. *
  443. * paths:
  444. * /user-groups/{id}/user-group-relations:
  445. * get:
  446. * tags: [UserGroup]
  447. * operationId: getUserGroupRelationsUserGroups
  448. * summary: /user-groups/{id}/user-group-relations
  449. * description: Get the user group relations for the userGroup
  450. * parameters:
  451. * - name: id
  452. * in: path
  453. * required: true
  454. * description: id of userGroup
  455. * schema:
  456. * type: string
  457. * responses:
  458. * 200:
  459. * description: user group relations are fetched
  460. * content:
  461. * application/json:
  462. * schema:
  463. * properties:
  464. * userGroupRelations:
  465. * type: array
  466. * items:
  467. * type: object
  468. * description: userGroupRelation objects
  469. */
  470. router.get('/:id/user-group-relations', loginRequiredStrictly, adminRequired, async(req, res) => {
  471. const { id } = req.params;
  472. try {
  473. const userGroup = await UserGroup.findById(id);
  474. const userGroupRelations = await UserGroupRelation.findAllRelationForUserGroup(userGroup);
  475. return res.apiv3({ userGroupRelations });
  476. }
  477. catch (err) {
  478. const msg = `Error occurred in fetching user group relations for group: ${id}`;
  479. logger.error(msg, err);
  480. return res.apiv3Err(new ErrorV3(msg, 'user-group-user-group-relation-list-fetch-failed'));
  481. }
  482. });
  483. validator.pages = {};
  484. validator.pages.get = [
  485. param('id').trim().exists({ checkFalsy: true }),
  486. sanitizeQuery('limit').customSanitizer(toPagingLimit),
  487. sanitizeQuery('offset').customSanitizer(toPagingOffset),
  488. ];
  489. /**
  490. * @swagger
  491. *
  492. * paths:
  493. * /user-groups/{id}/pages:
  494. * get:
  495. * tags: [UserGroup]
  496. * operationId: getPagesUserGroups
  497. * summary: /user-groups/{id}/pages
  498. * description: Get closed pages for the userGroup
  499. * parameters:
  500. * - name: id
  501. * in: path
  502. * required: true
  503. * description: id of userGroup
  504. * schema:
  505. * type: string
  506. * responses:
  507. * 200:
  508. * description: pages are fetched
  509. * content:
  510. * application/json:
  511. * schema:
  512. * properties:
  513. * pages:
  514. * type: array
  515. * items:
  516. * type: object
  517. * description: page objects
  518. */
  519. router.get('/:id/pages', loginRequiredStrictly, adminRequired, validator.pages.get, apiV3FormValidator, async(req, res) => {
  520. const { id } = req.params;
  521. const { limit, offset } = req.query;
  522. try {
  523. const { docs, totalDocs } = await Page.paginate({
  524. grant: Page.GRANT_USER_GROUP,
  525. grantedGroup: { $in: [id] },
  526. }, {
  527. offset,
  528. limit,
  529. populate: 'lastUpdateUser',
  530. });
  531. const current = offset / limit + 1;
  532. const pages = docs.map((doc) => {
  533. doc.lastUpdateUser = serializeUserSecurely(doc.lastUpdateUser);
  534. return doc;
  535. });
  536. // TODO: create a common moudule for paginated response
  537. return res.apiv3({ total: totalDocs, current, pages });
  538. }
  539. catch (err) {
  540. const msg = `Error occurred in fetching pages for group: ${id}`;
  541. logger.error(msg, err);
  542. return res.apiv3Err(new ErrorV3(msg, 'user-group-page-list-fetch-failed'));
  543. }
  544. });
  545. return router;
  546. };