user-group.js 18 KB

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