@rimelight/cms 0.0.9 → 0.0.11
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.
- package/dist/index.d.mts +1832 -16
- package/dist/index.mjs +1534 -218
- package/dist/schema/index.d.mts +257 -0
- package/dist/schema/index.mjs +18 -2
- package/dist/schema/sqlite.d.mts +232 -0
- package/dist/schema/sqlite.mjs +15 -1
- package/package.json +22 -11
- package/src/admin/api/media-file.ts +0 -1
- package/src/admin/api/media.ts +0 -2
- package/src/admin/layouts/CMSDashboardLayout.astro +7 -1
- package/src/admin/layouts/DefaultParentLayout.astro +0 -1
- package/src/admin/pages/pages-edit.astro +4 -1
- package/src/admin/pages/pages-index.astro +262 -101
- package/src/admin/pages/pages-preview.astro +5 -2
- package/src/admin/pages/templates-index.astro +224 -54
- package/src/admin/pages/users.astro +96 -50
- package/src/admin/pages/versions.astro +49 -37
- package/src/astro/blocks/DialogueBlock.astro +25 -0
- package/src/astro/blocks/LivePreviewBlock.astro +33 -0
- package/src/astro/blocks/SceneBlock.astro +39 -0
- package/src/astro/blocks/ScriptBlock.astro +77 -0
- package/src/astro/blocks/TableBlock.astro +30 -37
- package/src/astro/blocks/index.ts +9 -1
- package/src/env.d.ts +7 -1
- package/src/loader/live.ts +1 -2
- package/src/mcp/index.ts +1290 -32
- package/src/schema/content_types.ts +59 -0
- package/src/schema/index.ts +1 -0
- package/src/schema/sqlite.ts +35 -0
- package/src/services/search-indexer.ts +20 -9
- package/src/services/site-settings.ts +18 -14
- package/src/services/template-seeder.ts +249 -0
- package/src/storage/index.ts +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { bylines, contentSearchIndex, pageDraftLocks, pageDrafts, pageTaxonomyTerms, pageTemplates, pageVersionApprovals, pageVersionComments, pageVersions, pages, siteSettings, taxonomyTerms } from "./schema/index.mjs";
|
|
1
|
+
import { bylines, contentSearchIndex, contentTypes, pageDraftLocks, pageDrafts, pageTaxonomyTerms, pageTemplates, pageVersionApprovals, pageVersionComments, pageVersions, pages, siteSettings, taxonomyTerms } from "./schema/index.mjs";
|
|
2
2
|
import { rimelightCms } from "./integration.mjs";
|
|
3
3
|
import { deleteR2File, getR2Bucket, listR2Files, r2, uploadR2File } from "./storage/index.mjs";
|
|
4
4
|
import { auth0Auth, cfAccessAuth, evaluateAccess, hasPermission, hasRole, mockAuth } from "@rimelight/auth";
|
|
5
|
-
import { and, asc, desc, eq, isNotNull, isNull, lt, lte, or, sql } from "drizzle-orm";
|
|
5
|
+
import { and, asc, desc, eq, isNotNull, isNull, like, lt, lte, or, sql } from "drizzle-orm";
|
|
6
6
|
import { addToast, clearToasts, removeToast, showErrorToast, showSuccessToast, toast, toasts } from "@rimelight/ui/components/toast/store.ts";
|
|
7
7
|
//#region src/core/types/blocks.ts
|
|
8
8
|
const MIN_SECTION_HEADING_LEVEL = 2;
|
|
@@ -1274,17 +1274,27 @@ const DEFAULT_SITE_SETTINGS = {
|
|
|
1274
1274
|
maxDescriptionLength: 160
|
|
1275
1275
|
}
|
|
1276
1276
|
};
|
|
1277
|
+
function fallbackSettings(overrides) {
|
|
1278
|
+
return {
|
|
1279
|
+
id: overrides?.id ?? "default",
|
|
1280
|
+
name: overrides?.name ?? DEFAULT_SITE_SETTINGS.name,
|
|
1281
|
+
description: overrides?.description ?? DEFAULT_SITE_SETTINGS.description,
|
|
1282
|
+
url: overrides?.url ?? DEFAULT_SITE_SETTINGS.url,
|
|
1283
|
+
ogImage: overrides?.ogImage ?? DEFAULT_SITE_SETTINGS.ogImage,
|
|
1284
|
+
author: overrides?.author ?? DEFAULT_SITE_SETTINGS.author,
|
|
1285
|
+
email: overrides?.email ?? "",
|
|
1286
|
+
branding: overrides?.branding ?? DEFAULT_SITE_SETTINGS.branding,
|
|
1287
|
+
seo: overrides?.seo ?? DEFAULT_SITE_SETTINGS.seo,
|
|
1288
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1289
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1277
1292
|
/**
|
|
1278
1293
|
* Retrieves the dynamic site settings from the database. If no settings are found, seeds the
|
|
1279
1294
|
* default record and returns it.
|
|
1280
1295
|
*/
|
|
1281
1296
|
async function getSiteSettings(db, fallbackDefaults) {
|
|
1282
|
-
if (!db) return
|
|
1283
|
-
...DEFAULT_SITE_SETTINGS,
|
|
1284
|
-
...fallbackDefaults,
|
|
1285
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
1286
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1287
|
-
};
|
|
1297
|
+
if (!db) return fallbackSettings(fallbackDefaults);
|
|
1288
1298
|
try {
|
|
1289
1299
|
const records = await db.select().from(siteSettings).where(eq(siteSettings.id, "default")).limit(1);
|
|
1290
1300
|
if (records.length > 0) return records[0];
|
|
@@ -1296,12 +1306,7 @@ async function getSiteSettings(db, fallbackDefaults) {
|
|
|
1296
1306
|
return (await db.insert(siteSettings).values(initial).returning())[0];
|
|
1297
1307
|
} catch (error) {
|
|
1298
1308
|
console.warn("Failed to fetch site settings from DB, using fallback:", error);
|
|
1299
|
-
return
|
|
1300
|
-
...DEFAULT_SITE_SETTINGS,
|
|
1301
|
-
...fallbackDefaults,
|
|
1302
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
1303
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1304
|
-
};
|
|
1309
|
+
return fallbackSettings(fallbackDefaults);
|
|
1305
1310
|
}
|
|
1306
1311
|
}
|
|
1307
1312
|
/**
|
|
@@ -1487,6 +1492,186 @@ async function handleCmsCron(_event, _env, _ctx, db) {
|
|
|
1487
1492
|
return result;
|
|
1488
1493
|
}
|
|
1489
1494
|
//#endregion
|
|
1495
|
+
//#region src/services/taxonomies.ts
|
|
1496
|
+
/**
|
|
1497
|
+
* Retrieves all terms for a given taxonomy. If hierarchical, returns a tree structure with
|
|
1498
|
+
* `children: TaxonomyTerm[]`.
|
|
1499
|
+
*/
|
|
1500
|
+
async function getTaxonomyTerms(db, taxonomy, options = {}) {
|
|
1501
|
+
if (!db) return [];
|
|
1502
|
+
const includeCounts = options.includeCounts ?? true;
|
|
1503
|
+
const rawTerms = await db.select().from(taxonomyTerms).where(eq(taxonomyTerms.taxonomy, taxonomy)).orderBy(asc(taxonomyTerms.displayOrder), asc(taxonomyTerms.createdAt));
|
|
1504
|
+
if (!rawTerms || rawTerms.length === 0) return [];
|
|
1505
|
+
const countsMap = /* @__PURE__ */ new Map();
|
|
1506
|
+
if (includeCounts) try {
|
|
1507
|
+
const counts = await db.select({
|
|
1508
|
+
termId: pageTaxonomyTerms.termId,
|
|
1509
|
+
count: sql`count(${pageTaxonomyTerms.pageId})::int`
|
|
1510
|
+
}).from(pageTaxonomyTerms).innerJoin(pages, eq(pages.id, pageTaxonomyTerms.pageId)).where(isNull(pages.deletedAt)).groupBy(pageTaxonomyTerms.termId);
|
|
1511
|
+
for (const row of counts) countsMap.set(row.termId, Number(row.count));
|
|
1512
|
+
} catch {}
|
|
1513
|
+
const termsWithCounts = rawTerms.map((t) => ({
|
|
1514
|
+
id: t.id,
|
|
1515
|
+
taxonomy: t.taxonomy,
|
|
1516
|
+
slug: t.slug,
|
|
1517
|
+
label: t.label,
|
|
1518
|
+
description: t.description,
|
|
1519
|
+
parentId: t.parentId,
|
|
1520
|
+
displayOrder: t.displayOrder,
|
|
1521
|
+
count: countsMap.get(t.id) ?? 0,
|
|
1522
|
+
children: [],
|
|
1523
|
+
createdAt: t.createdAt,
|
|
1524
|
+
updatedAt: t.updatedAt
|
|
1525
|
+
}));
|
|
1526
|
+
if (!termsWithCounts.some((t) => !!t.parentId)) return termsWithCounts;
|
|
1527
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
1528
|
+
const rootTerms = [];
|
|
1529
|
+
termsWithCounts.forEach((term) => {
|
|
1530
|
+
idMap.set(term.id, term);
|
|
1531
|
+
});
|
|
1532
|
+
termsWithCounts.forEach((term) => {
|
|
1533
|
+
if (term.parentId && idMap.has(term.parentId)) {
|
|
1534
|
+
const parent = idMap.get(term.parentId);
|
|
1535
|
+
parent.children = parent.children || [];
|
|
1536
|
+
parent.children.push(term);
|
|
1537
|
+
} else rootTerms.push(term);
|
|
1538
|
+
});
|
|
1539
|
+
return rootTerms;
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Retrieves a single term by taxonomy and slug.
|
|
1543
|
+
*/
|
|
1544
|
+
async function getTerm(db, taxonomy, slug) {
|
|
1545
|
+
if (!db) return null;
|
|
1546
|
+
const results = await db.select().from(taxonomyTerms).where(and(eq(taxonomyTerms.taxonomy, taxonomy), eq(taxonomyTerms.slug, slug))).limit(1);
|
|
1547
|
+
if (!results[0]) return null;
|
|
1548
|
+
let count = 0;
|
|
1549
|
+
try {
|
|
1550
|
+
const countResult = await db.select({ count: sql`count(${pageTaxonomyTerms.pageId})::int` }).from(pageTaxonomyTerms).innerJoin(pages, eq(pages.id, pageTaxonomyTerms.pageId)).where(and(eq(pageTaxonomyTerms.termId, results[0].id), isNull(pages.deletedAt)));
|
|
1551
|
+
count = Number(countResult[0]?.count ?? 0);
|
|
1552
|
+
} catch {
|
|
1553
|
+
count = 0;
|
|
1554
|
+
}
|
|
1555
|
+
return {
|
|
1556
|
+
...results[0],
|
|
1557
|
+
count,
|
|
1558
|
+
children: []
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Retrieves all terms assigned to a specific page entry.
|
|
1563
|
+
*/
|
|
1564
|
+
async function getEntryTerms(db, pageId, taxonomy) {
|
|
1565
|
+
if (!db || !pageId) return [];
|
|
1566
|
+
const conditions = [eq(pageTaxonomyTerms.pageId, pageId)];
|
|
1567
|
+
if (taxonomy) conditions.push(eq(taxonomyTerms.taxonomy, taxonomy));
|
|
1568
|
+
return await db.select({
|
|
1569
|
+
id: taxonomyTerms.id,
|
|
1570
|
+
taxonomy: taxonomyTerms.taxonomy,
|
|
1571
|
+
slug: taxonomyTerms.slug,
|
|
1572
|
+
label: taxonomyTerms.label,
|
|
1573
|
+
description: taxonomyTerms.description,
|
|
1574
|
+
parentId: taxonomyTerms.parentId,
|
|
1575
|
+
displayOrder: taxonomyTerms.displayOrder
|
|
1576
|
+
}).from(pageTaxonomyTerms).innerJoin(taxonomyTerms, eq(taxonomyTerms.id, pageTaxonomyTerms.termId)).where(and(...conditions)).orderBy(asc(taxonomyTerms.displayOrder));
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Retrieves pages tagged with a given taxonomy term.
|
|
1580
|
+
*/
|
|
1581
|
+
async function getEntriesByTerm(db, taxonomy, slug, options = {}) {
|
|
1582
|
+
if (!db) return {
|
|
1583
|
+
entries: [],
|
|
1584
|
+
total: 0
|
|
1585
|
+
};
|
|
1586
|
+
const term = await getTerm(db, taxonomy, slug);
|
|
1587
|
+
if (!term) return {
|
|
1588
|
+
entries: [],
|
|
1589
|
+
total: 0
|
|
1590
|
+
};
|
|
1591
|
+
const conditions = [eq(pageTaxonomyTerms.termId, term.id), isNull(pages.deletedAt)];
|
|
1592
|
+
if (options.type) conditions.push(eq(pages.type, options.type));
|
|
1593
|
+
let query = db.select({ page: pages }).from(pageTaxonomyTerms).innerJoin(pages, eq(pages.id, pageTaxonomyTerms.pageId)).where(and(...conditions)).orderBy(asc(pages.createdAt));
|
|
1594
|
+
if (options.limit) query = query.limit(options.limit);
|
|
1595
|
+
if (options.offset) query = query.offset(options.offset);
|
|
1596
|
+
const entries = (await query).map((r) => r.page);
|
|
1597
|
+
return {
|
|
1598
|
+
entries,
|
|
1599
|
+
total: term.count ?? entries.length,
|
|
1600
|
+
term
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Sets/syncs the assigned terms for a page within a specific taxonomy.
|
|
1605
|
+
*/
|
|
1606
|
+
async function assignEntryTerms(db, pageId, taxonomy, termIds) {
|
|
1607
|
+
if (!db || !pageId) return;
|
|
1608
|
+
const currentAssigned = await getEntryTerms(db, pageId, taxonomy);
|
|
1609
|
+
const currentTermIds = new Set(currentAssigned.map((t) => t.id));
|
|
1610
|
+
const newTermIds = new Set(termIds);
|
|
1611
|
+
for (const term of currentAssigned) if (!newTermIds.has(term.id)) await db.delete(pageTaxonomyTerms).where(and(eq(pageTaxonomyTerms.pageId, pageId), eq(pageTaxonomyTerms.termId, term.id)));
|
|
1612
|
+
for (const termId of termIds) if (!currentTermIds.has(termId)) await db.insert(pageTaxonomyTerms).values({
|
|
1613
|
+
pageId,
|
|
1614
|
+
termId
|
|
1615
|
+
}).onConflictDoNothing();
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Creates a new taxonomy term.
|
|
1619
|
+
*/
|
|
1620
|
+
async function createTerm(db, data) {
|
|
1621
|
+
return (await db.insert(taxonomyTerms).values(data).returning())[0];
|
|
1622
|
+
}
|
|
1623
|
+
/**
|
|
1624
|
+
* Updates an existing taxonomy term.
|
|
1625
|
+
*/
|
|
1626
|
+
async function updateTerm(db, id, updates) {
|
|
1627
|
+
return (await db.update(taxonomyTerms).set({
|
|
1628
|
+
...updates,
|
|
1629
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1630
|
+
}).where(eq(taxonomyTerms.id, id)).returning())[0];
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Deletes a taxonomy term.
|
|
1634
|
+
*/
|
|
1635
|
+
async function deleteTerm(db, id) {
|
|
1636
|
+
await db.delete(taxonomyTerms).where(eq(taxonomyTerms.id, id));
|
|
1637
|
+
return { success: true };
|
|
1638
|
+
}
|
|
1639
|
+
//#endregion
|
|
1640
|
+
//#region src/services/search-indexer.ts
|
|
1641
|
+
async function indexPageForSearch(db, page, locales = ["en", "pt"]) {
|
|
1642
|
+
if (!db) return;
|
|
1643
|
+
const parseJson = (val) => typeof val === "string" ? JSON.parse(val) : val;
|
|
1644
|
+
const titleObj = parseJson(page.title) || {};
|
|
1645
|
+
const blocks = (parseJson(page.content) || {}).blocks || [];
|
|
1646
|
+
for (const locale of locales) {
|
|
1647
|
+
const titleText = typeof titleObj === "object" && titleObj !== null ? titleObj[locale] || titleObj.en || Object.values(titleObj)[0] || "" : String(titleObj || "");
|
|
1648
|
+
const contentText = extractTextFromBlocks(blocks, locale);
|
|
1649
|
+
if (!titleText && !contentText) continue;
|
|
1650
|
+
try {
|
|
1651
|
+
const existing = await db.select().from(contentSearchIndex).where(and(eq(contentSearchIndex.pageId, page.id), eq(contentSearchIndex.locale, locale))).limit(1);
|
|
1652
|
+
const hasSearchVector = "searchVector" in contentSearchIndex;
|
|
1653
|
+
const updatePayload = {
|
|
1654
|
+
titleText,
|
|
1655
|
+
contentText
|
|
1656
|
+
};
|
|
1657
|
+
if (hasSearchVector) updatePayload["searchVector"] = sql`to_tsvector('english', ${titleText} || ' ' || ${contentText})`;
|
|
1658
|
+
if (existing.length > 0) await db.update(contentSearchIndex).set(updatePayload).where(eq(contentSearchIndex.id, existing[0].id));
|
|
1659
|
+
else {
|
|
1660
|
+
const insertPayload = {
|
|
1661
|
+
pageId: page.id,
|
|
1662
|
+
locale,
|
|
1663
|
+
titleText,
|
|
1664
|
+
contentText
|
|
1665
|
+
};
|
|
1666
|
+
if (hasSearchVector) insertPayload["searchVector"] = sql`to_tsvector('english', ${titleText} || ' ' || ${contentText})`;
|
|
1667
|
+
await db.insert(contentSearchIndex).values(insertPayload);
|
|
1668
|
+
}
|
|
1669
|
+
} catch (err) {
|
|
1670
|
+
console.error(`[Search Indexer] Failed to index page ${page.id} for locale ${locale}:`, err);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
//#endregion
|
|
1490
1675
|
//#region src/mcp/index.ts
|
|
1491
1676
|
const CMS_MCP_TOOLS = [
|
|
1492
1677
|
{
|
|
@@ -1497,7 +1682,7 @@ const CMS_MCP_TOOLS = [
|
|
|
1497
1682
|
properties: {
|
|
1498
1683
|
type: {
|
|
1499
1684
|
type: "string",
|
|
1500
|
-
description: "Filter by page type (e.g. 'blog', '
|
|
1685
|
+
description: "Filter by page type (e.g. 'blog', 'doc', 'legal', 'wiki')"
|
|
1501
1686
|
},
|
|
1502
1687
|
includeDrafts: {
|
|
1503
1688
|
type: "boolean",
|
|
@@ -1506,13 +1691,17 @@ const CMS_MCP_TOOLS = [
|
|
|
1506
1691
|
limit: {
|
|
1507
1692
|
type: "number",
|
|
1508
1693
|
description: "Maximum number of pages to return (default 20)"
|
|
1694
|
+
},
|
|
1695
|
+
offset: {
|
|
1696
|
+
type: "number",
|
|
1697
|
+
description: "Offset for pagination (default 0)"
|
|
1509
1698
|
}
|
|
1510
1699
|
}
|
|
1511
1700
|
}
|
|
1512
1701
|
},
|
|
1513
1702
|
{
|
|
1514
1703
|
name: "cms_get_page",
|
|
1515
|
-
description: "Get full details, block tree, and
|
|
1704
|
+
description: "Get full details, block tree, properties, bylines, and active draft for a page by slug or UUID.",
|
|
1516
1705
|
inputSchema: {
|
|
1517
1706
|
type: "object",
|
|
1518
1707
|
properties: { slugOrId: {
|
|
@@ -1522,9 +1711,105 @@ const CMS_MCP_TOOLS = [
|
|
|
1522
1711
|
required: ["slugOrId"]
|
|
1523
1712
|
}
|
|
1524
1713
|
},
|
|
1714
|
+
{
|
|
1715
|
+
name: "cms_create_page",
|
|
1716
|
+
description: "Create a new CMS page and its initial draft blocks.",
|
|
1717
|
+
inputSchema: {
|
|
1718
|
+
type: "object",
|
|
1719
|
+
properties: {
|
|
1720
|
+
slug: {
|
|
1721
|
+
type: "string",
|
|
1722
|
+
description: "Unique URL slug for the page"
|
|
1723
|
+
},
|
|
1724
|
+
type: {
|
|
1725
|
+
type: "string",
|
|
1726
|
+
description: "Page type (e.g. 'blog', 'doc', 'legal', 'wiki', 'character')"
|
|
1727
|
+
},
|
|
1728
|
+
title: {
|
|
1729
|
+
type: "object",
|
|
1730
|
+
description: "Localized title object, e.g. { en: 'Page Title' }"
|
|
1731
|
+
},
|
|
1732
|
+
description: {
|
|
1733
|
+
type: "object",
|
|
1734
|
+
description: "Localized description object, e.g. { en: 'Page description' }"
|
|
1735
|
+
},
|
|
1736
|
+
templateId: {
|
|
1737
|
+
type: "string",
|
|
1738
|
+
description: "Optional UUID of the template to base the page on"
|
|
1739
|
+
},
|
|
1740
|
+
tags: {
|
|
1741
|
+
type: "array",
|
|
1742
|
+
description: "Array of localized tags or strings"
|
|
1743
|
+
},
|
|
1744
|
+
authorIds: {
|
|
1745
|
+
type: "array",
|
|
1746
|
+
description: "Array of author user IDs"
|
|
1747
|
+
},
|
|
1748
|
+
bylines: {
|
|
1749
|
+
type: "array",
|
|
1750
|
+
description: "Array of structured byline credits [{ bylineId, role, customCredit }]"
|
|
1751
|
+
},
|
|
1752
|
+
blocks: {
|
|
1753
|
+
type: "array",
|
|
1754
|
+
description: "Initial structured CMS blocks array"
|
|
1755
|
+
},
|
|
1756
|
+
properties: {
|
|
1757
|
+
type: "object",
|
|
1758
|
+
description: "Content type specific custom properties"
|
|
1759
|
+
}
|
|
1760
|
+
},
|
|
1761
|
+
required: [
|
|
1762
|
+
"slug",
|
|
1763
|
+
"type",
|
|
1764
|
+
"title"
|
|
1765
|
+
]
|
|
1766
|
+
}
|
|
1767
|
+
},
|
|
1768
|
+
{
|
|
1769
|
+
name: "cms_update_page",
|
|
1770
|
+
description: "Update an existing CMS page's metadata, properties, bylines, or content blocks.",
|
|
1771
|
+
inputSchema: {
|
|
1772
|
+
type: "object",
|
|
1773
|
+
properties: {
|
|
1774
|
+
id: {
|
|
1775
|
+
type: "string",
|
|
1776
|
+
description: "Page UUID"
|
|
1777
|
+
},
|
|
1778
|
+
slug: {
|
|
1779
|
+
type: "string",
|
|
1780
|
+
description: "New unique slug"
|
|
1781
|
+
},
|
|
1782
|
+
title: {
|
|
1783
|
+
type: "object",
|
|
1784
|
+
description: "Localized title object"
|
|
1785
|
+
},
|
|
1786
|
+
description: {
|
|
1787
|
+
type: "object",
|
|
1788
|
+
description: "Localized description object"
|
|
1789
|
+
},
|
|
1790
|
+
tags: {
|
|
1791
|
+
type: "array",
|
|
1792
|
+
description: "Array of localized tags"
|
|
1793
|
+
},
|
|
1794
|
+
bylines: {
|
|
1795
|
+
type: "array",
|
|
1796
|
+
description: "Array of structured byline credits"
|
|
1797
|
+
},
|
|
1798
|
+
blocks: {
|
|
1799
|
+
type: "array",
|
|
1800
|
+
description: "Updated structured CMS blocks array"
|
|
1801
|
+
},
|
|
1802
|
+
properties: {
|
|
1803
|
+
type: "object",
|
|
1804
|
+
description: "Updated custom properties object"
|
|
1805
|
+
}
|
|
1806
|
+
},
|
|
1807
|
+
required: ["id"]
|
|
1808
|
+
}
|
|
1809
|
+
},
|
|
1525
1810
|
{
|
|
1526
1811
|
name: "cms_create_draft",
|
|
1527
|
-
description: "Create or update
|
|
1812
|
+
description: "Create or update an in-progress draft version for a CMS page without publishing.",
|
|
1528
1813
|
inputSchema: {
|
|
1529
1814
|
type: "object",
|
|
1530
1815
|
properties: {
|
|
@@ -1534,7 +1819,7 @@ const CMS_MCP_TOOLS = [
|
|
|
1534
1819
|
},
|
|
1535
1820
|
userId: {
|
|
1536
1821
|
type: "string",
|
|
1537
|
-
description: "User ID creating the draft"
|
|
1822
|
+
description: "User or Agent ID creating the draft"
|
|
1538
1823
|
},
|
|
1539
1824
|
blocks: {
|
|
1540
1825
|
type: "array",
|
|
@@ -1553,11 +1838,581 @@ const CMS_MCP_TOOLS = [
|
|
|
1553
1838
|
}
|
|
1554
1839
|
},
|
|
1555
1840
|
{
|
|
1556
|
-
name: "
|
|
1557
|
-
description: "
|
|
1841
|
+
name: "cms_publish_page",
|
|
1842
|
+
description: "Publish a page draft, creating a new immutable version snapshot.",
|
|
1558
1843
|
inputSchema: {
|
|
1559
1844
|
type: "object",
|
|
1560
|
-
properties: {
|
|
1845
|
+
properties: {
|
|
1846
|
+
pageId: {
|
|
1847
|
+
type: "string",
|
|
1848
|
+
description: "Page UUID"
|
|
1849
|
+
},
|
|
1850
|
+
userId: {
|
|
1851
|
+
type: "string",
|
|
1852
|
+
description: "User or Agent ID publishing the page"
|
|
1853
|
+
},
|
|
1854
|
+
changeSummary: {
|
|
1855
|
+
type: "string",
|
|
1856
|
+
description: "Summary of changes in this version"
|
|
1857
|
+
}
|
|
1858
|
+
},
|
|
1859
|
+
required: ["pageId", "userId"]
|
|
1860
|
+
}
|
|
1861
|
+
},
|
|
1862
|
+
{
|
|
1863
|
+
name: "cms_unpublish_page",
|
|
1864
|
+
description: "Unpublish a live page, reverting it to draft status.",
|
|
1865
|
+
inputSchema: {
|
|
1866
|
+
type: "object",
|
|
1867
|
+
properties: { pageId: {
|
|
1868
|
+
type: "string",
|
|
1869
|
+
description: "Page UUID to unpublish"
|
|
1870
|
+
} },
|
|
1871
|
+
required: ["pageId"]
|
|
1872
|
+
}
|
|
1873
|
+
},
|
|
1874
|
+
{
|
|
1875
|
+
name: "cms_delete_page",
|
|
1876
|
+
description: "Soft delete a CMS page by setting deleted_at.",
|
|
1877
|
+
inputSchema: {
|
|
1878
|
+
type: "object",
|
|
1879
|
+
properties: { pageId: {
|
|
1880
|
+
type: "string",
|
|
1881
|
+
description: "Page UUID to delete"
|
|
1882
|
+
} },
|
|
1883
|
+
required: ["pageId"]
|
|
1884
|
+
}
|
|
1885
|
+
},
|
|
1886
|
+
{
|
|
1887
|
+
name: "cms_list_page_versions",
|
|
1888
|
+
description: "List version history snapshots for a page.",
|
|
1889
|
+
inputSchema: {
|
|
1890
|
+
type: "object",
|
|
1891
|
+
properties: {
|
|
1892
|
+
pageId: {
|
|
1893
|
+
type: "string",
|
|
1894
|
+
description: "Page UUID"
|
|
1895
|
+
},
|
|
1896
|
+
limit: {
|
|
1897
|
+
type: "number",
|
|
1898
|
+
description: "Max versions to return (default 20)"
|
|
1899
|
+
}
|
|
1900
|
+
},
|
|
1901
|
+
required: ["pageId"]
|
|
1902
|
+
}
|
|
1903
|
+
},
|
|
1904
|
+
{
|
|
1905
|
+
name: "cms_get_page_version",
|
|
1906
|
+
description: "Get full snapshot content and block tree for a specific version.",
|
|
1907
|
+
inputSchema: {
|
|
1908
|
+
type: "object",
|
|
1909
|
+
properties: { versionId: {
|
|
1910
|
+
type: "string",
|
|
1911
|
+
description: "Version UUID"
|
|
1912
|
+
} },
|
|
1913
|
+
required: ["versionId"]
|
|
1914
|
+
}
|
|
1915
|
+
},
|
|
1916
|
+
{
|
|
1917
|
+
name: "cms_rollback_page_version",
|
|
1918
|
+
description: "Rollback a page's working draft or live content to a past version snapshot.",
|
|
1919
|
+
inputSchema: {
|
|
1920
|
+
type: "object",
|
|
1921
|
+
properties: {
|
|
1922
|
+
pageId: {
|
|
1923
|
+
type: "string",
|
|
1924
|
+
description: "Page UUID"
|
|
1925
|
+
},
|
|
1926
|
+
versionId: {
|
|
1927
|
+
type: "string",
|
|
1928
|
+
description: "Target Version UUID to rollback to"
|
|
1929
|
+
},
|
|
1930
|
+
userId: {
|
|
1931
|
+
type: "string",
|
|
1932
|
+
description: "User or Agent ID performing rollback"
|
|
1933
|
+
}
|
|
1934
|
+
},
|
|
1935
|
+
required: [
|
|
1936
|
+
"pageId",
|
|
1937
|
+
"versionId",
|
|
1938
|
+
"userId"
|
|
1939
|
+
]
|
|
1940
|
+
}
|
|
1941
|
+
},
|
|
1942
|
+
{
|
|
1943
|
+
name: "cms_approve_page_version",
|
|
1944
|
+
description: "Record an editorial review approval for a version.",
|
|
1945
|
+
inputSchema: {
|
|
1946
|
+
type: "object",
|
|
1947
|
+
properties: {
|
|
1948
|
+
versionId: {
|
|
1949
|
+
type: "string",
|
|
1950
|
+
description: "Version UUID"
|
|
1951
|
+
},
|
|
1952
|
+
userId: {
|
|
1953
|
+
type: "string",
|
|
1954
|
+
description: "Approver user or agent ID"
|
|
1955
|
+
},
|
|
1956
|
+
userRole: {
|
|
1957
|
+
type: "string",
|
|
1958
|
+
description: "Approver role, e.g. 'Editor', 'Admin'"
|
|
1959
|
+
}
|
|
1960
|
+
},
|
|
1961
|
+
required: [
|
|
1962
|
+
"versionId",
|
|
1963
|
+
"userId",
|
|
1964
|
+
"userRole"
|
|
1965
|
+
]
|
|
1966
|
+
}
|
|
1967
|
+
},
|
|
1968
|
+
{
|
|
1969
|
+
name: "cms_add_version_comment",
|
|
1970
|
+
description: "Add an editorial or block-level comment to a version.",
|
|
1971
|
+
inputSchema: {
|
|
1972
|
+
type: "object",
|
|
1973
|
+
properties: {
|
|
1974
|
+
versionId: {
|
|
1975
|
+
type: "string",
|
|
1976
|
+
description: "Version UUID"
|
|
1977
|
+
},
|
|
1978
|
+
userId: {
|
|
1979
|
+
type: "string",
|
|
1980
|
+
description: "Author user or agent ID"
|
|
1981
|
+
},
|
|
1982
|
+
userRole: {
|
|
1983
|
+
type: "string",
|
|
1984
|
+
description: "Author role"
|
|
1985
|
+
},
|
|
1986
|
+
content: {
|
|
1987
|
+
type: "string",
|
|
1988
|
+
description: "Comment body text"
|
|
1989
|
+
},
|
|
1990
|
+
blockId: {
|
|
1991
|
+
type: "string",
|
|
1992
|
+
description: "Optional specific block ID target"
|
|
1993
|
+
}
|
|
1994
|
+
},
|
|
1995
|
+
required: [
|
|
1996
|
+
"versionId",
|
|
1997
|
+
"userId",
|
|
1998
|
+
"userRole",
|
|
1999
|
+
"content"
|
|
2000
|
+
]
|
|
2001
|
+
}
|
|
2002
|
+
},
|
|
2003
|
+
{
|
|
2004
|
+
name: "cms_list_templates",
|
|
2005
|
+
description: "List all page templates with starter blocks and property schemas.",
|
|
2006
|
+
inputSchema: {
|
|
2007
|
+
type: "object",
|
|
2008
|
+
properties: { pageType: {
|
|
2009
|
+
type: "string",
|
|
2010
|
+
description: "Optional filter by page type"
|
|
2011
|
+
} }
|
|
2012
|
+
}
|
|
2013
|
+
},
|
|
2014
|
+
{
|
|
2015
|
+
name: "cms_get_template",
|
|
2016
|
+
description: "Get template definition by slug or UUID.",
|
|
2017
|
+
inputSchema: {
|
|
2018
|
+
type: "object",
|
|
2019
|
+
properties: { slugOrId: {
|
|
2020
|
+
type: "string",
|
|
2021
|
+
description: "Template slug or UUID"
|
|
2022
|
+
} },
|
|
2023
|
+
required: ["slugOrId"]
|
|
2024
|
+
}
|
|
2025
|
+
},
|
|
2026
|
+
{
|
|
2027
|
+
name: "cms_create_template",
|
|
2028
|
+
description: "Create a new page template with default property groups and starter blocks.",
|
|
2029
|
+
inputSchema: {
|
|
2030
|
+
type: "object",
|
|
2031
|
+
properties: {
|
|
2032
|
+
slug: {
|
|
2033
|
+
type: "string",
|
|
2034
|
+
description: "Unique template slug"
|
|
2035
|
+
},
|
|
2036
|
+
title: {
|
|
2037
|
+
type: "object",
|
|
2038
|
+
description: "Localized title, e.g. { en: 'Wiki Template' }"
|
|
2039
|
+
},
|
|
2040
|
+
description: {
|
|
2041
|
+
type: "object",
|
|
2042
|
+
description: "Localized description"
|
|
2043
|
+
},
|
|
2044
|
+
pageType: {
|
|
2045
|
+
type: "string",
|
|
2046
|
+
description: "Page type target"
|
|
2047
|
+
},
|
|
2048
|
+
version: {
|
|
2049
|
+
type: "number",
|
|
2050
|
+
description: "Template version number (default 1)"
|
|
2051
|
+
},
|
|
2052
|
+
allowedRoles: {
|
|
2053
|
+
type: "array",
|
|
2054
|
+
description: "Roles allowed to use this template"
|
|
2055
|
+
},
|
|
2056
|
+
rolePermissions: {
|
|
2057
|
+
type: "object",
|
|
2058
|
+
description: "Granular template role permissions"
|
|
2059
|
+
},
|
|
2060
|
+
approvalRules: {
|
|
2061
|
+
type: "object",
|
|
2062
|
+
description: "Approval workflow configuration"
|
|
2063
|
+
},
|
|
2064
|
+
defaultProperties: {
|
|
2065
|
+
type: "object",
|
|
2066
|
+
description: "Default property schema groups"
|
|
2067
|
+
},
|
|
2068
|
+
initialBlocks: {
|
|
2069
|
+
type: "array",
|
|
2070
|
+
description: "Starter structured CMS blocks"
|
|
2071
|
+
}
|
|
2072
|
+
},
|
|
2073
|
+
required: [
|
|
2074
|
+
"slug",
|
|
2075
|
+
"title",
|
|
2076
|
+
"pageType"
|
|
2077
|
+
]
|
|
2078
|
+
}
|
|
2079
|
+
},
|
|
2080
|
+
{
|
|
2081
|
+
name: "cms_update_template",
|
|
2082
|
+
description: "Update an existing page template.",
|
|
2083
|
+
inputSchema: {
|
|
2084
|
+
type: "object",
|
|
2085
|
+
properties: {
|
|
2086
|
+
id: {
|
|
2087
|
+
type: "string",
|
|
2088
|
+
description: "Template UUID"
|
|
2089
|
+
},
|
|
2090
|
+
slug: { type: "string" },
|
|
2091
|
+
title: { type: "object" },
|
|
2092
|
+
description: { type: "object" },
|
|
2093
|
+
pageType: { type: "string" },
|
|
2094
|
+
version: { type: "number" },
|
|
2095
|
+
defaultProperties: { type: "object" },
|
|
2096
|
+
initialBlocks: { type: "array" },
|
|
2097
|
+
rolePermissions: { type: "object" },
|
|
2098
|
+
approvalRules: { type: "object" }
|
|
2099
|
+
},
|
|
2100
|
+
required: ["id"]
|
|
2101
|
+
}
|
|
2102
|
+
},
|
|
2103
|
+
{
|
|
2104
|
+
name: "cms_delete_template",
|
|
2105
|
+
description: "Delete a page template by UUID.",
|
|
2106
|
+
inputSchema: {
|
|
2107
|
+
type: "object",
|
|
2108
|
+
properties: { id: {
|
|
2109
|
+
type: "string",
|
|
2110
|
+
description: "Template UUID"
|
|
2111
|
+
} },
|
|
2112
|
+
required: ["id"]
|
|
2113
|
+
}
|
|
2114
|
+
},
|
|
2115
|
+
{
|
|
2116
|
+
name: "cms_list_taxonomies",
|
|
2117
|
+
description: "List taxonomy terms (categories, tags, genres, etc.) with usage counts.",
|
|
2118
|
+
inputSchema: {
|
|
2119
|
+
type: "object",
|
|
2120
|
+
properties: { taxonomy: {
|
|
2121
|
+
type: "string",
|
|
2122
|
+
description: "Taxonomy type, e.g. 'category', 'tag'"
|
|
2123
|
+
} },
|
|
2124
|
+
required: ["taxonomy"]
|
|
2125
|
+
}
|
|
2126
|
+
},
|
|
2127
|
+
{
|
|
2128
|
+
name: "cms_create_taxonomy_term",
|
|
2129
|
+
description: "Create a new taxonomy term (category or tag).",
|
|
2130
|
+
inputSchema: {
|
|
2131
|
+
type: "object",
|
|
2132
|
+
properties: {
|
|
2133
|
+
taxonomy: {
|
|
2134
|
+
type: "string",
|
|
2135
|
+
description: "'category', 'tag', or custom taxonomy name"
|
|
2136
|
+
},
|
|
2137
|
+
slug: {
|
|
2138
|
+
type: "string",
|
|
2139
|
+
description: "Unique slug within taxonomy"
|
|
2140
|
+
},
|
|
2141
|
+
label: {
|
|
2142
|
+
type: "object",
|
|
2143
|
+
description: "Localized label or string, e.g. { en: 'Guides' }"
|
|
2144
|
+
},
|
|
2145
|
+
description: {
|
|
2146
|
+
type: "object",
|
|
2147
|
+
description: "Localized description"
|
|
2148
|
+
},
|
|
2149
|
+
parentId: {
|
|
2150
|
+
type: "string",
|
|
2151
|
+
description: "Optional parent category UUID for hierarchy"
|
|
2152
|
+
},
|
|
2153
|
+
displayOrder: {
|
|
2154
|
+
type: "number",
|
|
2155
|
+
description: "Display sort order"
|
|
2156
|
+
}
|
|
2157
|
+
},
|
|
2158
|
+
required: [
|
|
2159
|
+
"taxonomy",
|
|
2160
|
+
"slug",
|
|
2161
|
+
"label"
|
|
2162
|
+
]
|
|
2163
|
+
}
|
|
2164
|
+
},
|
|
2165
|
+
{
|
|
2166
|
+
name: "cms_update_taxonomy_term",
|
|
2167
|
+
description: "Update an existing taxonomy term by UUID.",
|
|
2168
|
+
inputSchema: {
|
|
2169
|
+
type: "object",
|
|
2170
|
+
properties: {
|
|
2171
|
+
id: {
|
|
2172
|
+
type: "string",
|
|
2173
|
+
description: "Taxonomy term UUID"
|
|
2174
|
+
},
|
|
2175
|
+
slug: { type: "string" },
|
|
2176
|
+
label: { type: "object" },
|
|
2177
|
+
description: { type: "object" },
|
|
2178
|
+
parentId: { type: "string" },
|
|
2179
|
+
displayOrder: { type: "number" }
|
|
2180
|
+
},
|
|
2181
|
+
required: ["id"]
|
|
2182
|
+
}
|
|
2183
|
+
},
|
|
2184
|
+
{
|
|
2185
|
+
name: "cms_delete_taxonomy_term",
|
|
2186
|
+
description: "Delete a taxonomy term by UUID.",
|
|
2187
|
+
inputSchema: {
|
|
2188
|
+
type: "object",
|
|
2189
|
+
properties: { id: {
|
|
2190
|
+
type: "string",
|
|
2191
|
+
description: "Taxonomy term UUID"
|
|
2192
|
+
} },
|
|
2193
|
+
required: ["id"]
|
|
2194
|
+
}
|
|
2195
|
+
},
|
|
2196
|
+
{
|
|
2197
|
+
name: "cms_assign_page_taxonomy",
|
|
2198
|
+
description: "Assign or unassign taxonomy terms to/from a page.",
|
|
2199
|
+
inputSchema: {
|
|
2200
|
+
type: "object",
|
|
2201
|
+
properties: {
|
|
2202
|
+
pageId: {
|
|
2203
|
+
type: "string",
|
|
2204
|
+
description: "Page UUID"
|
|
2205
|
+
},
|
|
2206
|
+
termIds: {
|
|
2207
|
+
type: "array",
|
|
2208
|
+
description: "Array of taxonomy term UUIDs to assign to the page"
|
|
2209
|
+
}
|
|
2210
|
+
},
|
|
2211
|
+
required: ["pageId", "termIds"]
|
|
2212
|
+
}
|
|
2213
|
+
},
|
|
2214
|
+
{
|
|
2215
|
+
name: "cms_list_bylines",
|
|
2216
|
+
description: "List all registered author and contributor bylines.",
|
|
2217
|
+
inputSchema: {
|
|
2218
|
+
type: "object",
|
|
2219
|
+
properties: {}
|
|
2220
|
+
}
|
|
2221
|
+
},
|
|
2222
|
+
{
|
|
2223
|
+
name: "cms_get_byline",
|
|
2224
|
+
description: "Get a specific byline profile by slug or UUID.",
|
|
2225
|
+
inputSchema: {
|
|
2226
|
+
type: "object",
|
|
2227
|
+
properties: { slugOrId: {
|
|
2228
|
+
type: "string",
|
|
2229
|
+
description: "Byline slug or UUID"
|
|
2230
|
+
} },
|
|
2231
|
+
required: ["slugOrId"]
|
|
2232
|
+
}
|
|
2233
|
+
},
|
|
2234
|
+
{
|
|
2235
|
+
name: "cms_create_byline",
|
|
2236
|
+
description: "Create an author, creator, or studio byline profile.",
|
|
2237
|
+
inputSchema: {
|
|
2238
|
+
type: "object",
|
|
2239
|
+
properties: {
|
|
2240
|
+
name: {
|
|
2241
|
+
type: "string",
|
|
2242
|
+
description: "Display name"
|
|
2243
|
+
},
|
|
2244
|
+
slug: {
|
|
2245
|
+
type: "string",
|
|
2246
|
+
description: "Unique URL slug"
|
|
2247
|
+
},
|
|
2248
|
+
websiteUrl: {
|
|
2249
|
+
type: "string",
|
|
2250
|
+
description: "Author website URL"
|
|
2251
|
+
},
|
|
2252
|
+
bio: {
|
|
2253
|
+
type: "string",
|
|
2254
|
+
description: "Bio or summary text"
|
|
2255
|
+
},
|
|
2256
|
+
avatar: {
|
|
2257
|
+
type: "string",
|
|
2258
|
+
description: "Avatar image URL or asset key"
|
|
2259
|
+
},
|
|
2260
|
+
userId: {
|
|
2261
|
+
type: "string",
|
|
2262
|
+
description: "Linked user ID if applicable"
|
|
2263
|
+
},
|
|
2264
|
+
socials: {
|
|
2265
|
+
type: "object",
|
|
2266
|
+
description: "Social links, e.g. { twitter: '...', github: '...' }"
|
|
2267
|
+
},
|
|
2268
|
+
metadata: {
|
|
2269
|
+
type: "object",
|
|
2270
|
+
description: "Custom author metadata"
|
|
2271
|
+
}
|
|
2272
|
+
},
|
|
2273
|
+
required: ["name", "slug"]
|
|
2274
|
+
}
|
|
2275
|
+
},
|
|
2276
|
+
{
|
|
2277
|
+
name: "cms_update_byline",
|
|
2278
|
+
description: "Update an existing byline profile.",
|
|
2279
|
+
inputSchema: {
|
|
2280
|
+
type: "object",
|
|
2281
|
+
properties: {
|
|
2282
|
+
id: {
|
|
2283
|
+
type: "string",
|
|
2284
|
+
description: "Byline UUID"
|
|
2285
|
+
},
|
|
2286
|
+
name: { type: "string" },
|
|
2287
|
+
slug: { type: "string" },
|
|
2288
|
+
websiteUrl: { type: "string" },
|
|
2289
|
+
bio: { type: "string" },
|
|
2290
|
+
avatar: { type: "string" },
|
|
2291
|
+
userId: { type: "string" },
|
|
2292
|
+
socials: { type: "object" },
|
|
2293
|
+
metadata: { type: "object" }
|
|
2294
|
+
},
|
|
2295
|
+
required: ["id"]
|
|
2296
|
+
}
|
|
2297
|
+
},
|
|
2298
|
+
{
|
|
2299
|
+
name: "cms_delete_byline",
|
|
2300
|
+
description: "Delete an author byline profile by UUID.",
|
|
2301
|
+
inputSchema: {
|
|
2302
|
+
type: "object",
|
|
2303
|
+
properties: { id: {
|
|
2304
|
+
type: "string",
|
|
2305
|
+
description: "Byline UUID"
|
|
2306
|
+
} },
|
|
2307
|
+
required: ["id"]
|
|
2308
|
+
}
|
|
2309
|
+
},
|
|
2310
|
+
{
|
|
2311
|
+
name: "cms_list_content_types",
|
|
2312
|
+
description: "List all dynamic content types, collections, and e-commerce models.",
|
|
2313
|
+
inputSchema: {
|
|
2314
|
+
type: "object",
|
|
2315
|
+
properties: {}
|
|
2316
|
+
}
|
|
2317
|
+
},
|
|
2318
|
+
{
|
|
2319
|
+
name: "cms_get_content_type",
|
|
2320
|
+
description: "Get dynamic content type definition by slug or UUID.",
|
|
2321
|
+
inputSchema: {
|
|
2322
|
+
type: "object",
|
|
2323
|
+
properties: { slugOrId: {
|
|
2324
|
+
type: "string",
|
|
2325
|
+
description: "Content type slug or UUID"
|
|
2326
|
+
} },
|
|
2327
|
+
required: ["slugOrId"]
|
|
2328
|
+
}
|
|
2329
|
+
},
|
|
2330
|
+
{
|
|
2331
|
+
name: "cms_create_content_type",
|
|
2332
|
+
description: "Create a new dynamic collection, content type, or e-commerce schema.",
|
|
2333
|
+
inputSchema: {
|
|
2334
|
+
type: "object",
|
|
2335
|
+
properties: {
|
|
2336
|
+
slug: {
|
|
2337
|
+
type: "string",
|
|
2338
|
+
description: "Unique slug identifier (e.g. 'products', 'characters')"
|
|
2339
|
+
},
|
|
2340
|
+
name: {
|
|
2341
|
+
type: "object",
|
|
2342
|
+
description: "Localized name, e.g. { en: 'Products' }"
|
|
2343
|
+
},
|
|
2344
|
+
description: {
|
|
2345
|
+
type: "object",
|
|
2346
|
+
description: "Localized description"
|
|
2347
|
+
},
|
|
2348
|
+
icon: {
|
|
2349
|
+
type: "string",
|
|
2350
|
+
description: "Icon name (e.g. 'i-lucide-shopping-bag')"
|
|
2351
|
+
},
|
|
2352
|
+
mode: {
|
|
2353
|
+
type: "string",
|
|
2354
|
+
description: "'document' (blocks), 'data' (structured table), or 'singleton'"
|
|
2355
|
+
},
|
|
2356
|
+
fieldSchema: {
|
|
2357
|
+
type: "object",
|
|
2358
|
+
description: "Dynamic property field schemas and validation rules"
|
|
2359
|
+
},
|
|
2360
|
+
ecomSettings: {
|
|
2361
|
+
type: "object",
|
|
2362
|
+
description: "E-commerce settings { enabled, currencyDefault, trackInventory, hasVariants, allowDigitalDownloads, stripeSync }"
|
|
2363
|
+
},
|
|
2364
|
+
initialBlocks: {
|
|
2365
|
+
type: "array",
|
|
2366
|
+
description: "Starter blocks for document mode"
|
|
2367
|
+
},
|
|
2368
|
+
isSystem: {
|
|
2369
|
+
type: "boolean",
|
|
2370
|
+
description: "Whether this is a protected system schema"
|
|
2371
|
+
}
|
|
2372
|
+
},
|
|
2373
|
+
required: ["slug", "name"]
|
|
2374
|
+
}
|
|
2375
|
+
},
|
|
2376
|
+
{
|
|
2377
|
+
name: "cms_update_content_type",
|
|
2378
|
+
description: "Update an existing dynamic content type schema or e-commerce settings.",
|
|
2379
|
+
inputSchema: {
|
|
2380
|
+
type: "object",
|
|
2381
|
+
properties: {
|
|
2382
|
+
id: {
|
|
2383
|
+
type: "string",
|
|
2384
|
+
description: "Content type UUID"
|
|
2385
|
+
},
|
|
2386
|
+
slug: { type: "string" },
|
|
2387
|
+
name: { type: "object" },
|
|
2388
|
+
description: { type: "object" },
|
|
2389
|
+
icon: { type: "string" },
|
|
2390
|
+
mode: { type: "string" },
|
|
2391
|
+
fieldSchema: { type: "object" },
|
|
2392
|
+
ecomSettings: { type: "object" },
|
|
2393
|
+
initialBlocks: { type: "array" }
|
|
2394
|
+
},
|
|
2395
|
+
required: ["id"]
|
|
2396
|
+
}
|
|
2397
|
+
},
|
|
2398
|
+
{
|
|
2399
|
+
name: "cms_delete_content_type",
|
|
2400
|
+
description: "Delete a custom dynamic content type by UUID.",
|
|
2401
|
+
inputSchema: {
|
|
2402
|
+
type: "object",
|
|
2403
|
+
properties: { id: {
|
|
2404
|
+
type: "string",
|
|
2405
|
+
description: "Content type UUID"
|
|
2406
|
+
} },
|
|
2407
|
+
required: ["id"]
|
|
2408
|
+
}
|
|
2409
|
+
},
|
|
2410
|
+
{
|
|
2411
|
+
name: "cms_get_site_settings",
|
|
2412
|
+
description: "Retrieve current dynamic site settings including branding and SEO defaults.",
|
|
2413
|
+
inputSchema: {
|
|
2414
|
+
type: "object",
|
|
2415
|
+
properties: {}
|
|
1561
2416
|
}
|
|
1562
2417
|
},
|
|
1563
2418
|
{
|
|
@@ -1574,6 +2429,28 @@ const CMS_MCP_TOOLS = [
|
|
|
1574
2429
|
seo: { type: "object" }
|
|
1575
2430
|
}
|
|
1576
2431
|
}
|
|
2432
|
+
},
|
|
2433
|
+
{
|
|
2434
|
+
name: "cms_search_content",
|
|
2435
|
+
description: "Search across active CMS pages by keyword.",
|
|
2436
|
+
inputSchema: {
|
|
2437
|
+
type: "object",
|
|
2438
|
+
properties: {
|
|
2439
|
+
query: {
|
|
2440
|
+
type: "string",
|
|
2441
|
+
description: "Search query text"
|
|
2442
|
+
},
|
|
2443
|
+
type: {
|
|
2444
|
+
type: "string",
|
|
2445
|
+
description: "Optional filter by page type"
|
|
2446
|
+
},
|
|
2447
|
+
limit: {
|
|
2448
|
+
type: "number",
|
|
2449
|
+
description: "Max results to return (default 10)"
|
|
2450
|
+
}
|
|
2451
|
+
},
|
|
2452
|
+
required: ["query"]
|
|
2453
|
+
}
|
|
1577
2454
|
}
|
|
1578
2455
|
];
|
|
1579
2456
|
/**
|
|
@@ -1594,14 +2471,498 @@ async function handleCmsMcpRequest(body, db) {
|
|
|
1594
2471
|
if (args.type) conditions.push(eq(pages.type, args.type));
|
|
1595
2472
|
if (!args.includeDrafts) conditions.push(isNotNull(pages.publishedVersionId));
|
|
1596
2473
|
const queryLimit = typeof args.limit === "number" ? args.limit : 20;
|
|
2474
|
+
const queryOffset = typeof args.offset === "number" ? args.offset : 0;
|
|
1597
2475
|
const rows = await db.select({
|
|
1598
2476
|
id: pages.id,
|
|
1599
2477
|
slug: pages.slug,
|
|
1600
2478
|
type: pages.type,
|
|
1601
2479
|
title: pages.title,
|
|
2480
|
+
description: pages.description,
|
|
1602
2481
|
postedAt: pages.postedAt,
|
|
1603
|
-
publishedVersionId: pages.publishedVersionId
|
|
1604
|
-
|
|
2482
|
+
publishedVersionId: pages.publishedVersionId,
|
|
2483
|
+
createdAt: pages.createdAt,
|
|
2484
|
+
updatedAt: pages.updatedAt
|
|
2485
|
+
}).from(pages).where(and(...conditions)).orderBy(desc(pages.postedAt), desc(pages.createdAt)).limit(queryLimit).offset(queryOffset);
|
|
2486
|
+
return {
|
|
2487
|
+
jsonrpc: "2.0",
|
|
2488
|
+
id,
|
|
2489
|
+
result: { content: [{
|
|
2490
|
+
type: "text",
|
|
2491
|
+
text: JSON.stringify(rows, null, 2)
|
|
2492
|
+
}] }
|
|
2493
|
+
};
|
|
2494
|
+
}
|
|
2495
|
+
case "cms_get_page": {
|
|
2496
|
+
const condition = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(args.slugOrId) ? eq(pages.id, args.slugOrId) : eq(pages.slug, args.slugOrId);
|
|
2497
|
+
const page = (await db.select().from(pages).where(and(isNull(pages.deletedAt), condition)).limit(1))[0] || null;
|
|
2498
|
+
if (!page) return {
|
|
2499
|
+
jsonrpc: "2.0",
|
|
2500
|
+
id,
|
|
2501
|
+
error: {
|
|
2502
|
+
code: -32004,
|
|
2503
|
+
message: `Page '${args.slugOrId}' not found`
|
|
2504
|
+
}
|
|
2505
|
+
};
|
|
2506
|
+
let draft = null;
|
|
2507
|
+
try {
|
|
2508
|
+
draft = (await db.select().from(pageDrafts).where(eq(pageDrafts.pageId, page.id)).limit(1))[0] || null;
|
|
2509
|
+
} catch {}
|
|
2510
|
+
return {
|
|
2511
|
+
jsonrpc: "2.0",
|
|
2512
|
+
id,
|
|
2513
|
+
result: { content: [{
|
|
2514
|
+
type: "text",
|
|
2515
|
+
text: JSON.stringify({
|
|
2516
|
+
page,
|
|
2517
|
+
draft
|
|
2518
|
+
}, null, 2)
|
|
2519
|
+
}] }
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
case "cms_create_page": {
|
|
2523
|
+
const titlePayload = typeof args.title === "string" ? { en: args.title } : args.title;
|
|
2524
|
+
const descPayload = typeof args.description === "string" ? { en: args.description } : args.description || null;
|
|
2525
|
+
const blocksPayload = Array.isArray(args.blocks) ? args.blocks : [];
|
|
2526
|
+
const propertiesPayload = args.properties || {};
|
|
2527
|
+
const newPage = (await db.insert(pages).values({
|
|
2528
|
+
slug: args.slug,
|
|
2529
|
+
type: args.type,
|
|
2530
|
+
templateId: args.templateId || null,
|
|
2531
|
+
title: titlePayload,
|
|
2532
|
+
description: descPayload,
|
|
2533
|
+
tags: args.tags || [],
|
|
2534
|
+
authorIds: args.authorIds || [],
|
|
2535
|
+
bylines: args.bylines || [],
|
|
2536
|
+
content: {
|
|
2537
|
+
blocks: blocksPayload,
|
|
2538
|
+
properties: propertiesPayload
|
|
2539
|
+
}
|
|
2540
|
+
}).returning())[0];
|
|
2541
|
+
if (newPage) try {
|
|
2542
|
+
await db.insert(pageDrafts).values({
|
|
2543
|
+
pageId: newPage.id,
|
|
2544
|
+
updatedBy: args.authorIds?.[0] || "agent",
|
|
2545
|
+
content: {
|
|
2546
|
+
blocks: blocksPayload,
|
|
2547
|
+
properties: propertiesPayload
|
|
2548
|
+
}
|
|
2549
|
+
}).onConflictDoNothing();
|
|
2550
|
+
} catch {}
|
|
2551
|
+
return {
|
|
2552
|
+
jsonrpc: "2.0",
|
|
2553
|
+
id,
|
|
2554
|
+
result: { content: [{
|
|
2555
|
+
type: "text",
|
|
2556
|
+
text: JSON.stringify(newPage, null, 2)
|
|
2557
|
+
}] }
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
case "cms_update_page": {
|
|
2561
|
+
const updateData = { updatedAt: /* @__PURE__ */ new Date() };
|
|
2562
|
+
if (args.slug) updateData["slug"] = args.slug;
|
|
2563
|
+
if (args.title) updateData["title"] = typeof args.title === "string" ? { en: args.title } : args.title;
|
|
2564
|
+
if (args.description !== void 0) updateData["description"] = typeof args.description === "string" ? { en: args.description } : args.description;
|
|
2565
|
+
if (args.tags) updateData["tags"] = args.tags;
|
|
2566
|
+
if (args.bylines) updateData["bylines"] = args.bylines;
|
|
2567
|
+
if (args.blocks || args.properties) {
|
|
2568
|
+
const existingContent = (await db.select().from(pages).where(eq(pages.id, args.id)).limit(1))[0]?.content || {
|
|
2569
|
+
blocks: [],
|
|
2570
|
+
properties: {}
|
|
2571
|
+
};
|
|
2572
|
+
updateData["content"] = {
|
|
2573
|
+
blocks: args.blocks || existingContent.blocks || [],
|
|
2574
|
+
properties: args.properties || existingContent.properties || {}
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
const updated = await db.update(pages).set(updateData).where(eq(pages.id, args.id)).returning();
|
|
2578
|
+
return {
|
|
2579
|
+
jsonrpc: "2.0",
|
|
2580
|
+
id,
|
|
2581
|
+
result: { content: [{
|
|
2582
|
+
type: "text",
|
|
2583
|
+
text: JSON.stringify(updated[0] || null, null, 2)
|
|
2584
|
+
}] }
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
case "cms_create_draft": {
|
|
2588
|
+
const contentPayload = {
|
|
2589
|
+
blocks: args.blocks,
|
|
2590
|
+
properties: args.properties || {}
|
|
2591
|
+
};
|
|
2592
|
+
const draft = await db.insert(pageDrafts).values({
|
|
2593
|
+
pageId: args.pageId,
|
|
2594
|
+
updatedBy: args.userId,
|
|
2595
|
+
content: contentPayload
|
|
2596
|
+
}).onConflictDoUpdate({
|
|
2597
|
+
target: [pageDrafts.pageId],
|
|
2598
|
+
set: {
|
|
2599
|
+
content: contentPayload,
|
|
2600
|
+
updatedBy: args.userId,
|
|
2601
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2602
|
+
}
|
|
2603
|
+
}).returning();
|
|
2604
|
+
return {
|
|
2605
|
+
jsonrpc: "2.0",
|
|
2606
|
+
id,
|
|
2607
|
+
result: { content: [{
|
|
2608
|
+
type: "text",
|
|
2609
|
+
text: JSON.stringify(draft[0], null, 2)
|
|
2610
|
+
}] }
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
case "cms_publish_page": {
|
|
2614
|
+
const page = (await db.select().from(pages).where(eq(pages.id, args.pageId)).limit(1))[0];
|
|
2615
|
+
if (!page) return {
|
|
2616
|
+
jsonrpc: "2.0",
|
|
2617
|
+
id,
|
|
2618
|
+
error: {
|
|
2619
|
+
code: -32004,
|
|
2620
|
+
message: `Page '${args.pageId}' not found`
|
|
2621
|
+
}
|
|
2622
|
+
};
|
|
2623
|
+
const content = (await db.select().from(pageDrafts).where(eq(pageDrafts.pageId, args.pageId)).limit(1))[0]?.content || page.content;
|
|
2624
|
+
const nextVersionNumber = ((await db.select().from(pageVersions).where(eq(pageVersions.pageId, args.pageId)).orderBy(desc(pageVersions.versionNumber)).limit(1))[0]?.versionNumber || 0) + 1;
|
|
2625
|
+
const version = (await db.insert(pageVersions).values({
|
|
2626
|
+
pageId: args.pageId,
|
|
2627
|
+
versionNumber: nextVersionNumber,
|
|
2628
|
+
status: "published",
|
|
2629
|
+
slug: page.slug,
|
|
2630
|
+
type: page.type,
|
|
2631
|
+
title: page.title,
|
|
2632
|
+
description: page.description,
|
|
2633
|
+
tags: page.tags,
|
|
2634
|
+
authorIds: page.authorIds,
|
|
2635
|
+
bylines: page.bylines,
|
|
2636
|
+
content,
|
|
2637
|
+
createdBy: args.userId,
|
|
2638
|
+
approvedBy: [args.userId],
|
|
2639
|
+
approvedAt: /* @__PURE__ */ new Date(),
|
|
2640
|
+
changeSummary: args.changeSummary || "Published via MCP",
|
|
2641
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
2642
|
+
}).returning())[0];
|
|
2643
|
+
await db.update(pages).set({
|
|
2644
|
+
content,
|
|
2645
|
+
publishedVersionId: version.id,
|
|
2646
|
+
postedAt: /* @__PURE__ */ new Date(),
|
|
2647
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2648
|
+
}).where(eq(pages.id, args.pageId));
|
|
2649
|
+
await indexPageForSearch(db, {
|
|
2650
|
+
id: args.pageId,
|
|
2651
|
+
title: page.title,
|
|
2652
|
+
content
|
|
2653
|
+
}, ["en", "pt"]);
|
|
2654
|
+
return {
|
|
2655
|
+
jsonrpc: "2.0",
|
|
2656
|
+
id,
|
|
2657
|
+
result: { content: [{
|
|
2658
|
+
type: "text",
|
|
2659
|
+
text: JSON.stringify(version, null, 2)
|
|
2660
|
+
}] }
|
|
2661
|
+
};
|
|
2662
|
+
}
|
|
2663
|
+
case "cms_unpublish_page": {
|
|
2664
|
+
const updated = await db.update(pages).set({
|
|
2665
|
+
postedAt: null,
|
|
2666
|
+
publishedVersionId: null,
|
|
2667
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2668
|
+
}).where(eq(pages.id, args.pageId)).returning();
|
|
2669
|
+
return {
|
|
2670
|
+
jsonrpc: "2.0",
|
|
2671
|
+
id,
|
|
2672
|
+
result: { content: [{
|
|
2673
|
+
type: "text",
|
|
2674
|
+
text: JSON.stringify({
|
|
2675
|
+
success: true,
|
|
2676
|
+
page: updated[0] || null
|
|
2677
|
+
}, null, 2)
|
|
2678
|
+
}] }
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
case "cms_delete_page": {
|
|
2682
|
+
const deleted = await db.update(pages).set({ deletedAt: /* @__PURE__ */ new Date() }).where(eq(pages.id, args.pageId)).returning();
|
|
2683
|
+
return {
|
|
2684
|
+
jsonrpc: "2.0",
|
|
2685
|
+
id,
|
|
2686
|
+
result: { content: [{
|
|
2687
|
+
type: "text",
|
|
2688
|
+
text: JSON.stringify({
|
|
2689
|
+
success: true,
|
|
2690
|
+
page: deleted[0] || null
|
|
2691
|
+
}, null, 2)
|
|
2692
|
+
}] }
|
|
2693
|
+
};
|
|
2694
|
+
}
|
|
2695
|
+
case "cms_list_page_versions": {
|
|
2696
|
+
const vLimit = typeof args.limit === "number" ? args.limit : 20;
|
|
2697
|
+
const rows = await db.select().from(pageVersions).where(eq(pageVersions.pageId, args.pageId)).orderBy(desc(pageVersions.versionNumber)).limit(vLimit);
|
|
2698
|
+
return {
|
|
2699
|
+
jsonrpc: "2.0",
|
|
2700
|
+
id,
|
|
2701
|
+
result: { content: [{
|
|
2702
|
+
type: "text",
|
|
2703
|
+
text: JSON.stringify(rows, null, 2)
|
|
2704
|
+
}] }
|
|
2705
|
+
};
|
|
2706
|
+
}
|
|
2707
|
+
case "cms_get_page_version": {
|
|
2708
|
+
const row = await db.select().from(pageVersions).where(eq(pageVersions.id, args.versionId)).limit(1);
|
|
2709
|
+
return {
|
|
2710
|
+
jsonrpc: "2.0",
|
|
2711
|
+
id,
|
|
2712
|
+
result: { content: [{
|
|
2713
|
+
type: "text",
|
|
2714
|
+
text: JSON.stringify(row[0] || null, null, 2)
|
|
2715
|
+
}] }
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
case "cms_rollback_page_version": {
|
|
2719
|
+
const targetVersion = (await db.select().from(pageVersions).where(eq(pageVersions.id, args.versionId)).limit(1))[0];
|
|
2720
|
+
if (!targetVersion) return {
|
|
2721
|
+
jsonrpc: "2.0",
|
|
2722
|
+
id,
|
|
2723
|
+
error: {
|
|
2724
|
+
code: -32004,
|
|
2725
|
+
message: "Version snapshot not found"
|
|
2726
|
+
}
|
|
2727
|
+
};
|
|
2728
|
+
const draft = await db.insert(pageDrafts).values({
|
|
2729
|
+
pageId: args.pageId,
|
|
2730
|
+
updatedBy: args.userId,
|
|
2731
|
+
content: targetVersion.content
|
|
2732
|
+
}).onConflictDoUpdate({
|
|
2733
|
+
target: [pageDrafts.pageId],
|
|
2734
|
+
set: {
|
|
2735
|
+
content: targetVersion.content,
|
|
2736
|
+
updatedBy: args.userId,
|
|
2737
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2738
|
+
}
|
|
2739
|
+
}).returning();
|
|
2740
|
+
return {
|
|
2741
|
+
jsonrpc: "2.0",
|
|
2742
|
+
id,
|
|
2743
|
+
result: { content: [{
|
|
2744
|
+
type: "text",
|
|
2745
|
+
text: JSON.stringify({
|
|
2746
|
+
success: true,
|
|
2747
|
+
rolledBackToVersion: targetVersion.versionNumber,
|
|
2748
|
+
draft: draft[0]
|
|
2749
|
+
}, null, 2)
|
|
2750
|
+
}] }
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
case "cms_approve_page_version": {
|
|
2754
|
+
const approval = await db.insert(pageVersionApprovals).values({
|
|
2755
|
+
versionId: args.versionId,
|
|
2756
|
+
userId: args.userId,
|
|
2757
|
+
userRole: args.userRole,
|
|
2758
|
+
approvedAt: /* @__PURE__ */ new Date()
|
|
2759
|
+
}).returning();
|
|
2760
|
+
return {
|
|
2761
|
+
jsonrpc: "2.0",
|
|
2762
|
+
id,
|
|
2763
|
+
result: { content: [{
|
|
2764
|
+
type: "text",
|
|
2765
|
+
text: JSON.stringify(approval[0], null, 2)
|
|
2766
|
+
}] }
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
case "cms_add_version_comment": {
|
|
2770
|
+
const comment = await db.insert(pageVersionComments).values({
|
|
2771
|
+
versionId: args.versionId,
|
|
2772
|
+
userId: args.userId,
|
|
2773
|
+
userRole: args.userRole,
|
|
2774
|
+
content: args.content,
|
|
2775
|
+
blockId: args.blockId || null,
|
|
2776
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
2777
|
+
}).returning();
|
|
2778
|
+
return {
|
|
2779
|
+
jsonrpc: "2.0",
|
|
2780
|
+
id,
|
|
2781
|
+
result: { content: [{
|
|
2782
|
+
type: "text",
|
|
2783
|
+
text: JSON.stringify(comment[0], null, 2)
|
|
2784
|
+
}] }
|
|
2785
|
+
};
|
|
2786
|
+
}
|
|
2787
|
+
case "cms_list_templates": {
|
|
2788
|
+
const conditions = [];
|
|
2789
|
+
if (args.pageType) conditions.push(eq(pageTemplates.pageType, args.pageType));
|
|
2790
|
+
const rows = await db.select().from(pageTemplates).where(conditions.length > 0 ? and(...conditions) : void 0).orderBy(desc(pageTemplates.createdAt));
|
|
2791
|
+
return {
|
|
2792
|
+
jsonrpc: "2.0",
|
|
2793
|
+
id,
|
|
2794
|
+
result: { content: [{
|
|
2795
|
+
type: "text",
|
|
2796
|
+
text: JSON.stringify(rows, null, 2)
|
|
2797
|
+
}] }
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
case "cms_get_template": {
|
|
2801
|
+
const condition = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(args.slugOrId) ? eq(pageTemplates.id, args.slugOrId) : eq(pageTemplates.slug, args.slugOrId);
|
|
2802
|
+
const row = await db.select().from(pageTemplates).where(condition).limit(1);
|
|
2803
|
+
return {
|
|
2804
|
+
jsonrpc: "2.0",
|
|
2805
|
+
id,
|
|
2806
|
+
result: { content: [{
|
|
2807
|
+
type: "text",
|
|
2808
|
+
text: JSON.stringify(row[0] || null, null, 2)
|
|
2809
|
+
}] }
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2812
|
+
case "cms_create_template": {
|
|
2813
|
+
const titlePayload = typeof args.title === "string" ? { en: args.title } : args.title;
|
|
2814
|
+
const descPayload = typeof args.description === "string" ? { en: args.description } : args.description || null;
|
|
2815
|
+
const tmpl = await db.insert(pageTemplates).values({
|
|
2816
|
+
slug: args.slug,
|
|
2817
|
+
title: titlePayload,
|
|
2818
|
+
description: descPayload,
|
|
2819
|
+
pageType: args.pageType,
|
|
2820
|
+
version: args.version || 1,
|
|
2821
|
+
allowedRoles: args.allowedRoles || [],
|
|
2822
|
+
rolePermissions: args.rolePermissions || {
|
|
2823
|
+
whoCanCreate: [],
|
|
2824
|
+
whoCanEdit: [],
|
|
2825
|
+
whoCanReview: [],
|
|
2826
|
+
whoCanView: []
|
|
2827
|
+
},
|
|
2828
|
+
approvalRules: args.approvalRules || {
|
|
2829
|
+
allowSelfApproval: true,
|
|
2830
|
+
minApprovals: 1
|
|
2831
|
+
},
|
|
2832
|
+
defaultProperties: args.defaultProperties || {},
|
|
2833
|
+
initialBlocks: args.initialBlocks || []
|
|
2834
|
+
}).returning();
|
|
2835
|
+
return {
|
|
2836
|
+
jsonrpc: "2.0",
|
|
2837
|
+
id,
|
|
2838
|
+
result: { content: [{
|
|
2839
|
+
type: "text",
|
|
2840
|
+
text: JSON.stringify(tmpl[0], null, 2)
|
|
2841
|
+
}] }
|
|
2842
|
+
};
|
|
2843
|
+
}
|
|
2844
|
+
case "cms_update_template": {
|
|
2845
|
+
const updateData = { updatedAt: /* @__PURE__ */ new Date() };
|
|
2846
|
+
if (args.slug) updateData["slug"] = args.slug;
|
|
2847
|
+
if (args.title) updateData["title"] = typeof args.title === "string" ? { en: args.title } : args.title;
|
|
2848
|
+
if (args.description !== void 0) updateData["description"] = typeof args.description === "string" ? { en: args.description } : args.description;
|
|
2849
|
+
if (args.pageType) updateData["pageType"] = args.pageType;
|
|
2850
|
+
if (args.version) updateData["version"] = args.version;
|
|
2851
|
+
if (args.defaultProperties) updateData["defaultProperties"] = args.defaultProperties;
|
|
2852
|
+
if (args.initialBlocks) updateData["initialBlocks"] = args.initialBlocks;
|
|
2853
|
+
if (args.rolePermissions) updateData["rolePermissions"] = args.rolePermissions;
|
|
2854
|
+
if (args.approvalRules) updateData["approvalRules"] = args.approvalRules;
|
|
2855
|
+
const updated = await db.update(pageTemplates).set(updateData).where(eq(pageTemplates.id, args.id)).returning();
|
|
2856
|
+
return {
|
|
2857
|
+
jsonrpc: "2.0",
|
|
2858
|
+
id,
|
|
2859
|
+
result: { content: [{
|
|
2860
|
+
type: "text",
|
|
2861
|
+
text: JSON.stringify(updated[0] || null, null, 2)
|
|
2862
|
+
}] }
|
|
2863
|
+
};
|
|
2864
|
+
}
|
|
2865
|
+
case "cms_delete_template": {
|
|
2866
|
+
const deleted = await db.delete(pageTemplates).where(eq(pageTemplates.id, args.id)).returning();
|
|
2867
|
+
return {
|
|
2868
|
+
jsonrpc: "2.0",
|
|
2869
|
+
id,
|
|
2870
|
+
result: { content: [{
|
|
2871
|
+
type: "text",
|
|
2872
|
+
text: JSON.stringify({
|
|
2873
|
+
success: true,
|
|
2874
|
+
template: deleted[0] || null
|
|
2875
|
+
}, null, 2)
|
|
2876
|
+
}] }
|
|
2877
|
+
};
|
|
2878
|
+
}
|
|
2879
|
+
case "cms_list_taxonomies": {
|
|
2880
|
+
const terms = await getTaxonomyTerms(db, args.taxonomy);
|
|
2881
|
+
return {
|
|
2882
|
+
jsonrpc: "2.0",
|
|
2883
|
+
id,
|
|
2884
|
+
result: { content: [{
|
|
2885
|
+
type: "text",
|
|
2886
|
+
text: JSON.stringify(terms, null, 2)
|
|
2887
|
+
}] }
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
case "cms_create_taxonomy_term": {
|
|
2891
|
+
const labelPayload = typeof args.label === "string" ? { en: args.label } : args.label;
|
|
2892
|
+
const descPayload = typeof args.description === "string" ? { en: args.description } : args.description || null;
|
|
2893
|
+
const term = await db.insert(taxonomyTerms).values({
|
|
2894
|
+
taxonomy: args.taxonomy,
|
|
2895
|
+
slug: args.slug,
|
|
2896
|
+
label: labelPayload,
|
|
2897
|
+
description: descPayload,
|
|
2898
|
+
parentId: args.parentId || null,
|
|
2899
|
+
displayOrder: args.displayOrder || 0
|
|
2900
|
+
}).returning();
|
|
2901
|
+
return {
|
|
2902
|
+
jsonrpc: "2.0",
|
|
2903
|
+
id,
|
|
2904
|
+
result: { content: [{
|
|
2905
|
+
type: "text",
|
|
2906
|
+
text: JSON.stringify(term[0], null, 2)
|
|
2907
|
+
}] }
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2910
|
+
case "cms_update_taxonomy_term": {
|
|
2911
|
+
const updateData = { updatedAt: /* @__PURE__ */ new Date() };
|
|
2912
|
+
if (args.slug) updateData["slug"] = args.slug;
|
|
2913
|
+
if (args.label) updateData["label"] = typeof args.label === "string" ? { en: args.label } : args.label;
|
|
2914
|
+
if (args.description !== void 0) updateData["description"] = typeof args.description === "string" ? { en: args.description } : args.description;
|
|
2915
|
+
if (args.parentId !== void 0) updateData["parentId"] = args.parentId || null;
|
|
2916
|
+
if (args.displayOrder !== void 0) updateData["displayOrder"] = args.displayOrder;
|
|
2917
|
+
const updated = await db.update(taxonomyTerms).set(updateData).where(eq(taxonomyTerms.id, args.id)).returning();
|
|
2918
|
+
return {
|
|
2919
|
+
jsonrpc: "2.0",
|
|
2920
|
+
id,
|
|
2921
|
+
result: { content: [{
|
|
2922
|
+
type: "text",
|
|
2923
|
+
text: JSON.stringify(updated[0] || null, null, 2)
|
|
2924
|
+
}] }
|
|
2925
|
+
};
|
|
2926
|
+
}
|
|
2927
|
+
case "cms_delete_taxonomy_term": {
|
|
2928
|
+
const deleted = await db.delete(taxonomyTerms).where(eq(taxonomyTerms.id, args.id)).returning();
|
|
2929
|
+
return {
|
|
2930
|
+
jsonrpc: "2.0",
|
|
2931
|
+
id,
|
|
2932
|
+
result: { content: [{
|
|
2933
|
+
type: "text",
|
|
2934
|
+
text: JSON.stringify({
|
|
2935
|
+
success: true,
|
|
2936
|
+
term: deleted[0] || null
|
|
2937
|
+
}, null, 2)
|
|
2938
|
+
}] }
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
case "cms_assign_page_taxonomy": {
|
|
2942
|
+
await db.delete(pageTaxonomyTerms).where(eq(pageTaxonomyTerms.pageId, args.pageId));
|
|
2943
|
+
const termIds = Array.isArray(args.termIds) ? args.termIds : [];
|
|
2944
|
+
const assigned = [];
|
|
2945
|
+
for (const termId of termIds) {
|
|
2946
|
+
const row = await db.insert(pageTaxonomyTerms).values({
|
|
2947
|
+
pageId: args.pageId,
|
|
2948
|
+
termId
|
|
2949
|
+
}).returning();
|
|
2950
|
+
assigned.push(row[0]);
|
|
2951
|
+
}
|
|
2952
|
+
return {
|
|
2953
|
+
jsonrpc: "2.0",
|
|
2954
|
+
id,
|
|
2955
|
+
result: { content: [{
|
|
2956
|
+
type: "text",
|
|
2957
|
+
text: JSON.stringify({
|
|
2958
|
+
success: true,
|
|
2959
|
+
assignedCount: assigned.length
|
|
2960
|
+
}, null, 2)
|
|
2961
|
+
}] }
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
case "cms_list_bylines": {
|
|
2965
|
+
const rows = await db.select().from(bylines).where(isNull(bylines.deletedAt)).orderBy(desc(bylines.createdAt));
|
|
1605
2966
|
return {
|
|
1606
2967
|
jsonrpc: "2.0",
|
|
1607
2968
|
id,
|
|
@@ -1611,40 +2972,149 @@ async function handleCmsMcpRequest(body, db) {
|
|
|
1611
2972
|
}] }
|
|
1612
2973
|
};
|
|
1613
2974
|
}
|
|
1614
|
-
case "
|
|
1615
|
-
const
|
|
2975
|
+
case "cms_get_byline": {
|
|
2976
|
+
const condition = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(args.slugOrId) ? eq(bylines.id, args.slugOrId) : eq(bylines.slug, args.slugOrId);
|
|
2977
|
+
const row = await db.select().from(bylines).where(and(isNull(bylines.deletedAt), condition)).limit(1);
|
|
1616
2978
|
return {
|
|
1617
2979
|
jsonrpc: "2.0",
|
|
1618
2980
|
id,
|
|
1619
2981
|
result: { content: [{
|
|
1620
2982
|
type: "text",
|
|
1621
|
-
text: JSON.stringify(
|
|
2983
|
+
text: JSON.stringify(row[0] || null, null, 2)
|
|
1622
2984
|
}] }
|
|
1623
2985
|
};
|
|
1624
2986
|
}
|
|
1625
|
-
case "
|
|
1626
|
-
const
|
|
1627
|
-
|
|
1628
|
-
|
|
2987
|
+
case "cms_create_byline": {
|
|
2988
|
+
const row = await db.insert(bylines).values({
|
|
2989
|
+
name: args.name,
|
|
2990
|
+
slug: args.slug,
|
|
2991
|
+
websiteUrl: args.websiteUrl || null,
|
|
2992
|
+
bio: args.bio || null,
|
|
2993
|
+
avatar: args.avatar || null,
|
|
2994
|
+
userId: args.userId || null,
|
|
2995
|
+
socials: args.socials || {},
|
|
2996
|
+
metadata: args.metadata || {}
|
|
2997
|
+
}).returning();
|
|
2998
|
+
return {
|
|
2999
|
+
jsonrpc: "2.0",
|
|
3000
|
+
id,
|
|
3001
|
+
result: { content: [{
|
|
3002
|
+
type: "text",
|
|
3003
|
+
text: JSON.stringify(row[0], null, 2)
|
|
3004
|
+
}] }
|
|
1629
3005
|
};
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
3006
|
+
}
|
|
3007
|
+
case "cms_update_byline": {
|
|
3008
|
+
const updateData = { updatedAt: /* @__PURE__ */ new Date() };
|
|
3009
|
+
if (args.name) updateData["name"] = args.name;
|
|
3010
|
+
if (args.slug) updateData["slug"] = args.slug;
|
|
3011
|
+
if (args.websiteUrl !== void 0) updateData["websiteUrl"] = args.websiteUrl;
|
|
3012
|
+
if (args.bio !== void 0) updateData["bio"] = args.bio;
|
|
3013
|
+
if (args.avatar !== void 0) updateData["avatar"] = args.avatar;
|
|
3014
|
+
if (args.userId !== void 0) updateData["userId"] = args.userId;
|
|
3015
|
+
if (args.socials) updateData["socials"] = args.socials;
|
|
3016
|
+
if (args.metadata) updateData["metadata"] = args.metadata;
|
|
3017
|
+
const updated = await db.update(bylines).set(updateData).where(eq(bylines.id, args.id)).returning();
|
|
3018
|
+
return {
|
|
3019
|
+
jsonrpc: "2.0",
|
|
3020
|
+
id,
|
|
3021
|
+
result: { content: [{
|
|
3022
|
+
type: "text",
|
|
3023
|
+
text: JSON.stringify(updated[0] || null, null, 2)
|
|
3024
|
+
}] }
|
|
3025
|
+
};
|
|
3026
|
+
}
|
|
3027
|
+
case "cms_delete_byline": {
|
|
3028
|
+
const deleted = await db.update(bylines).set({ deletedAt: /* @__PURE__ */ new Date() }).where(eq(bylines.id, args.id)).returning();
|
|
3029
|
+
return {
|
|
3030
|
+
jsonrpc: "2.0",
|
|
3031
|
+
id,
|
|
3032
|
+
result: { content: [{
|
|
3033
|
+
type: "text",
|
|
3034
|
+
text: JSON.stringify({
|
|
3035
|
+
success: true,
|
|
3036
|
+
byline: deleted[0] || null
|
|
3037
|
+
}, null, 2)
|
|
3038
|
+
}] }
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
case "cms_list_content_types": {
|
|
3042
|
+
const rows = await db.select().from(contentTypes).orderBy(desc(contentTypes.createdAt));
|
|
3043
|
+
return {
|
|
3044
|
+
jsonrpc: "2.0",
|
|
3045
|
+
id,
|
|
3046
|
+
result: { content: [{
|
|
3047
|
+
type: "text",
|
|
3048
|
+
text: JSON.stringify(rows, null, 2)
|
|
3049
|
+
}] }
|
|
3050
|
+
};
|
|
3051
|
+
}
|
|
3052
|
+
case "cms_get_content_type": {
|
|
3053
|
+
const condition = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(args.slugOrId) ? eq(contentTypes.id, args.slugOrId) : eq(contentTypes.slug, args.slugOrId);
|
|
3054
|
+
const row = await db.select().from(contentTypes).where(condition).limit(1);
|
|
3055
|
+
return {
|
|
3056
|
+
jsonrpc: "2.0",
|
|
3057
|
+
id,
|
|
3058
|
+
result: { content: [{
|
|
3059
|
+
type: "text",
|
|
3060
|
+
text: JSON.stringify(row[0] || null, null, 2)
|
|
3061
|
+
}] }
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
case "cms_create_content_type": {
|
|
3065
|
+
const namePayload = typeof args.name === "string" ? { en: args.name } : args.name;
|
|
3066
|
+
const descPayload = typeof args.description === "string" ? { en: args.description } : args.description || null;
|
|
3067
|
+
const row = await db.insert(contentTypes).values({
|
|
3068
|
+
slug: args.slug,
|
|
3069
|
+
name: namePayload,
|
|
3070
|
+
description: descPayload,
|
|
3071
|
+
icon: args.icon || "i-lucide-box",
|
|
3072
|
+
mode: args.mode || "document",
|
|
3073
|
+
fieldSchema: args.fieldSchema || {},
|
|
3074
|
+
ecomSettings: args.ecomSettings || null,
|
|
3075
|
+
initialBlocks: args.initialBlocks || [],
|
|
3076
|
+
isSystem: Boolean(args.isSystem)
|
|
1641
3077
|
}).returning();
|
|
1642
3078
|
return {
|
|
1643
3079
|
jsonrpc: "2.0",
|
|
1644
3080
|
id,
|
|
1645
3081
|
result: { content: [{
|
|
1646
3082
|
type: "text",
|
|
1647
|
-
text: JSON.stringify(
|
|
3083
|
+
text: JSON.stringify(row[0], null, 2)
|
|
3084
|
+
}] }
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
case "cms_update_content_type": {
|
|
3088
|
+
const updateData = { updatedAt: /* @__PURE__ */ new Date() };
|
|
3089
|
+
if (args.slug) updateData["slug"] = args.slug;
|
|
3090
|
+
if (args.name) updateData["name"] = typeof args.name === "string" ? { en: args.name } : args.name;
|
|
3091
|
+
if (args.description !== void 0) updateData["description"] = typeof args.description === "string" ? { en: args.description } : args.description;
|
|
3092
|
+
if (args.icon) updateData["icon"] = args.icon;
|
|
3093
|
+
if (args.mode) updateData["mode"] = args.mode;
|
|
3094
|
+
if (args.fieldSchema) updateData["fieldSchema"] = args.fieldSchema;
|
|
3095
|
+
if (args.ecomSettings !== void 0) updateData["ecomSettings"] = args.ecomSettings;
|
|
3096
|
+
if (args.initialBlocks) updateData["initialBlocks"] = args.initialBlocks;
|
|
3097
|
+
const updated = await db.update(contentTypes).set(updateData).where(eq(contentTypes.id, args.id)).returning();
|
|
3098
|
+
return {
|
|
3099
|
+
jsonrpc: "2.0",
|
|
3100
|
+
id,
|
|
3101
|
+
result: { content: [{
|
|
3102
|
+
type: "text",
|
|
3103
|
+
text: JSON.stringify(updated[0] || null, null, 2)
|
|
3104
|
+
}] }
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
3107
|
+
case "cms_delete_content_type": {
|
|
3108
|
+
const deleted = await db.delete(contentTypes).where(eq(contentTypes.id, args.id)).returning();
|
|
3109
|
+
return {
|
|
3110
|
+
jsonrpc: "2.0",
|
|
3111
|
+
id,
|
|
3112
|
+
result: { content: [{
|
|
3113
|
+
type: "text",
|
|
3114
|
+
text: JSON.stringify({
|
|
3115
|
+
success: true,
|
|
3116
|
+
contentType: deleted[0] || null
|
|
3117
|
+
}, null, 2)
|
|
1648
3118
|
}] }
|
|
1649
3119
|
};
|
|
1650
3120
|
}
|
|
@@ -1670,6 +3140,27 @@ async function handleCmsMcpRequest(body, db) {
|
|
|
1670
3140
|
}] }
|
|
1671
3141
|
};
|
|
1672
3142
|
}
|
|
3143
|
+
case "cms_search_content": {
|
|
3144
|
+
const queryLimit = typeof args.limit === "number" ? args.limit : 10;
|
|
3145
|
+
const conditions = [isNull(pages.deletedAt)];
|
|
3146
|
+
if (args.type) conditions.push(eq(pages.type, args.type));
|
|
3147
|
+
const rows = await db.select({
|
|
3148
|
+
id: pages.id,
|
|
3149
|
+
slug: pages.slug,
|
|
3150
|
+
type: pages.type,
|
|
3151
|
+
title: pages.title,
|
|
3152
|
+
description: pages.description,
|
|
3153
|
+
postedAt: pages.postedAt
|
|
3154
|
+
}).from(pages).where(and(...conditions, or(like(pages.slug, `%${args.query}%`), sql`CAST(${pages.title} AS TEXT) ILIKE ${`%${args.query}%`}`))).limit(queryLimit);
|
|
3155
|
+
return {
|
|
3156
|
+
jsonrpc: "2.0",
|
|
3157
|
+
id,
|
|
3158
|
+
result: { content: [{
|
|
3159
|
+
type: "text",
|
|
3160
|
+
text: JSON.stringify(rows, null, 2)
|
|
3161
|
+
}] }
|
|
3162
|
+
};
|
|
3163
|
+
}
|
|
1673
3164
|
default: return {
|
|
1674
3165
|
jsonrpc: "2.0",
|
|
1675
3166
|
id,
|
|
@@ -1898,181 +3389,6 @@ function diffPageSnapshots(oldSnapshot, newSnapshot) {
|
|
|
1898
3389
|
};
|
|
1899
3390
|
}
|
|
1900
3391
|
//#endregion
|
|
1901
|
-
//#region src/services/search-indexer.ts
|
|
1902
|
-
async function indexPageForSearch(db, page, locales = ["en", "pt"]) {
|
|
1903
|
-
if (!db) return;
|
|
1904
|
-
const parseJson = (val) => typeof val === "string" ? JSON.parse(val) : val;
|
|
1905
|
-
const titleObj = parseJson(page.title) || {};
|
|
1906
|
-
const blocks = (parseJson(page.content) || {}).blocks || [];
|
|
1907
|
-
for (const locale of locales) {
|
|
1908
|
-
const titleText = typeof titleObj === "object" && titleObj !== null ? titleObj[locale] || titleObj.en || Object.values(titleObj)[0] || "" : String(titleObj || "");
|
|
1909
|
-
const contentText = extractTextFromBlocks(blocks, locale);
|
|
1910
|
-
if (!titleText && !contentText) continue;
|
|
1911
|
-
try {
|
|
1912
|
-
const existing = await db.select().from(contentSearchIndex).where(and(eq(contentSearchIndex.pageId, page.id), eq(contentSearchIndex.locale, locale))).limit(1);
|
|
1913
|
-
if (existing.length > 0) await db.update(contentSearchIndex).set({
|
|
1914
|
-
titleText,
|
|
1915
|
-
contentText,
|
|
1916
|
-
searchVector: sql`to_tsvector('english', ${titleText} || ' ' || ${contentText})`
|
|
1917
|
-
}).where(eq(contentSearchIndex.id, existing[0].id));
|
|
1918
|
-
else await db.insert(contentSearchIndex).values({
|
|
1919
|
-
pageId: page.id,
|
|
1920
|
-
locale,
|
|
1921
|
-
titleText,
|
|
1922
|
-
contentText,
|
|
1923
|
-
searchVector: sql`to_tsvector('english', ${titleText} || ' ' || ${contentText})`
|
|
1924
|
-
});
|
|
1925
|
-
} catch (err) {
|
|
1926
|
-
console.error(`[Search Indexer] Failed to index page ${page.id} for locale ${locale}:`, err);
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
//#endregion
|
|
1931
|
-
//#region src/services/taxonomies.ts
|
|
1932
|
-
/**
|
|
1933
|
-
* Retrieves all terms for a given taxonomy. If hierarchical, returns a tree structure with
|
|
1934
|
-
* `children: TaxonomyTerm[]`.
|
|
1935
|
-
*/
|
|
1936
|
-
async function getTaxonomyTerms(db, taxonomy, options = {}) {
|
|
1937
|
-
if (!db) return [];
|
|
1938
|
-
const includeCounts = options.includeCounts ?? true;
|
|
1939
|
-
const rawTerms = await db.select().from(taxonomyTerms).where(eq(taxonomyTerms.taxonomy, taxonomy)).orderBy(asc(taxonomyTerms.displayOrder), asc(taxonomyTerms.createdAt));
|
|
1940
|
-
if (!rawTerms || rawTerms.length === 0) return [];
|
|
1941
|
-
const countsMap = /* @__PURE__ */ new Map();
|
|
1942
|
-
if (includeCounts) try {
|
|
1943
|
-
const counts = await db.select({
|
|
1944
|
-
termId: pageTaxonomyTerms.termId,
|
|
1945
|
-
count: sql`count(${pageTaxonomyTerms.pageId})::int`
|
|
1946
|
-
}).from(pageTaxonomyTerms).innerJoin(pages, eq(pages.id, pageTaxonomyTerms.pageId)).where(isNull(pages.deletedAt)).groupBy(pageTaxonomyTerms.termId);
|
|
1947
|
-
for (const row of counts) countsMap.set(row.termId, Number(row.count));
|
|
1948
|
-
} catch {}
|
|
1949
|
-
const termsWithCounts = rawTerms.map((t) => ({
|
|
1950
|
-
id: t.id,
|
|
1951
|
-
taxonomy: t.taxonomy,
|
|
1952
|
-
slug: t.slug,
|
|
1953
|
-
label: t.label,
|
|
1954
|
-
description: t.description,
|
|
1955
|
-
parentId: t.parentId,
|
|
1956
|
-
displayOrder: t.displayOrder,
|
|
1957
|
-
count: countsMap.get(t.id) ?? 0,
|
|
1958
|
-
children: [],
|
|
1959
|
-
createdAt: t.createdAt,
|
|
1960
|
-
updatedAt: t.updatedAt
|
|
1961
|
-
}));
|
|
1962
|
-
if (!termsWithCounts.some((t) => !!t.parentId)) return termsWithCounts;
|
|
1963
|
-
const idMap = /* @__PURE__ */ new Map();
|
|
1964
|
-
const rootTerms = [];
|
|
1965
|
-
termsWithCounts.forEach((term) => {
|
|
1966
|
-
idMap.set(term.id, term);
|
|
1967
|
-
});
|
|
1968
|
-
termsWithCounts.forEach((term) => {
|
|
1969
|
-
if (term.parentId && idMap.has(term.parentId)) {
|
|
1970
|
-
const parent = idMap.get(term.parentId);
|
|
1971
|
-
parent.children = parent.children || [];
|
|
1972
|
-
parent.children.push(term);
|
|
1973
|
-
} else rootTerms.push(term);
|
|
1974
|
-
});
|
|
1975
|
-
return rootTerms;
|
|
1976
|
-
}
|
|
1977
|
-
/**
|
|
1978
|
-
* Retrieves a single term by taxonomy and slug.
|
|
1979
|
-
*/
|
|
1980
|
-
async function getTerm(db, taxonomy, slug) {
|
|
1981
|
-
if (!db) return null;
|
|
1982
|
-
const results = await db.select().from(taxonomyTerms).where(and(eq(taxonomyTerms.taxonomy, taxonomy), eq(taxonomyTerms.slug, slug))).limit(1);
|
|
1983
|
-
if (!results[0]) return null;
|
|
1984
|
-
let count = 0;
|
|
1985
|
-
try {
|
|
1986
|
-
const countResult = await db.select({ count: sql`count(${pageTaxonomyTerms.pageId})::int` }).from(pageTaxonomyTerms).innerJoin(pages, eq(pages.id, pageTaxonomyTerms.pageId)).where(and(eq(pageTaxonomyTerms.termId, results[0].id), isNull(pages.deletedAt)));
|
|
1987
|
-
count = Number(countResult[0]?.count ?? 0);
|
|
1988
|
-
} catch {
|
|
1989
|
-
count = 0;
|
|
1990
|
-
}
|
|
1991
|
-
return {
|
|
1992
|
-
...results[0],
|
|
1993
|
-
count,
|
|
1994
|
-
children: []
|
|
1995
|
-
};
|
|
1996
|
-
}
|
|
1997
|
-
/**
|
|
1998
|
-
* Retrieves all terms assigned to a specific page entry.
|
|
1999
|
-
*/
|
|
2000
|
-
async function getEntryTerms(db, pageId, taxonomy) {
|
|
2001
|
-
if (!db || !pageId) return [];
|
|
2002
|
-
const conditions = [eq(pageTaxonomyTerms.pageId, pageId)];
|
|
2003
|
-
if (taxonomy) conditions.push(eq(taxonomyTerms.taxonomy, taxonomy));
|
|
2004
|
-
return await db.select({
|
|
2005
|
-
id: taxonomyTerms.id,
|
|
2006
|
-
taxonomy: taxonomyTerms.taxonomy,
|
|
2007
|
-
slug: taxonomyTerms.slug,
|
|
2008
|
-
label: taxonomyTerms.label,
|
|
2009
|
-
description: taxonomyTerms.description,
|
|
2010
|
-
parentId: taxonomyTerms.parentId,
|
|
2011
|
-
displayOrder: taxonomyTerms.displayOrder
|
|
2012
|
-
}).from(pageTaxonomyTerms).innerJoin(taxonomyTerms, eq(taxonomyTerms.id, pageTaxonomyTerms.termId)).where(and(...conditions)).orderBy(asc(taxonomyTerms.displayOrder));
|
|
2013
|
-
}
|
|
2014
|
-
/**
|
|
2015
|
-
* Retrieves pages tagged with a given taxonomy term.
|
|
2016
|
-
*/
|
|
2017
|
-
async function getEntriesByTerm(db, taxonomy, slug, options = {}) {
|
|
2018
|
-
if (!db) return {
|
|
2019
|
-
entries: [],
|
|
2020
|
-
total: 0
|
|
2021
|
-
};
|
|
2022
|
-
const term = await getTerm(db, taxonomy, slug);
|
|
2023
|
-
if (!term) return {
|
|
2024
|
-
entries: [],
|
|
2025
|
-
total: 0
|
|
2026
|
-
};
|
|
2027
|
-
const conditions = [eq(pageTaxonomyTerms.termId, term.id), isNull(pages.deletedAt)];
|
|
2028
|
-
if (options.type) conditions.push(eq(pages.type, options.type));
|
|
2029
|
-
let query = db.select({ page: pages }).from(pageTaxonomyTerms).innerJoin(pages, eq(pages.id, pageTaxonomyTerms.pageId)).where(and(...conditions)).orderBy(asc(pages.createdAt));
|
|
2030
|
-
if (options.limit) query = query.limit(options.limit);
|
|
2031
|
-
if (options.offset) query = query.offset(options.offset);
|
|
2032
|
-
const entries = (await query).map((r) => r.page);
|
|
2033
|
-
return {
|
|
2034
|
-
entries,
|
|
2035
|
-
total: term.count ?? entries.length,
|
|
2036
|
-
term
|
|
2037
|
-
};
|
|
2038
|
-
}
|
|
2039
|
-
/**
|
|
2040
|
-
* Sets/syncs the assigned terms for a page within a specific taxonomy.
|
|
2041
|
-
*/
|
|
2042
|
-
async function assignEntryTerms(db, pageId, taxonomy, termIds) {
|
|
2043
|
-
if (!db || !pageId) return;
|
|
2044
|
-
const currentAssigned = await getEntryTerms(db, pageId, taxonomy);
|
|
2045
|
-
const currentTermIds = new Set(currentAssigned.map((t) => t.id));
|
|
2046
|
-
const newTermIds = new Set(termIds);
|
|
2047
|
-
for (const term of currentAssigned) if (!newTermIds.has(term.id)) await db.delete(pageTaxonomyTerms).where(and(eq(pageTaxonomyTerms.pageId, pageId), eq(pageTaxonomyTerms.termId, term.id)));
|
|
2048
|
-
for (const termId of termIds) if (!currentTermIds.has(termId)) await db.insert(pageTaxonomyTerms).values({
|
|
2049
|
-
pageId,
|
|
2050
|
-
termId
|
|
2051
|
-
}).onConflictDoNothing();
|
|
2052
|
-
}
|
|
2053
|
-
/**
|
|
2054
|
-
* Creates a new taxonomy term.
|
|
2055
|
-
*/
|
|
2056
|
-
async function createTerm(db, data) {
|
|
2057
|
-
return (await db.insert(taxonomyTerms).values(data).returning())[0];
|
|
2058
|
-
}
|
|
2059
|
-
/**
|
|
2060
|
-
* Updates an existing taxonomy term.
|
|
2061
|
-
*/
|
|
2062
|
-
async function updateTerm(db, id, updates) {
|
|
2063
|
-
return (await db.update(taxonomyTerms).set({
|
|
2064
|
-
...updates,
|
|
2065
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
2066
|
-
}).where(eq(taxonomyTerms.id, id)).returning())[0];
|
|
2067
|
-
}
|
|
2068
|
-
/**
|
|
2069
|
-
* Deletes a taxonomy term.
|
|
2070
|
-
*/
|
|
2071
|
-
async function deleteTerm(db, id) {
|
|
2072
|
-
await db.delete(taxonomyTerms).where(eq(taxonomyTerms.id, id));
|
|
2073
|
-
return { success: true };
|
|
2074
|
-
}
|
|
2075
|
-
//#endregion
|
|
2076
3392
|
//#region src/docs/config.ts
|
|
2077
3393
|
const defineDocsConfig = (config) => config;
|
|
2078
3394
|
//#endregion
|
|
@@ -2379,4 +3695,4 @@ async function renderCorpusMarkdown(db, options = {}) {
|
|
|
2379
3695
|
return parts.join("\n");
|
|
2380
3696
|
}
|
|
2381
3697
|
//#endregion
|
|
2382
|
-
export { ALLOWED_CHILDREN_MAP, BLOG_POST_DEFINITION, BlockRegistry, CARD_DEFINITION, CHARACTER_DEFINITION, CMS_MCP_TOOLS, DEFAULT_SITE_SETTINGS, DOCUMENT_DEFINITION, GROUP_DEFINITION, HERO_DEFINITION, ITEM_DEFINITION, LOCATION_DEFINITION, MAX_SECTION_DEPTH, MAX_SECTION_HEADING_LEVEL, MIN_SECTION_HEADING_LEVEL, OBJECT_DEFINITION, PAGE_MAP, PATCH_NOTE_DEFINITION, POSTGRES_SEARCH_CONFIGS, SERIES_DEFINITION, SKILL_DEFINITION, SPECIES_DEFINITION, addToast, assignEntryTerms, auth0Auth, blocksToMarkdown, bylines, calculateHeadingLevel, canUserCreateFromTemplate, canUserEditBlock, canUserEditFromTemplate, canUserInstantiateTemplate, canUserReviewFromTemplate, canUserViewFromTemplate, cfAccessAuth, clearToasts, cmsAc, cmsStatements, contentSearchIndex, createByline, createPageExcerpt, createTerm, defineDocsConfig, definePageDefinition, deleteByline, deleteR2File, deleteTerm, diffBlocks, diffPageSnapshots, evaluateAccess, extractHeadingsFromBlocks, extractTextFromBlocks, filterBlocksForReader, filterTemplatesForUser, formatDocsUrl, generatePreviewToken, getByline, getBylines, getCmsCollection, getCmsEntry, getDocsSidebar, getDocsVersions, getEntriesByTerm, getEntryTerms, getPageDefinition, getPostgresSearchConfig, getR2Bucket, getSiteSettings, getSurroundItems, getTaxonomyTerms, getTerm, handleCmsCron, handleCmsMcpRequest, hasLockedDescendants, hasPermission, hasRole, indexPageForSearch, inlinesToMarkdown, invalidatePageCache, isValidUUID, listR2Files, mockAuth, pageDraftLocks, pageDrafts, pageTaxonomyTerms, pageTemplates, pageVersionApprovals, pageVersionComments, pageVersions, pages, parseDocsSlug, r2, removeToast, renderCorpusMarkdown, renderPageAsMarkdown, rimelightCms, rimelightCmsLoader, showErrorToast, showSuccessToast, siteSettings, slugifyHeading, taxonomyTerms, toast, toasts, updateByline, updateSiteSettings, updateTerm, uploadR2File, validateBlockAST, validateServerBlockPayload, verifyPreviewToken };
|
|
3698
|
+
export { ALLOWED_CHILDREN_MAP, BLOG_POST_DEFINITION, BlockRegistry, CARD_DEFINITION, CHARACTER_DEFINITION, CMS_MCP_TOOLS, DEFAULT_SITE_SETTINGS, DOCUMENT_DEFINITION, GROUP_DEFINITION, HERO_DEFINITION, ITEM_DEFINITION, LOCATION_DEFINITION, MAX_SECTION_DEPTH, MAX_SECTION_HEADING_LEVEL, MIN_SECTION_HEADING_LEVEL, OBJECT_DEFINITION, PAGE_MAP, PATCH_NOTE_DEFINITION, POSTGRES_SEARCH_CONFIGS, SERIES_DEFINITION, SKILL_DEFINITION, SPECIES_DEFINITION, addToast, assignEntryTerms, auth0Auth, blocksToMarkdown, bylines, calculateHeadingLevel, canUserCreateFromTemplate, canUserEditBlock, canUserEditFromTemplate, canUserInstantiateTemplate, canUserReviewFromTemplate, canUserViewFromTemplate, cfAccessAuth, clearToasts, cmsAc, cmsStatements, contentSearchIndex, contentTypes, createByline, createPageExcerpt, createTerm, defineDocsConfig, definePageDefinition, deleteByline, deleteR2File, deleteTerm, diffBlocks, diffPageSnapshots, evaluateAccess, extractHeadingsFromBlocks, extractTextFromBlocks, filterBlocksForReader, filterTemplatesForUser, formatDocsUrl, generatePreviewToken, getByline, getBylines, getCmsCollection, getCmsEntry, getDocsSidebar, getDocsVersions, getEntriesByTerm, getEntryTerms, getPageDefinition, getPostgresSearchConfig, getR2Bucket, getSiteSettings, getSurroundItems, getTaxonomyTerms, getTerm, handleCmsCron, handleCmsMcpRequest, hasLockedDescendants, hasPermission, hasRole, indexPageForSearch, inlinesToMarkdown, invalidatePageCache, isValidUUID, listR2Files, mockAuth, pageDraftLocks, pageDrafts, pageTaxonomyTerms, pageTemplates, pageVersionApprovals, pageVersionComments, pageVersions, pages, parseDocsSlug, r2, removeToast, renderCorpusMarkdown, renderPageAsMarkdown, rimelightCms, rimelightCmsLoader, showErrorToast, showSuccessToast, siteSettings, slugifyHeading, taxonomyTerms, toast, toasts, updateByline, updateSiteSettings, updateTerm, uploadR2File, validateBlockAST, validateServerBlockPayload, verifyPreviewToken };
|