2
0

bookmark-folder.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import { ErrorV3 } from '@growi/core';
  2. import { body } from 'express-validator';
  3. import { Types } from 'mongoose';
  4. import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
  5. import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
  6. import { InvalidParentBookmarkFolderError } from '~/server/models/errors';
  7. import { serializeBookmarkSecurely } from '~/server/models/serializers/bookmark-serializer';
  8. import loggerFactory from '~/utils/logger';
  9. import BookmarkFolder from '../../models/bookmark-folder';
  10. const logger = loggerFactory('growi:routes:apiv3:bookmark-folder');
  11. const express = require('express');
  12. const router = express.Router();
  13. const validator = {
  14. bookmarkFolder: [
  15. body('name').isString().withMessage('name must be a string'),
  16. body('parent').isMongoId().optional({ nullable: true })
  17. .custom(async(parent: string) => {
  18. const parentFolder = await BookmarkFolder.findById(parent);
  19. if (parentFolder == null || parentFolder.parent != null) {
  20. throw new Error('Maximum folder hierarchy of 2 levels');
  21. }
  22. }),
  23. body('children').optional().isArray().withMessage('Children must be an array'),
  24. body('bookmarkFolderId').optional().isMongoId().withMessage('Bookark Folder ID must be a valid mongo ID'),
  25. ],
  26. bookmarkPage: [
  27. body('pageId').isMongoId().withMessage('Page ID must be a valid mongo ID'),
  28. body('folderId').optional({ nullable: true }).isMongoId().withMessage('Folder ID must be a valid mongo ID'),
  29. ],
  30. bookmark: [
  31. body('pageId').isMongoId().withMessage('Page ID must be a valid mongo ID'),
  32. body('status').isBoolean().withMessage('status must be one of true or false'),
  33. ],
  34. };
  35. module.exports = (crowi) => {
  36. const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
  37. const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  38. // Create new bookmark folder
  39. router.post('/', accessTokenParser, loginRequiredStrictly, validator.bookmarkFolder, apiV3FormValidator, async(req, res) => {
  40. const owner = req.user?._id;
  41. const { name, parent } = req.body;
  42. const params = {
  43. name, owner, parent,
  44. };
  45. try {
  46. const bookmarkFolder = await BookmarkFolder.createByParameters(params);
  47. logger.debug('bookmark folder created', bookmarkFolder);
  48. return res.apiv3({ bookmarkFolder });
  49. }
  50. catch (err) {
  51. logger.error(err);
  52. if (err instanceof InvalidParentBookmarkFolderError) {
  53. return res.apiv3Err(new ErrorV3(err.message, 'failed_to_create_bookmark_folder'));
  54. }
  55. return res.apiv3Err(err, 500);
  56. }
  57. });
  58. // List bookmark folders and child
  59. router.get('/list/:userId', accessTokenParser, loginRequiredStrictly, async(req, res) => {
  60. const { userId } = req.params;
  61. const getBookmarkFolders = async(
  62. userId: Types.ObjectId | string,
  63. parentFolderId?: Types.ObjectId | string,
  64. ) => {
  65. const folders = await BookmarkFolder.find({ owner: userId, parent: parentFolderId })
  66. .populate('children')
  67. .populate({
  68. path: 'bookmarks',
  69. model: 'Bookmark',
  70. populate: {
  71. path: 'page',
  72. model: 'Page',
  73. populate: {
  74. path: 'lastUpdateUser',
  75. model: 'User',
  76. },
  77. },
  78. }).exec() as never as BookmarkFolderItems[];
  79. const returnValue: BookmarkFolderItems[] = [];
  80. const promises = folders.map(async(folder: BookmarkFolderItems) => {
  81. const children = await getBookmarkFolders(userId, folder._id);
  82. // !! DO NOT THIS SERIALIZING OUTSIDE OF PROMISES !! -- 05.23.2023 ryoji-s
  83. // Serializing outside of promises will cause not populated.
  84. const bookmarks = folder.bookmarks.map(bookmark => serializeBookmarkSecurely(bookmark));
  85. const res = {
  86. _id: folder._id.toString(),
  87. name: folder.name,
  88. owner: folder.owner,
  89. bookmarks,
  90. children,
  91. parent: folder.parent,
  92. };
  93. return res;
  94. });
  95. const results = await Promise.all(promises) as unknown as BookmarkFolderItems[];
  96. returnValue.push(...results);
  97. return returnValue;
  98. };
  99. try {
  100. const bookmarkFolderItems = await getBookmarkFolders(userId, undefined);
  101. return res.apiv3({ bookmarkFolderItems });
  102. }
  103. catch (err) {
  104. logger.error(err);
  105. return res.apiv3Err(err, 500);
  106. }
  107. });
  108. // Delete bookmark folder and children
  109. router.delete('/:id', accessTokenParser, loginRequiredStrictly, async(req, res) => {
  110. const { id } = req.params;
  111. try {
  112. const result = await BookmarkFolder.deleteFolderAndChildren(id);
  113. const { deletedCount } = result;
  114. return res.apiv3({ deletedCount });
  115. }
  116. catch (err) {
  117. logger.error(err);
  118. return res.apiv3Err(err, 500);
  119. }
  120. });
  121. router.put('/', accessTokenParser, loginRequiredStrictly, validator.bookmarkFolder, async(req, res) => {
  122. const {
  123. bookmarkFolderId, name, parent, children,
  124. } = req.body;
  125. try {
  126. const bookmarkFolder = await BookmarkFolder.updateBookmarkFolder(bookmarkFolderId, name, parent, children);
  127. return res.apiv3({ bookmarkFolder });
  128. }
  129. catch (err) {
  130. logger.error(err);
  131. return res.apiv3Err(err, 500);
  132. }
  133. });
  134. router.post('/add-boookmark-to-folder', accessTokenParser, loginRequiredStrictly, validator.bookmarkPage, apiV3FormValidator, async(req, res) => {
  135. const userId = req.user?._id;
  136. const { pageId, folderId } = req.body;
  137. try {
  138. const bookmarkFolder = await BookmarkFolder.insertOrUpdateBookmarkedPage(pageId, userId, folderId);
  139. logger.debug('bookmark added to folder', bookmarkFolder);
  140. return res.apiv3({ bookmarkFolder });
  141. }
  142. catch (err) {
  143. logger.error(err);
  144. return res.apiv3Err(err, 500);
  145. }
  146. });
  147. router.put('/update-bookmark', accessTokenParser, loginRequiredStrictly, validator.bookmark, async(req, res) => {
  148. const { pageId, status } = req.body;
  149. const userId = req.user?._id;
  150. try {
  151. const bookmarkFolder = await BookmarkFolder.updateBookmark(pageId, status, userId);
  152. return res.apiv3({ bookmarkFolder });
  153. }
  154. catch (err) {
  155. logger.error(err);
  156. return res.apiv3Err(err, 500);
  157. }
  158. });
  159. return router;
  160. };