|
|
|
// 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({ 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 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); },
|
|
|
|
};
|