import { isValidObjectId } from '@growi/core/src/utils/objectid-utils'; import { Types, Document, Model, Schema, } from 'mongoose'; import { IBookmarkFolder, BookmarkFolderItems } from '~/interfaces/bookmark-info'; import loggerFactory from '../../utils/logger'; import { getOrCreateModel } from '../util/mongoose-utils'; import { InvalidParentBookmarkFolderError } from './errors'; const logger = loggerFactory('growi:models:bookmark-folder'); export interface BookmarkFolderDocument extends Document { _id: Types.ObjectId name: string owner: Types.ObjectId parent?: [this] } export interface BookmarkFolderModel extends Model{ createByParameters(params: IBookmarkFolder): BookmarkFolderDocument findFolderAndChildren(user: Types.ObjectId | string, parentId?: Types.ObjectId | string): BookmarkFolderItems[] findChildFolderById(parentBookmarkFolder: Types.ObjectId | string): Promise deleteFolderAndChildren(bookmarkFolderId: string): {deletedCount: number} updateBookmarkFolder(bookmarkFolderId: string, name: string, parent: string): BookmarkFolderDocument | null } const bookmarkFolderSchema = new Schema({ name: { type: String }, owner: { type: Schema.Types.ObjectId, ref: 'User', index: true }, parent: { type: Schema.Types.ObjectId, ref: 'BookmarkFolder', required: false }, }, { toObject: { virtuals: true }, }); bookmarkFolderSchema.virtual('children', { ref: 'BookmarkFolder', localField: '_id', foreignField: 'parent', }); bookmarkFolderSchema.statics.createByParameters = async function(params: IBookmarkFolder): Promise { const { name, owner, parent } = params; let bookmarkFolder; if (parent == null) { bookmarkFolder = await this.create({ name, owner }) as unknown as BookmarkFolderDocument; } else { // Check if parent folder id is valid and parent folder exists const isParentFolderIdValid = isValidObjectId(parent as string); if (!isParentFolderIdValid) { throw new InvalidParentBookmarkFolderError('Parent folder id is invalid'); } const parentFolder = await this.findById(parent); if (parentFolder == null) { throw new InvalidParentBookmarkFolderError('Parent folder not found'); } bookmarkFolder = await this.create({ name, owner, parent: parentFolder._id }) as unknown as BookmarkFolderDocument; } return bookmarkFolder; }; bookmarkFolderSchema.statics.findFolderAndChildren = async function( userId: Types.ObjectId | string, parentId?: Types.ObjectId | string, ): Promise { const parentFolder = await this.findById(parentId) as unknown as BookmarkFolderDocument; const bookmarkFolders = await this.find({ owner: userId, parent: parentFolder }) .populate({ path: 'children' }).exec() as unknown as BookmarkFolderItems[]; return bookmarkFolders; }; bookmarkFolderSchema.statics.findChildFolderById = async function(parentFolderId: Types.ObjectId | string): Promise { const parentFolder = await this.findById(parentFolderId) as unknown as BookmarkFolderDocument; const childFolders = await this.find({ parent: parentFolder }); return childFolders; }; bookmarkFolderSchema.statics.deleteFolderAndChildren = async function(boookmarkFolderId: Types.ObjectId | string): Promise<{deletedCount: number}> { // Delete parent and all children folder const bookmarkFolder = await this.findByIdAndDelete(boookmarkFolderId); let deletedCount = 0; if (bookmarkFolder != null) { const childFolders = await this.deleteMany({ parent: bookmarkFolder?.id }); deletedCount = childFolders.deletedCount + 1; } return { deletedCount }; }; bookmarkFolderSchema.statics.updateBookmarkFolder = async function(bookmarkFolderId: string, name: string, parent: string): Promise { const parentFolder = await this.findById(parent); const updateFields = { name, parent: parentFolder?._id || null, }; const bookmarkFolder = await this.findByIdAndUpdate(bookmarkFolderId, { $set: updateFields }, { new: true }); return bookmarkFolder; }; export default getOrCreateModel('BookmarkFolder', bookmarkFolderSchema);