Files
old.cms.fellies.org/packages/db/src/pages-navigation.ts

340 lines
7.0 KiB
TypeScript

import {
createNavigationItemInputSchema,
createNavigationMenuInputSchema,
createPageInputSchema,
updateNavigationItemInputSchema,
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<PublicNavigationItem[]> {
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 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,
},
})
}