feat(pages): add pages and navigation builder baseline

This commit is contained in:
2026-02-12 19:30:09 +01:00
parent 7d9bc9dca9
commit 281b1d7a1b
15 changed files with 1372 additions and 16 deletions

View File

@@ -16,6 +16,18 @@ export {
listMediaFoundationGroups,
updateMediaAsset,
} from "./media-foundation"
export {
createNavigationItem,
createNavigationMenu,
createPage,
deleteNavigationItem,
deletePage,
getPageById,
listNavigationMenus,
listPages,
updateNavigationItem,
updatePage,
} from "./pages-navigation"
export {
createPost,
deletePost,

View File

@@ -0,0 +1,92 @@
import { beforeEach, describe, expect, it, vi } from "vitest"
const { mockDb } = vi.hoisted(() => ({
mockDb: {
page: {
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
findUnique: vi.fn(),
findMany: vi.fn(),
},
navigationMenu: {
create: vi.fn(),
findMany: vi.fn(),
},
navigationItem: {
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
},
},
}))
vi.mock("./client", () => ({
db: mockDb,
}))
import {
createNavigationItem,
createNavigationMenu,
createPage,
updatePage,
} from "./pages-navigation"
describe("pages-navigation service", () => {
beforeEach(() => {
for (const value of Object.values(mockDb)) {
for (const fn of Object.values(value)) {
if (typeof fn === "function") {
fn.mockReset()
}
}
}
})
it("creates published pages with publishedAt", async () => {
mockDb.page.create.mockResolvedValue({ id: "page-1" })
await createPage({
title: "About",
slug: "about",
status: "published",
content: "hello",
})
expect(mockDb.page.create).toHaveBeenCalledTimes(1)
expect(mockDb.page.create.mock.calls[0]?.[0].data.publishedAt).toBeInstanceOf(Date)
})
it("updates page status publication timestamp", async () => {
mockDb.page.update.mockResolvedValue({ id: "page-1" })
await updatePage({
id: "550e8400-e29b-41d4-a716-446655440000",
status: "draft",
})
expect(mockDb.page.update).toHaveBeenCalledTimes(1)
expect(mockDb.page.update.mock.calls[0]?.[0].data.publishedAt).toBeNull()
})
it("creates menus and items with schema parsing", async () => {
mockDb.navigationMenu.create.mockResolvedValue({ id: "menu-1" })
mockDb.navigationItem.create.mockResolvedValue({ id: "item-1" })
await createNavigationMenu({
name: "Primary",
slug: "primary",
location: "header",
})
await createNavigationItem({
menuId: "550e8400-e29b-41d4-a716-446655440001",
label: "Home",
href: "/",
sortOrder: 0,
})
expect(mockDb.navigationMenu.create).toHaveBeenCalledTimes(1)
expect(mockDb.navigationItem.create).toHaveBeenCalledTimes(1)
})
})

View File

@@ -0,0 +1,109 @@
import {
createNavigationItemInputSchema,
createNavigationMenuInputSchema,
createPageInputSchema,
updateNavigationItemInputSchema,
updatePageInputSchema,
} from "@cms/content"
import { db } from "./client"
function resolvePublishedAt(status: string): Date | null {
return status === "published" ? new Date() : null
}
export async function listPages(limit = 50) {
return db.page.findMany({
orderBy: [{ updatedAt: "desc" }],
take: limit,
})
}
export async function getPageById(id: string) {
return db.page.findUnique({
where: { id },
})
}
export async function createPage(input: unknown) {
const payload = createPageInputSchema.parse(input)
return db.page.create({
data: {
...payload,
publishedAt: resolvePublishedAt(payload.status),
},
})
}
export async function updatePage(input: unknown) {
const payload = updatePageInputSchema.parse(input)
const { id, ...data } = payload
return db.page.update({
where: { id },
data: {
...data,
publishedAt:
data.status === undefined ? undefined : data.status === "published" ? new Date() : null,
},
})
}
export async function deletePage(id: string) {
return db.page.delete({
where: { id },
})
}
export async function listNavigationMenus() {
return db.navigationMenu.findMany({
orderBy: [{ location: "asc" }, { name: "asc" }],
include: {
items: {
orderBy: [{ sortOrder: "asc" }, { label: "asc" }],
include: {
page: {
select: {
id: true,
title: true,
slug: true,
},
},
},
},
},
})
}
export async function createNavigationMenu(input: unknown) {
const payload = createNavigationMenuInputSchema.parse(input)
return db.navigationMenu.create({
data: payload,
})
}
export async function createNavigationItem(input: unknown) {
const payload = createNavigationItemInputSchema.parse(input)
return db.navigationItem.create({
data: payload,
})
}
export async function updateNavigationItem(input: unknown) {
const payload = updateNavigationItemInputSchema.parse(input)
const { id, ...data } = payload
return db.navigationItem.update({
where: { id },
data,
})
}
export async function deleteNavigationItem(id: string) {
return db.navigationItem.delete({
where: { id },
})
}