Files
homaroma/apps/backend/src/routes/menus.ts
T
2026-06-15 19:00:31 -04:00

194 lines
6.1 KiB
TypeScript

import { Elysia, t } from "elysia";
import prisma from "../lib/prisma";
import { adminOnly } from "../middleware/auth";
export const menusRoute = new Elysia({ prefix: "/menus" })
.get("/", async () => {
return await prisma.menu.findMany({
include: {
items: {
orderBy: { order: "asc" },
include: { translations: true }
}
}
});
})
.get("/:name", async ({ params: { name }, set }) => {
const menu = await prisma.menu.findUnique({
where: { name },
include: {
items: {
orderBy: { order: "asc" },
include: { translations: true }
}
}
});
if (!menu) {
return { name, items: [], payload: null, isActive: false };
}
return menu;
})
.use(adminOnly)
.post("/", async ({ body, set }) => {
try {
const { name, items } = body as { name: string; items?: { label: string; url: string; order: number }[] };
const menu = await prisma.menu.create({
data: {
name,
items: items ? {
create: items
} : undefined
},
include: { items: true }
});
return menu;
} catch (err) {
console.error(err);
set.status = 500;
return { error: "Failed to create menu" };
}
}, {
body: t.Object({
name: t.String(),
items: t.Optional(t.Array(t.Object({
label: t.String(),
url: t.String(),
order: t.Number()
})))
})
})
.put("/:name", async ({ params: { name }, body, set }) => {
try {
const { items, payload, isActive } = body as {
payload?: any;
isActive?: boolean;
items: { id?: number; label: string; url: string; order: number; translations?: { langCode: string; label: string }[] }[]
};
let menu = await prisma.menu.findUnique({ where: { name } });
const updateData: any = {};
if (payload !== undefined) updateData.payload = payload;
if (isActive !== undefined) updateData.isActive = isActive;
if (!menu) {
menu = await prisma.menu.create({ data: { name, ...updateData } });
} else if (Object.keys(updateData).length > 0) {
menu = await prisma.menu.update({ where: { name }, data: updateData });
}
await prisma.$transaction(async (tx: any) => {
const existingItems = await tx.menuItem.findMany({ where: { menuId: menu!.id } });
const existingItemIds = existingItems.map((i: any) => i.id);
const payloadItemIds = items.map(i => i.id).filter(id => id != null);
const itemsToDelete = existingItemIds.filter((id: number) => !payloadItemIds.includes(id));
if (itemsToDelete.length > 0) {
await tx.menuItem.deleteMany({ where: { id: { in: itemsToDelete } } });
}
for (const item of items) {
if (item.id && existingItemIds.includes(item.id)) {
await tx.menuItem.update({
where: { id: item.id },
data: { label: item.label, url: item.url, order: item.order }
});
if (item.translations?.length) {
for (const t of item.translations) {
await tx.menuItemTranslation.upsert({
where: { menuItemId_langCode: { menuItemId: item.id, langCode: t.langCode } },
update: { label: t.label },
create: { menuItemId: item.id, langCode: t.langCode, label: t.label }
});
}
}
} else {
await tx.menuItem.create({
data: {
menuId: menu!.id,
label: item.label,
url: item.url,
order: item.order,
translations: item.translations?.length ? {
create: item.translations.map(t => ({
langCode: t.langCode,
label: t.label
}))
} : undefined
}
});
}
}
});
return await prisma.menu.findUnique({
where: { name },
include: { items: { orderBy: { order: "asc" }, include: { translations: true } } }
});
} catch (err) {
console.error(err);
set.status = 500;
return { error: "Failed to update menu" };
}
}, {
body: t.Object({
payload: t.Optional(t.Any()),
isActive: t.Optional(t.Boolean()),
items: t.Array(t.Object({
id: t.Optional(t.Number()),
label: t.String(),
url: t.String(),
order: t.Number(),
translations: t.Optional(t.Array(t.Object({
langCode: t.String(),
label: t.String()
})))
}))
})
})
.patch("/items/:id/translations", async ({ params, body, set }) => {
const id = Number(params.id);
if (isNaN(id)) {
set.status = 400;
return { error: "Invalid menu item ID" };
}
const menuItem = await prisma.menuItem.findUnique({ where: { id } });
if (!menuItem) {
set.status = 404;
return { error: "Menu item not found. Please ensure database is seeded or the item exists." };
}
const payload = body as any;
const results: any[] = [];
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko'];
for (const lang of LANGS) {
const label = typeof payload.label === 'string' ? (lang === 'th' ? payload.label : null) : payload.label?.[lang];
if (!label) continue;
const existing = await prisma.menuItemTranslation.findUnique({
where: { menuItemId_langCode: { menuItemId: id, langCode: lang } }
});
if (existing) {
const updated = await prisma.menuItemTranslation.update({
where: { id: existing.id },
data: { label }
});
results.push(updated);
} else {
const created = await prisma.menuItemTranslation.create({
data: { menuItemId: id, langCode: lang, label }
});
results.push(created);
}
}
const updated = await prisma.menuItem.findUnique({
where: { id },
include: { translations: true }
});
return { success: true, translations: results, menuItem: updated };
});