bookmark-folder.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import { isValidObjectId } from '@growi/core/src/utils/objectid-utils';
  2. import monggoose, {
  3. Types, Document, Model, Schema,
  4. } from 'mongoose';
  5. import { IBookmarkFolder, BookmarkFolderItems } from '~/interfaces/bookmark-info';
  6. import { IPageHasId } from '~/interfaces/page';
  7. import loggerFactory from '../../utils/logger';
  8. import { getOrCreateModel } from '../util/mongoose-utils';
  9. import { InvalidParentBookmarkFolderError } from './errors';
  10. const logger = loggerFactory('growi:models:bookmark-folder');
  11. const Bookmark = monggoose.model('Bookmark');
  12. export interface BookmarkFolderDocument extends Document {
  13. _id: Types.ObjectId
  14. name: string
  15. owner: Types.ObjectId
  16. parent?: this[]
  17. bookmarks?: Types.ObjectId[]
  18. }
  19. export interface BookmarkFolderModel extends Model<BookmarkFolderDocument>{
  20. createByParameters(params: IBookmarkFolder): Promise<BookmarkFolderDocument>
  21. findFolderAndChildren(user: Types.ObjectId | string, parentId?: Types.ObjectId | string): Promise<BookmarkFolderItems[]>
  22. deleteFolderAndChildren(bookmarkFolderId: Types.ObjectId | string): Promise<{deletedCount: number}>
  23. updateBookmarkFolder(bookmarkFolderId: string, name: string, parent: string): Promise<BookmarkFolderDocument>
  24. insertOrUpdateBookmarkedPage(pageId: IPageHasId, userId: Types.ObjectId | string, folderId: string): Promise<BookmarkFolderDocument>
  25. }
  26. const bookmarkFolderSchema = new Schema<BookmarkFolderDocument, BookmarkFolderModel>({
  27. name: { type: String },
  28. owner: { type: Schema.Types.ObjectId, ref: 'User', index: true },
  29. parent: { type: Schema.Types.ObjectId, ref: 'BookmarkFolder', required: false },
  30. bookmarks: {
  31. type: [{
  32. type: Schema.Types.ObjectId, ref: 'Bookmark', required: false,
  33. }],
  34. required: false,
  35. default: [],
  36. },
  37. }, {
  38. toObject: { virtuals: true },
  39. });
  40. bookmarkFolderSchema.virtual('children', {
  41. ref: 'BookmarkFolder',
  42. localField: '_id',
  43. foreignField: 'parent',
  44. });
  45. bookmarkFolderSchema.statics.createByParameters = async function(params: IBookmarkFolder): Promise<BookmarkFolderDocument> {
  46. const { name, owner, parent } = params;
  47. let bookmarkFolder: BookmarkFolderDocument;
  48. if (parent == null) {
  49. bookmarkFolder = await this.create({ name, owner });
  50. }
  51. else {
  52. // Check if parent folder id is valid and parent folder exists
  53. const isParentFolderIdValid = isValidObjectId(parent as string);
  54. if (!isParentFolderIdValid) {
  55. throw new InvalidParentBookmarkFolderError('Parent folder id is invalid');
  56. }
  57. const parentFolder = await this.findById(parent);
  58. if (parentFolder == null) {
  59. throw new InvalidParentBookmarkFolderError('Parent folder not found');
  60. }
  61. bookmarkFolder = await this.create({ name, owner, parent: parentFolder._id });
  62. }
  63. return bookmarkFolder;
  64. };
  65. bookmarkFolderSchema.statics.findFolderAndChildren = async function(
  66. userId: Types.ObjectId | string,
  67. parentId?: Types.ObjectId | string,
  68. ): Promise<BookmarkFolderItems[]> {
  69. let parentFolder: BookmarkFolderDocument | null;
  70. let query = {};
  71. // Load child bookmark folders
  72. if (parentId != null) {
  73. parentFolder = await this.findById(parentId);
  74. if (parentFolder != null) {
  75. query = { owner: userId, parent: parentFolder };
  76. }
  77. else {
  78. throw new InvalidParentBookmarkFolderError('Parent folder not found');
  79. }
  80. }
  81. // Load initial / root bookmark folders
  82. else {
  83. query = { owner: userId, parent: null };
  84. }
  85. const bookmarkFolders: BookmarkFolderItems[] = await this.find(query)
  86. .populate({ path: 'children' })
  87. .populate({
  88. path: 'bookmarks',
  89. model: 'Bookmark',
  90. populate: {
  91. path: 'page',
  92. model: 'Page',
  93. },
  94. });
  95. return bookmarkFolders;
  96. };
  97. bookmarkFolderSchema.statics.deleteFolderAndChildren = async function(bookmarkFolderId: Types.ObjectId | string): Promise<{deletedCount: number}> {
  98. const bookmarkFolder = await this.findById(bookmarkFolderId);
  99. // Delete parent and all children folder
  100. let deletedCount = 0;
  101. if (bookmarkFolder != null) {
  102. // Delete Bookmarks
  103. const bookmarks = bookmarkFolder?.bookmarks;
  104. if (bookmarks && bookmarks.length > 0) {
  105. await Bookmark.deleteMany({ _id: { $in: bookmarks } });
  106. }
  107. // Delete all child recursively and update deleted count
  108. const childFolders = await this.find({ parent: bookmarkFolder });
  109. await Promise.all(childFolders.map(async(child) => {
  110. const deletedChildFolder = await this.deleteFolderAndChildren(child._id);
  111. deletedCount += deletedChildFolder.deletedCount;
  112. }));
  113. const deletedChild = await this.deleteMany({ parent: bookmarkFolder });
  114. deletedCount += deletedChild.deletedCount + 1;
  115. bookmarkFolder.delete();
  116. }
  117. return { deletedCount };
  118. };
  119. bookmarkFolderSchema.statics.updateBookmarkFolder = async function(bookmarkFolderId: string, name: string, parentId: string):
  120. Promise<BookmarkFolderDocument> {
  121. const parentFolder = await this.findById(parentId);
  122. const updateFields = {
  123. name, parent: parentFolder?._id || null,
  124. };
  125. const bookmarkFolder = await this.findByIdAndUpdate(bookmarkFolderId, { $set: updateFields }, { new: true });
  126. if (bookmarkFolder == null) {
  127. throw new Error('Update bookmark folder failed');
  128. }
  129. return bookmarkFolder;
  130. };
  131. bookmarkFolderSchema.statics.insertOrUpdateBookmarkedPage = async function(pageId: IPageHasId, userId: Types.ObjectId | string, folderId: string):
  132. Promise<BookmarkFolderDocument> {
  133. // Create bookmark or update existing
  134. const bookmarkedPage = await Bookmark.findOneAndUpdate({ page: pageId, user: userId }, { page: pageId, user: userId }, { new: true, upsert: true });
  135. // Remove existing bookmark in bookmark folder
  136. await this.updateMany({}, { $pull: { bookmarks: bookmarkedPage._id } });
  137. // Insert bookmark into bookmark folder
  138. const bookmarkFolder = await this.findByIdAndUpdate(folderId, { $addToSet: { bookmarks: bookmarkedPage } }, { new: true, upsert: true });
  139. return bookmarkFolder;
  140. };
  141. export default getOrCreateModel<BookmarkFolderDocument, BookmarkFolderModel>('BookmarkFolder', bookmarkFolderSchema);