import { createNavigationItemInputSchema, createNavigationMenuInputSchema, createPageInputSchema, updateNavigationItemInputSchema, updateNavigationMenuInputSchema, updatePageInputSchema, upsertPageTranslationInputSchema, } from "@cms/content" import { z } from "zod" import { db } from "./client" export type PublicNavigationItem = { id: string label: string href: string children: PublicNavigationItem[] } const supportedLocaleSchema = z.enum(["de", "en", "es", "fr"]) const upsertNavigationItemTranslationInputSchema = z.object({ navigationItemId: z.string().uuid(), locale: supportedLocaleSchema, label: z.string().min(1).max(180), }) 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 listPublishedPageSlugs() { const pages = await db.page.findMany({ where: { status: "published" }, orderBy: { updatedAt: "desc" }, select: { slug: true, updatedAt: true, }, }) return pages } export async function getPageById(id: string) { return db.page.findUnique({ where: { id }, }) } export async function getPublishedPageBySlug(slug: string) { return db.page.findFirst({ where: { slug, status: "published", }, }) } export async function getPublishedPageBySlugForLocale(slug: string, locale: string) { const page = await db.page.findFirst({ where: { slug, status: "published", }, include: { translations: { where: { locale, }, take: 1, }, }, }) if (!page) { return null } const translation = page.translations[0] return { ...page, title: translation?.title ?? page.title, summary: translation?.summary ?? page.summary, content: translation?.content ?? page.content, seoTitle: translation?.seoTitle ?? page.seoTitle, seoDescription: translation?.seoDescription ?? page.seoDescription, } } 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 upsertPageTranslation(input: unknown) { const payload = upsertPageTranslationInputSchema.parse(input) const { pageId, locale, ...data } = payload return db.pageTranslation.upsert({ where: { pageId_locale: { pageId, locale, }, }, create: { pageId, locale, ...data, }, update: data, }) } export async function listPageTranslations(pageId: string) { return db.pageTranslation.findMany({ where: { pageId }, orderBy: [{ locale: "asc" }], }) } 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, }, }, translations: { orderBy: [{ locale: "asc" }], }, }, }, }, }) } function resolveNavigationHref(item: { href: string | null page: { slug: string status: string } | null }): string | null { if (item.href) { return item.href } if (item.page?.status === "published") { return item.page.slug === "home" ? "/" : `/${item.page.slug}` } return null } export async function listPublicNavigation( location = "header", locale?: string, ): Promise { const normalizedLocale = locale ? supportedLocaleSchema.safeParse(locale).data : undefined const menu = await db.navigationMenu.findFirst({ where: { location, isVisible: true, }, orderBy: { updatedAt: "desc" }, include: { items: { where: { isVisible: true, }, orderBy: [{ sortOrder: "asc" }, { label: "asc" }], include: { page: { select: { slug: true, status: true, }, }, translations: normalizedLocale ? { where: { locale: normalizedLocale }, take: 1, } : false, }, }, }, }) if (!menu) { return [] } const itemMap = new Map< string, { id: string label: string href: string parentId: string | null children: PublicNavigationItem[] } >() for (const item of menu.items) { const href = resolveNavigationHref(item) if (!href) { continue } itemMap.set(item.id, { id: item.id, label: item.translations?.[0]?.label ?? item.label, href, parentId: item.parentId, children: [], }) } const roots: PublicNavigationItem[] = [] for (const entry of itemMap.values()) { if (entry.parentId) { const parent = itemMap.get(entry.parentId) if (parent) { parent.children.push({ id: entry.id, label: entry.label, href: entry.href, children: entry.children, }) continue } } roots.push({ id: entry.id, label: entry.label, href: entry.href, children: entry.children, }) } return roots } export async function createNavigationMenu(input: unknown) { const payload = createNavigationMenuInputSchema.parse(input) return db.navigationMenu.create({ data: payload, }) } export async function updateNavigationMenu(input: unknown) { const payload = updateNavigationMenuInputSchema.parse(input) const { id, ...data } = payload return db.navigationMenu.update({ where: { id }, data, }) } export async function deleteNavigationMenu(id: string) { return db.navigationMenu.delete({ where: { id }, }) } 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 }, }) } export async function upsertNavigationItemTranslation(input: unknown) { const payload = upsertNavigationItemTranslationInputSchema.parse(input) return db.navigationItemTranslation.upsert({ where: { navigationItemId_locale: { navigationItemId: payload.navigationItemId, locale: payload.locale, }, }, create: payload, update: { label: payload.label, }, }) }