@jgardner04/ghost-mcp-server 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,121 @@
1
+ import sanitizeHtml from 'sanitize-html';
2
+ import Joi from 'joi';
3
+ import { createContextLogger } from '../utils/logger.js';
4
+ import { createPage as createGhostPage } from './ghostServiceImproved.js';
5
+
6
+ /**
7
+ * Helper to generate a simple meta description from HTML content.
8
+ * Uses sanitize-html to safely strip HTML tags and truncates.
9
+ * @param {string} htmlContent - The HTML content of the page.
10
+ * @param {number} maxLength - The maximum length of the description.
11
+ * @returns {string} A plain text truncated description.
12
+ */
13
+ const generateSimpleMetaDescription = (htmlContent, maxLength = 500) => {
14
+ if (!htmlContent) return '';
15
+
16
+ // Use sanitize-html to safely remove all HTML tags
17
+ // This prevents ReDoS attacks and properly handles malformed HTML
18
+ const textContent = sanitizeHtml(htmlContent, {
19
+ allowedTags: [], // Remove all HTML tags
20
+ allowedAttributes: {},
21
+ textFilter: function (text) {
22
+ return text.replace(/\s\s+/g, ' ').trim();
23
+ },
24
+ });
25
+
26
+ // Truncate and add ellipsis if needed
27
+ return textContent.length > maxLength
28
+ ? textContent.substring(0, maxLength - 3) + '...'
29
+ : textContent;
30
+ };
31
+
32
+ /**
33
+ * Validation schema for page input
34
+ * Pages are similar to posts but do NOT support tags
35
+ */
36
+ const pageInputSchema = Joi.object({
37
+ title: Joi.string().max(255).required(),
38
+ html: Joi.string().required(),
39
+ custom_excerpt: Joi.string().max(500).optional(),
40
+ status: Joi.string().valid('draft', 'published', 'scheduled').optional(),
41
+ published_at: Joi.string().isoDate().optional(),
42
+ // NO tags field - pages don't support tags
43
+ feature_image: Joi.string().uri().optional(),
44
+ feature_image_alt: Joi.string().max(255).optional(),
45
+ feature_image_caption: Joi.string().max(500).optional(),
46
+ meta_title: Joi.string().max(70).optional(),
47
+ meta_description: Joi.string().max(160).optional(),
48
+ });
49
+
50
+ /**
51
+ * Service layer function to handle the business logic of creating a page.
52
+ * Transforms input data, generates metadata defaults.
53
+ * Note: Pages do NOT support tags (unlike posts).
54
+ * @param {object} pageInput - Data received from the controller.
55
+ * @returns {Promise<object>} The created page object from the Ghost API.
56
+ */
57
+ const createPageService = async (pageInput) => {
58
+ const logger = createContextLogger('page-service');
59
+
60
+ // Validate input to prevent format string vulnerabilities
61
+ const { error, value: validatedInput } = pageInputSchema.validate(pageInput);
62
+ if (error) {
63
+ logger.error('Page input validation failed', {
64
+ error: error.details[0].message,
65
+ inputKeys: Object.keys(pageInput),
66
+ });
67
+ throw new Error(`Invalid page input: ${error.details[0].message}`);
68
+ }
69
+
70
+ const {
71
+ title,
72
+ html,
73
+ custom_excerpt,
74
+ status,
75
+ published_at,
76
+ // NO tags destructuring - pages don't support tags
77
+ feature_image,
78
+ feature_image_alt,
79
+ feature_image_caption,
80
+ meta_title,
81
+ meta_description,
82
+ } = validatedInput;
83
+
84
+ // NO tag resolution section (removed from postService)
85
+ // Pages do not support tags in Ghost CMS
86
+
87
+ // Metadata defaults
88
+ const finalMetaTitle = meta_title || title;
89
+ const finalMetaDescription =
90
+ meta_description || custom_excerpt || generateSimpleMetaDescription(html);
91
+ const truncatedMetaDescription =
92
+ finalMetaDescription.length > 500
93
+ ? finalMetaDescription.substring(0, 497) + '...'
94
+ : finalMetaDescription;
95
+
96
+ // Prepare data for Ghost API
97
+ const pageDataForApi = {
98
+ title,
99
+ html,
100
+ custom_excerpt,
101
+ status: status || 'draft',
102
+ published_at,
103
+ // NO tags field
104
+ feature_image,
105
+ feature_image_alt,
106
+ feature_image_caption,
107
+ meta_title: finalMetaTitle,
108
+ meta_description: truncatedMetaDescription,
109
+ };
110
+
111
+ logger.info('Creating Ghost page', {
112
+ title: pageDataForApi.title,
113
+ status: pageDataForApi.status,
114
+ hasFeatureImage: !!pageDataForApi.feature_image,
115
+ });
116
+
117
+ const newPage = await createGhostPage(pageDataForApi);
118
+ return newPage;
119
+ };
120
+
121
+ export { createPageService };