Skip to main content
Glama
pageService.js3.95 kB
import sanitizeHtml from 'sanitize-html'; import Joi from 'joi'; import { createContextLogger } from '../utils/logger.js'; import { createPage as createGhostPage } from './ghostServiceImproved.js'; /** * Helper to generate a simple meta description from HTML content. * Uses sanitize-html to safely strip HTML tags and truncates. * @param {string} htmlContent - The HTML content of the page. * @param {number} maxLength - The maximum length of the description. * @returns {string} A plain text truncated description. */ const generateSimpleMetaDescription = (htmlContent, maxLength = 500) => { if (!htmlContent) return ''; // Use sanitize-html to safely remove all HTML tags // This prevents ReDoS attacks and properly handles malformed HTML const textContent = sanitizeHtml(htmlContent, { allowedTags: [], // Remove all HTML tags allowedAttributes: {}, textFilter: function (text) { return text.replace(/\s\s+/g, ' ').trim(); }, }); // Truncate and add ellipsis if needed return textContent.length > maxLength ? textContent.substring(0, maxLength - 3) + '...' : textContent; }; /** * Validation schema for page input * Pages are similar to posts but do NOT support tags */ const pageInputSchema = Joi.object({ title: Joi.string().max(255).required(), html: Joi.string().required(), custom_excerpt: Joi.string().max(500).optional(), status: Joi.string().valid('draft', 'published', 'scheduled').optional(), published_at: Joi.string().isoDate().optional(), // NO tags field - pages don't support tags feature_image: Joi.string().uri().optional(), feature_image_alt: Joi.string().max(255).optional(), feature_image_caption: Joi.string().max(500).optional(), meta_title: Joi.string().max(70).optional(), meta_description: Joi.string().max(160).optional(), }); /** * Service layer function to handle the business logic of creating a page. * Transforms input data, generates metadata defaults. * Note: Pages do NOT support tags (unlike posts). * @param {object} pageInput - Data received from the controller. * @returns {Promise<object>} The created page object from the Ghost API. */ const createPageService = async (pageInput) => { const logger = createContextLogger('page-service'); // Validate input to prevent format string vulnerabilities const { error, value: validatedInput } = pageInputSchema.validate(pageInput); if (error) { logger.error('Page input validation failed', { error: error.details[0].message, inputKeys: Object.keys(pageInput), }); throw new Error(`Invalid page input: ${error.details[0].message}`); } const { title, html, custom_excerpt, status, published_at, // NO tags destructuring - pages don't support tags feature_image, feature_image_alt, feature_image_caption, meta_title, meta_description, } = validatedInput; // NO tag resolution section (removed from postService) // Pages do not support tags in Ghost CMS // Metadata defaults const finalMetaTitle = meta_title || title; const finalMetaDescription = meta_description || custom_excerpt || generateSimpleMetaDescription(html); const truncatedMetaDescription = finalMetaDescription.length > 500 ? finalMetaDescription.substring(0, 497) + '...' : finalMetaDescription; // Prepare data for Ghost API const pageDataForApi = { title, html, custom_excerpt, status: status || 'draft', published_at, // NO tags field feature_image, feature_image_alt, feature_image_caption, meta_title: finalMetaTitle, meta_description: truncatedMetaDescription, }; logger.info('Creating Ghost page', { title: pageDataForApi.title, status: pageDataForApi.status, hasFeatureImage: !!pageDataForApi.feature_image, }); const newPage = await createGhostPage(pageDataForApi); return newPage; }; export { createPageService };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jgardner04/Ghost-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server