x
This commit is contained in:
1 parent
602eaa9260
commit
c42dd4272f
30 files changed
+2024
-252
No files matched your search
@@ -221,6 +221,100 @@ export function mergeSectionTranslations(payload: any, translations: any, paths:
|
||||
return { payload: result, translatedPaths };
|
||||
}
|
||||
|
||||
export async function savePageTranslations(pageId: number, payload: any) {
|
||||
if (!payload || typeof payload !== "object") return [];
|
||||
const results: any[] = [];
|
||||
const LANGS = ["th", "en", "ja", "zh", "vi", "ko", "lo", "it"];
|
||||
|
||||
const page = await prisma.page.findUnique({ where: { id: pageId } });
|
||||
if (!page) return results;
|
||||
|
||||
if (Array.isArray(payload)) {
|
||||
for (const item of payload) {
|
||||
const lang = item.langCode || item.lang;
|
||||
if (!lang || !LANGS.includes(lang)) continue;
|
||||
const title = item.title ?? page.title;
|
||||
const seoTitle = item.seoTitle !== undefined ? item.seoTitle : undefined;
|
||||
const seoDescription = item.seoDescription !== undefined ? item.seoDescription : undefined;
|
||||
const seoKeywords = item.seoKeywords !== undefined ? item.seoKeywords : undefined;
|
||||
|
||||
const updateData: any = {};
|
||||
if (item.title !== undefined) updateData.title = item.title;
|
||||
if (seoTitle !== undefined) updateData.seoTitle = seoTitle;
|
||||
if (seoDescription !== undefined) updateData.seoDescription = seoDescription;
|
||||
if (seoKeywords !== undefined) updateData.seoKeywords = seoKeywords;
|
||||
|
||||
const updated = await prisma.pageTranslation.upsert({
|
||||
where: { pageId_langCode: { pageId, langCode: lang } },
|
||||
update: updateData,
|
||||
create: {
|
||||
pageId,
|
||||
langCode: lang,
|
||||
title,
|
||||
seoTitle: seoTitle ?? null,
|
||||
seoDescription: seoDescription ?? null,
|
||||
seoKeywords: seoKeywords ?? null,
|
||||
},
|
||||
});
|
||||
results.push(updated);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
for (const lang of LANGS) {
|
||||
const langObj = payload[lang];
|
||||
const title = typeof langObj?.title === "string"
|
||||
? langObj.title
|
||||
: typeof payload.title === "string"
|
||||
? (lang === "th" ? payload.title : undefined)
|
||||
: payload.title?.[lang];
|
||||
|
||||
const seoTitle = typeof langObj?.seoTitle === "string" || langObj?.seoTitle === null
|
||||
? langObj.seoTitle
|
||||
: typeof payload.seoTitle === "string" || payload.seoTitle === null
|
||||
? (lang === "th" ? payload.seoTitle : undefined)
|
||||
: payload.seoTitle?.[lang];
|
||||
|
||||
const seoDescription = typeof langObj?.seoDescription === "string" || langObj?.seoDescription === null
|
||||
? langObj.seoDescription
|
||||
: typeof payload.seoDescription === "string" || payload.seoDescription === null
|
||||
? (lang === "th" ? payload.seoDescription : undefined)
|
||||
: payload.seoDescription?.[lang];
|
||||
|
||||
const seoKeywords = typeof langObj?.seoKeywords === "string" || langObj?.seoKeywords === null
|
||||
? langObj.seoKeywords
|
||||
: typeof payload.seoKeywords === "string" || payload.seoKeywords === null
|
||||
? (lang === "th" ? payload.seoKeywords : undefined)
|
||||
: payload.seoKeywords?.[lang];
|
||||
|
||||
if (title === undefined && seoTitle === undefined && seoDescription === undefined && seoKeywords === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const updateData: any = {};
|
||||
if (title !== undefined) updateData.title = title;
|
||||
if (seoTitle !== undefined) updateData.seoTitle = seoTitle;
|
||||
if (seoDescription !== undefined) updateData.seoDescription = seoDescription;
|
||||
if (seoKeywords !== undefined) updateData.seoKeywords = seoKeywords;
|
||||
|
||||
const updated = await prisma.pageTranslation.upsert({
|
||||
where: { pageId_langCode: { pageId, langCode: lang } },
|
||||
update: updateData,
|
||||
create: {
|
||||
pageId,
|
||||
langCode: lang,
|
||||
title: title ?? page.title,
|
||||
seoTitle: seoTitle ?? null,
|
||||
seoDescription: seoDescription ?? null,
|
||||
seoKeywords: seoKeywords ?? null,
|
||||
},
|
||||
});
|
||||
results.push(updated);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
export const pagesRoute = new Elysia({ prefix: "/pages" })
|
||||
.get("/", async ({ query }) => {
|
||||
const view = String((query as any)?.view || "summary").toLowerCase();
|
||||
@@ -230,6 +324,7 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
|
||||
sections: {
|
||||
orderBy: { order: "asc" },
|
||||
},
|
||||
translations: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -239,6 +334,10 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
|
||||
id: true,
|
||||
slug: true,
|
||||
title: true,
|
||||
seoTitle: true,
|
||||
seoDescription: true,
|
||||
seoKeywords: true,
|
||||
ogImageUrl: true,
|
||||
isPublished: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
@@ -262,6 +361,7 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
|
||||
where: includeInactive ? undefined : { isActive: true },
|
||||
orderBy: { order: "asc" },
|
||||
},
|
||||
translations: true,
|
||||
},
|
||||
});
|
||||
if (!page) {
|
||||
@@ -273,9 +373,22 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
|
||||
page.sections.map(async (section: any) => await hydrateSectionPayload(section, lang))
|
||||
);
|
||||
|
||||
const trans = page.translations?.find((t: any) => t.langCode === lang);
|
||||
const title = trans?.title || page.title;
|
||||
const seoTitle = trans?.seoTitle || page.seoTitle;
|
||||
const seoDescription = trans?.seoDescription || page.seoDescription;
|
||||
const seoKeywords = trans?.seoKeywords || page.seoKeywords;
|
||||
|
||||
return {
|
||||
...page,
|
||||
title,
|
||||
seoTitle,
|
||||
seoDescription,
|
||||
seoKeywords,
|
||||
ogImageUrl: page.ogImageUrl,
|
||||
isPublished: page.isPublished,
|
||||
sections: populatedSections,
|
||||
translations: page.translations,
|
||||
};
|
||||
})
|
||||
.get("/:slug/sections", async ({ params: { slug }, set }) => {
|
||||
@@ -545,6 +658,10 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
|
||||
title: string;
|
||||
slug: string;
|
||||
isPublished?: boolean;
|
||||
seoTitle?: string | null;
|
||||
seoDescription?: string | null;
|
||||
seoKeywords?: string | null;
|
||||
ogImageUrl?: string | null;
|
||||
};
|
||||
section: {
|
||||
type: string;
|
||||
@@ -617,6 +734,10 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
|
||||
title: pageInput.title,
|
||||
slug: pageInput.slug,
|
||||
isPublished: pageInput.isPublished ?? false,
|
||||
seoTitle: pageInput.seoTitle ?? null,
|
||||
seoDescription: pageInput.seoDescription ?? null,
|
||||
seoKeywords: pageInput.seoKeywords ?? null,
|
||||
ogImageUrl: pageInput.ogImageUrl ?? null,
|
||||
},
|
||||
});
|
||||
const section = await tx.pageSection.create({
|
||||
@@ -661,6 +782,10 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
|
||||
title: t.String(),
|
||||
slug: t.String(),
|
||||
isPublished: t.Optional(t.Boolean()),
|
||||
seoTitle: t.Optional(t.Nullable(t.String())),
|
||||
seoDescription: t.Optional(t.Nullable(t.String())),
|
||||
seoKeywords: t.Optional(t.Nullable(t.String())),
|
||||
ogImageUrl: t.Optional(t.Nullable(t.String())),
|
||||
}),
|
||||
section: t.Object({
|
||||
type: t.String(),
|
||||
@@ -677,7 +802,7 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
|
||||
const data = body as any;
|
||||
const operation = data?.id ? "update" : "create";
|
||||
try {
|
||||
const { id, slug, title, isPublished, status, metadata } = data;
|
||||
const { id, slug, title, isPublished, status, metadata, seoTitle, seoDescription, seoKeywords, ogImageUrl, translations } = data;
|
||||
|
||||
if (
|
||||
typeof slug === "string" &&
|
||||
@@ -752,14 +877,41 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
|
||||
}
|
||||
}
|
||||
|
||||
const page = id
|
||||
? await prisma.page.update({
|
||||
where: { id: Number(id) },
|
||||
data: { slug, title, isPublished: finalIsPublished },
|
||||
})
|
||||
: await prisma.page.create({
|
||||
data: { slug, title, isPublished: finalIsPublished },
|
||||
});
|
||||
let page: any;
|
||||
if (id) {
|
||||
const updateData: any = { slug, title, isPublished: finalIsPublished };
|
||||
if (seoTitle !== undefined) updateData.seoTitle = seoTitle;
|
||||
if (seoDescription !== undefined) updateData.seoDescription = seoDescription;
|
||||
if (seoKeywords !== undefined) updateData.seoKeywords = seoKeywords;
|
||||
if (ogImageUrl !== undefined) updateData.ogImageUrl = ogImageUrl;
|
||||
|
||||
page = await prisma.page.update({
|
||||
where: { id: Number(id) },
|
||||
data: updateData,
|
||||
include: { translations: true },
|
||||
});
|
||||
} else {
|
||||
page = await prisma.page.create({
|
||||
data: {
|
||||
slug,
|
||||
title,
|
||||
isPublished: finalIsPublished,
|
||||
seoTitle: seoTitle ?? null,
|
||||
seoDescription: seoDescription ?? null,
|
||||
seoKeywords: seoKeywords ?? null,
|
||||
ogImageUrl: ogImageUrl ?? null,
|
||||
},
|
||||
include: { translations: true },
|
||||
});
|
||||
}
|
||||
|
||||
if (translations) {
|
||||
await savePageTranslations(page.id, translations);
|
||||
page = await prisma.page.findUnique({
|
||||
where: { id: page.id },
|
||||
include: { translations: true },
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -802,6 +954,80 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
|
||||
slug: t.String(),
|
||||
title: t.String(),
|
||||
isPublished: t.Optional(t.Boolean()),
|
||||
seoTitle: t.Optional(t.Nullable(t.String())),
|
||||
seoDescription: t.Optional(t.Nullable(t.String())),
|
||||
seoKeywords: t.Optional(t.Nullable(t.String())),
|
||||
ogImageUrl: t.Optional(t.Nullable(t.String())),
|
||||
translations: t.Optional(t.Any()),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.put(
|
||||
"/:id",
|
||||
async ({ params: { id: pageParam }, body, set }) => {
|
||||
try {
|
||||
const numId = Number(pageParam);
|
||||
const isNumericId = Number.isInteger(numId) && numId > 0;
|
||||
const existing = await prisma.page.findFirst({
|
||||
where: isNumericId ? { id: numId } : { slug: pageParam },
|
||||
});
|
||||
if (!existing) {
|
||||
set.status = 404;
|
||||
return { error: "Page not found" };
|
||||
}
|
||||
|
||||
const data = body as any;
|
||||
const { slug, title, isPublished, seoTitle, seoDescription, seoKeywords, ogImageUrl, translations } = data;
|
||||
|
||||
if (
|
||||
typeof slug === "string" &&
|
||||
slug !== existing.slug &&
|
||||
RESERVED_SLUGS.includes(slug.toLowerCase())
|
||||
) {
|
||||
set.status = 400;
|
||||
return { success: false, error: "Reserved slug cannot be used" };
|
||||
}
|
||||
|
||||
const updateData: any = {};
|
||||
if (slug !== undefined) updateData.slug = slug;
|
||||
if (title !== undefined) updateData.title = title;
|
||||
if (isPublished !== undefined) updateData.isPublished = isPublished;
|
||||
if (seoTitle !== undefined) updateData.seoTitle = seoTitle;
|
||||
if (seoDescription !== undefined) updateData.seoDescription = seoDescription;
|
||||
if (seoKeywords !== undefined) updateData.seoKeywords = seoKeywords;
|
||||
if (ogImageUrl !== undefined) updateData.ogImageUrl = ogImageUrl;
|
||||
|
||||
let page = await prisma.page.update({
|
||||
where: { id: existing.id },
|
||||
data: updateData,
|
||||
include: { translations: true },
|
||||
});
|
||||
|
||||
if (translations) {
|
||||
await savePageTranslations(page.id, translations);
|
||||
page = await prisma.page.findUnique({
|
||||
where: { id: page.id },
|
||||
include: { translations: true, sections: { orderBy: { order: "asc" } } },
|
||||
}) as any;
|
||||
}
|
||||
|
||||
return { success: true, page };
|
||||
} catch (err) {
|
||||
console.error("[Pages] Update Error:", err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to update page" };
|
||||
}
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
slug: t.Optional(t.String()),
|
||||
title: t.Optional(t.String()),
|
||||
isPublished: t.Optional(t.Boolean()),
|
||||
seoTitle: t.Optional(t.Nullable(t.String())),
|
||||
seoDescription: t.Optional(t.Nullable(t.String())),
|
||||
seoKeywords: t.Optional(t.Nullable(t.String())),
|
||||
ogImageUrl: t.Optional(t.Nullable(t.String())),
|
||||
translations: t.Optional(t.Any()),
|
||||
}),
|
||||
},
|
||||
)
|
||||
@@ -818,41 +1044,12 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
|
||||
})
|
||||
.patch("/:id/translations", async ({ params, body, set }) => {
|
||||
const id = Number(params.id);
|
||||
if (isNaN(id)) {
|
||||
if (isNaN(id) || id <= 0) {
|
||||
set.status = 400;
|
||||
return { error: "Invalid page ID" };
|
||||
}
|
||||
|
||||
const payload = body as any;
|
||||
const results: any[] = [];
|
||||
const LANGS = ["th", "en", "ja", "zh", "vi", "ko", "lo", "it"];
|
||||
|
||||
for (const lang of LANGS) {
|
||||
const title =
|
||||
typeof payload.title === "string"
|
||||
? lang === "th"
|
||||
? payload.title
|
||||
: null
|
||||
: payload.title?.[lang];
|
||||
if (!title) continue;
|
||||
|
||||
const existing = await prisma.pageTranslation.findUnique({
|
||||
where: { pageId_langCode: { pageId: id, langCode: lang } },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
const updated = await prisma.pageTranslation.update({
|
||||
where: { id: existing.id },
|
||||
data: { title },
|
||||
});
|
||||
results.push(updated);
|
||||
} else {
|
||||
const created = await prisma.pageTranslation.create({
|
||||
data: { pageId: id, langCode: lang, title },
|
||||
});
|
||||
results.push(created);
|
||||
}
|
||||
}
|
||||
const results = await savePageTranslations(id, body);
|
||||
|
||||
const updated = await prisma.page.findUnique({
|
||||
where: { id },
|
||||
@@ -860,7 +1057,23 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
|
||||
});
|
||||
|
||||
return { success: true, translations: results, page: updated };
|
||||
})
|
||||
}, { body: t.Any() })
|
||||
.post("/:id/translations", async ({ params, body, set }) => {
|
||||
const id = Number(params.id);
|
||||
if (isNaN(id) || id <= 0) {
|
||||
set.status = 400;
|
||||
return { error: "Invalid page ID" };
|
||||
}
|
||||
|
||||
const results = await savePageTranslations(id, body);
|
||||
|
||||
const updated = await prisma.page.findUnique({
|
||||
where: { id },
|
||||
include: { translations: true },
|
||||
});
|
||||
|
||||
return { success: true, translations: results, page: updated };
|
||||
}, { body: t.Any() })
|
||||
.post(
|
||||
"/:slug/sections",
|
||||
async ({ params: { slug }, body, set }) => {
|
||||
|
||||
Reference in new issue
Block a user