user-group.js 17 KB

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