const Controller = require('libflitter/controller/Controller') const { PageType } = require('../../../enum') /* * Menu Controller * ------------------------------------------------------------- * Put some description here! */ class Menu extends Controller { static get services() { return [...super.services, 'models'] } async get_items(req, res) { const virtual_root = req.query.virtualRootPageId const Page = this.models.get('api:Page') const page_only = req.query.type === 'page' if ( !virtual_root ) { return this.get_top_level_menu(req, res); } else { const root_page = await Page.findOne({ UUID: virtual_root }) if ( !root_page || !(await root_page.is_accessible_by(req.user, 'view')) ) { return res.status(404) .message('Invalid virtual root page ID.') .api() } const nodes = await this._build_menu_object(req.user, root_page, [], page_only) const menu = [] if ( req.user.preferences?.bookmark_page_ids?.length ) { menu.push({ id: 0, name: 'My Bookmarks', children: await this.get_bookmarks(req.user), noDelete: true, noChildren: true, virtual: true, type: PageType.Branch, }) } const virtual_page = { id: root_page.UUID, children: [...(await root_page.get_menu_items(page_only)), ...nodes], name: root_page.is_shared() ? root_page.Name + ' ⁽ˢʰᵃʳᵉᵈ⁾' : root_page.Name, shared: root_page.is_shared(), type: PageType.Note, }; menu.push({ id: 0, name: 'Virtual Tree Root', children: [virtual_page], noDelete: true, noChildren: true, virtual: true, type: PageType.Branch, }) return res.api(menu) } } async get_top_level_menu(req, res) { const Page = this.models.get('api:Page') const page_only = req.query.type === 'page' // Build the "My Tree" option const root_page = await req.user.get_root_page() const nodes = await this._build_menu_object(req.user, root_page, [], page_only) const menu = [] if ( req.user.preferences?.bookmark_page_ids?.length ) { menu.push({ id: 0, name: 'My Bookmarks', children: await this.get_bookmarks(req.user), noDelete: true, noChildren: true, virtual: true, type: PageType.Branch, }) } menu.push({ id: 0, name: 'My Info Tree', children: nodes, noDelete: true, noChildren: true, virtual: true, type: PageType.Branch, userRootPage: true, }) // Get view only shared trees const view_only_trees = await Page.find({ shared_users_view: req.user._id }) const view_only_nodes = [] for ( const tree of view_only_trees ) { if ( !(await tree.is_accessible_by(req.user)) ) continue view_only_nodes.push({ id: tree.UUID, name: tree.Name, children: await this._build_secure_menu_object(tree, req.user), level: await tree.access_level_for(req.user), type: tree.PageType || PageType.Note, bookmark: (req.user.preferences.bookmark_page_ids || []).includes(tree.UUID), }) } // Get update, view shared trees const update_trees = await Page.find({ shared_users_update: req.user._id }) const update_nodes = [] for ( const tree of update_trees ) { if ( !(await tree.is_accessible_by(req.user)) ) continue update_nodes.push({ id: tree.UUID, name: tree.Name, children: await this._build_secure_menu_object(tree, req.user), level: await tree.access_level_for(req.user), type: tree.PageType || PageType.Note, bookmark: (req.user.preferences.bookmark_page_ids || []).includes(tree.UUID), }) } // Get update, view, manage shared trees const manage_trees = await Page.find({ shared_users_manage: req.user._id }) const manage_nodes = [] for ( const tree of manage_trees ) { if ( !(await tree.is_accessible_by(req.user)) ) continue manage_nodes.push({ id: tree.UUID, name: tree.Name, children: await this._build_secure_menu_object(tree, req.user), level: await tree.access_level_for(req.user), type: tree.PageType || PageType.Note, bookmark: (req.user.preferences.bookmark_page_ids || []).includes(tree.UUID), }) } menu.push({ id: 0, name: 'Trees Shared With Me', children: [...view_only_nodes, ...update_nodes, ...manage_nodes], noDelete: true, noChildren: true, virtual: true, type: PageType.Branch, }) return res.api(menu) } async get_bookmarks(user) { const pages = await user.get_bookmarked_pages() const items = [] for ( const page of pages ) { items.push({ id: page.UUID, name: page.is_shared() ? page.Name + ' ⁽ˢʰᵃʳᵉᵈ⁾' : page.Name, shared: page.is_shared(), bookmark: true, type: page.PageType || PageType.Note, children: [], }) } return items } async move_node(req, res, next) { const Page = this.models.get('api:Page'); if ( !req.body.MovedPageId ) { return res.status(400) .message('Missing MovedPageId.') .api() } if ( !req.body.ParentPageId && req.body.ParentPageId !== 0 ) { return res.status(400) .message('Missing ParentPageId.') .api() } const moved_page = await Page.findOne({ UUID: req.body.MovedPageId, Active: true }) if ( !moved_page || !(await moved_page.is_accessible_by(req.user, 'manage')) ) { return res.status(400) .message('You do not have permission to move this page.') .api() } const parent_page = req.body.ParentPageId === 0 ? await req.user.get_root_page() : await Page.findOne({ UUID: req.body.ParentPageId, Active: true }) if ( !parent_page || !(await parent_page.is_accessible_by(req.user, 'manage')) ) { return res.status(400) .message('You do not have permission to move into that page.') .api() } // For now, disallow moving pages between users if ( `${moved_page.OrgUserId}` !== `${parent_page.OrgUserId}` ) { return res.status(400) .message('Moving pages between user accounts is not supported at this time.') .api() } if ( !moved_page.ParentId || moved_page.ParentId === '0' ) { return res.status(400) .message('You cannot move a root page node.') .api() } const old_parent = await moved_page.parent moved_page.ParentId = parent_page.UUID; old_parent.ChildPageIds = old_parent.ChildPageIds.filter(x => x !== moved_page.UUID); parent_page.ChildPageIds.push(moved_page.UUID); await parent_page.save(); await moved_page.save(); await old_parent.save(); return res.api(); } async _build_menu_object(user, parent_node, arr= [], page_only = false) { const children = await this.models.get('api:Page').find({UUID: {$in: parent_node.ChildPageIds}}) if ( children ) { for ( const child of children ) { arr.push({ id: child.UUID, name: child.is_shared() ? child.Name + ' ⁽ˢʰᵃʳᵉᵈ⁾' : child.Name, shared: child.is_shared(), children: [...(await child.get_menu_items(page_only)), ...(await this._build_menu_object(user, child, [], page_only))], type: child.PageType || PageType.Note, bookmark: (user.preferences.bookmark_page_ids || []).includes(child.UUID), }) } } return arr } async _build_secure_menu_object(parent_node, user, arr = []) { const children = await parent_node.childPages if ( children ) { for ( const child of children ) { if ( !(await child.is_accessible_by(user)) ) continue arr.push({ id: child.UUID, name: child.Name, children: await this._build_secure_menu_object(child, user), level: await child.access_level_for(user), type: child.PageType || PageType.Note, bookmark: (user.preferences.bookmark_page_ids || []).includes(child.UUID), }) } } return arr } } module.exports = exports = Menu