// page.js // Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; const striptags = require('striptags'); const slug = require('slug'); const { SiteService, SiteError } = require('../../lib/site-lib'); const mongoose = require('mongoose'); const ObjectId = mongoose.Types.ObjectId; const Page = mongoose.model('Page'); class PageService extends SiteService { constructor (dtp) { super(dtp, module.exports); } async menuMiddleware (req, res, next) { try { const pages = await Page .find({ status: 'published', parent: { $exists: false } }) .select('slug menu') .lean(); res.locals.mainMenu = pages .filter((page) => !page.parent) .map((page) => { return { url: `/page/${page.slug}`, slug: page.slug, icon: page.menu.icon, label: page.menu.label, order: page.menu.order, }; }) .sort((a, b) => { return a.order < b.order; }); return next(); } catch (error) { this.log.error('failed to build page menu', { error }); return next(); } } async createPlaceholder (author) { const NOW = new Date(); if (!author.flags.isAdmin) { throw new SiteError(403, 'You are not permitted to author pages'); } let page = new Page(); page.created = NOW; page.authorType = author.type; page.author = author._id; page.title = "New Draft page"; page.slug = `draft-page-${page._id}`; await page.save(); page = page.toObject(); page.author = author; // self-populate instead of calling db return page; } async create (author, pageDefinition) { if (!author.permissions.canAuthorPages) { throw new SiteError(403, 'You are not permitted to author pages'); } const page = new Page(); page.title = striptags(pageDefinition.title.trim()); page.slug = this.createPageSlug(page._id, page.title); page.content = pageDefinition.content.trim(); page.status = pageDefinition.status || 'draft'; page.menu = { icon: striptags((pageDefinition.menuIcon || 'fa-slash').trim().toLowerCase()), label: striptags((pageDefinition.menuLabel || page.title.slice(0, 10))), order: parseInt(pageDefinition.menuOrder || '0', 10), }; if (pageDefinition.parentPageId && (pageDefinition.parentPageId !== 'none')) { page.menu.parent = pageDefinition.parentPageId; } await page.save(); return page.toObject(); } async update (user, page, pageDefinition) { const NOW = new Date(); const updateOp = { $set: { updated: NOW, }, }; // if (!user.permissions.canAuthorPages) { // throw new SiteError(403, 'You are not permitted to author or change pages.'); // } if (pageDefinition.title) { updateOp.$set.title = striptags(pageDefinition.title.trim()); } if (pageDefinition.slug) { let pageSlug = striptags(slug(pageDefinition.slug.trim())).split('-'); while (ObjectId.isValid(pageSlug[pageSlug.length - 1])) { pageSlug.pop(); } pageSlug = pageSlug.splice(0, 4); pageSlug.push(page._id.toString()); updateOp.$set.slug = `${pageSlug.join('-')}`; } if (pageDefinition.summary) { updateOp.$set.summary = striptags(pageDefinition.summary.trim()); } if (pageDefinition.content) { updateOp.$set.content = pageDefinition.content.trim(); } updateOp.$set.menu = { icon: striptags((pageDefinition.menuIcon || 'fa-slash').trim().toLowerCase()), label: striptags((pageDefinition.menuLabel || page.title.slice(0, 10))), order: parseInt(pageDefinition.menuOrder || '0', 10), }; if (pageDefinition.parentPageId && (pageDefinition.parentPageId !== 'none')) { updateOp.$set.menu.parent = pageDefinition.parentPageId; } // if old status is not published and new status is published, we have to // verify that the calling user has Publisher privileges. if ((page.status !== 'published') && (pageDefinition.status === 'published')) { if (!user.permissions.canPublishPages) { throw new SiteError(403, 'You are not permitted to publish pages'); } updateOp.$set.status = striptags(pageDefinition.status.trim()); } await Page.updateOne( { _id: page._id }, updateOp, { upsert: true }, ); } async getPages (pagination, status = ['published']) { if (!Array.isArray(status)) { status = [status]; } const pages = await Page .find({ status: { $in: status } }) .sort({ created: -1 }) .skip(pagination.skip) .limit(pagination.cpp) .lean(); return pages; } async getById (pageId) { const page = await Page .findById(pageId) .select('+content') .lean(); return page; } async getBySlug (pageSlug) { const slugParts = pageSlug.split('-'); const pageId = slugParts[slugParts.length - 1]; return this.getById(pageId); } async getAvailablePages (excludedPageIds) { const search = { }; if (excludedPageIds) { search._id = { $nin: excludedPageIds }; } const pages = await Page.find(search).lean(); return pages; } async deletePage (page) { this.log.info('deleting page', { pageId: page._id }); await Page.deleteOne({ _id: page._id }); } createPageSlug (pageId, pageTitle) { if ((typeof pageTitle !== 'string') || (pageTitle.length < 1)) { throw new Error('Invalid input for making a page slug'); } const pageSlug = slug(pageTitle.trim().toLowerCase()).split('-').slice(0, 4).join('-'); return `${pageSlug}-${pageId}`; } } module.exports = { slug: 'page', name: 'page', create: (dtp) => { return new PageService(dtp); }, };