This commit is contained in:
Your Name committed 2026-06-13 19:45:46 -04:00
1 parent 59363616c4
commit 20f43bd58d
13 files changed
+789 -42

No files matched your search

+34 -1
View File
@@ -155,7 +155,22 @@ model StoreSettings {
bankAccountName String @default("HomAroma Co., Ltd.")
bankAccountNumber String @default("012-3-45678-9")
pointsEarnRate Float @default(1.0)
updatedAt DateTime @updatedAt
facebookUrl String?
footerDescription String?
instagramUrl String?
tiktokUrl String?
newsletterTitle String?
newsletterDescription String?
newsletterPlaceholder String?
newsletterButtonText String?
updatedAt DateTime @updatedAt
}
model NewsletterSubscriber {
id Int @id @default(autoincrement())
email String @unique
isActive Boolean @default(true)
createdAt DateTime @default(now())
}
model Order {
@@ -468,3 +483,21 @@ model NewsletterSubscription {
updatedAt DateTime @updatedAt
}
model Menu {
id Int @id @default(autoincrement())
name String @unique // e.g., "header", "footer"
items MenuItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model MenuItem {
id Int @id @default(autoincrement())
menuId Int
menu Menu @relation(fields: [menuId], references: [id], onDelete: Cascade)
label String
url String
order Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
+3 -1
View File
@@ -24,6 +24,8 @@ import { pagesRoute } from "./routes/pages";
import { paymentsRoute } from "./routes/payments";
import { profileRoute } from "./routes/profile";
import { lineRoutes } from "./routes/line";
import { menusRoute } from "./routes/menus";
import { newsletterRoute } from "./routes/newsletter";
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3051;
const CORS_ORIGIN = process.env.CORS_ORIGIN || "http://localhost:5173";
const JWT_SECRET = process.env.JWT_SECRET || "homaroma_secret_2026_scent";
@@ -97,7 +99,7 @@ const app = new Elysia()
return file;
})
.group("/api", (app) =>
app.use(authRoute).use(categoriesRoute).use(productsRoute).use(uploadRoute).use(heroRoute).use(ordersRoute).use(settingsRoute).use(articlesRoute).use(tiktokRoute).use(rawMaterialsRoute).use(shopeeRoute).use(tiktokOrderRoute).use(lazadaOrderRoute).use(customersRoute).use(couponsRoute).use(reviewsRoute).use(promotionsRoute).use(pagesRoute).use(paymentsRoute).use(profileRoute).use(lineRoutes)
app.use(authRoute).use(categoriesRoute).use(productsRoute).use(uploadRoute).use(heroRoute).use(ordersRoute).use(settingsRoute).use(articlesRoute).use(tiktokRoute).use(rawMaterialsRoute).use(shopeeRoute).use(tiktokOrderRoute).use(lazadaOrderRoute).use(customersRoute).use(couponsRoute).use(reviewsRoute).use(promotionsRoute).use(pagesRoute).use(paymentsRoute).use(profileRoute).use(lineRoutes).use(menusRoute).use(newsletterRoute)
)
.listen({ port: PORT, hostname: "0.0.0.0" });
+96
View File
@@ -0,0 +1,96 @@
import { Elysia, t } from "elysia";
import prisma from "../lib/prisma";
export const menusRoute = new Elysia({ prefix: "/menus" })
.get("/", async () => {
return await prisma.menu.findMany({
include: {
items: {
orderBy: { order: "asc" }
}
}
});
})
.get("/:name", async ({ params: { name }, set }) => {
const menu = await prisma.menu.findUnique({
where: { name },
include: {
items: {
orderBy: { order: "asc" }
}
}
});
if (!menu) {
set.status = 404;
return { error: "Menu not found" };
}
return menu;
})
.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 } = body as { items: { id?: number; label: string; url: string; order: number }[] };
let menu = await prisma.menu.findUnique({ where: { name } });
if (!menu) {
menu = await prisma.menu.create({ data: { name } });
}
await prisma.$transaction([
prisma.menuItem.deleteMany({ where: { menuId: menu.id } }),
prisma.menuItem.createMany({
data: items.map(item => ({
menuId: menu.id,
label: item.label,
url: item.url,
order: item.order
}))
})
]);
return await prisma.menu.findUnique({
where: { name },
include: { items: { orderBy: { order: "asc" } } }
});
} catch (err) {
console.error(err);
set.status = 500;
return { error: "Failed to update menu" };
}
}, {
body: t.Object({
items: t.Array(t.Object({
id: t.Optional(t.Number()),
label: t.String(),
url: t.String(),
order: t.Number()
}))
})
});
+36
View File
@@ -0,0 +1,36 @@
import { Elysia, t } from "elysia";
import prisma from "../lib/prisma";
export const newsletterRoute = new Elysia({ prefix: "/newsletter" })
.post("/subscribe", async ({ body, set }) => {
try {
const { email } = body as { email: string };
const existing = await prisma.newsletterSubscriber.findUnique({
where: { email }
});
if (existing) {
return { message: "Email already subscribed" };
}
await prisma.newsletterSubscriber.create({
data: { email }
});
return { message: "Successfully subscribed" };
} catch (err) {
console.error(err);
set.status = 500;
return { error: "Failed to subscribe" };
}
}, {
body: t.Object({
email: t.String({ format: "email" })
})
})
.get("/subscribers", async () => {
return await prisma.newsletterSubscriber.findMany({
orderBy: { createdAt: "desc" }
});
});
+9 -1
View File
@@ -41,6 +41,14 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
bankName: t.String(),
bankAccountName: t.String(),
bankAccountNumber: t.String(),
pointsEarnRate: t.Number()
pointsEarnRate: t.Number(),
facebookUrl: t.Optional(t.String()),
instagramUrl: t.Optional(t.String()),
tiktokUrl: t.Optional(t.String()),
footerDescription: t.Optional(t.String()),
newsletterTitle: t.Optional(t.String()),
newsletterDescription: t.Optional(t.String()),
newsletterPlaceholder: t.Optional(t.String()),
newsletterButtonText: t.Optional(t.String())
})
});