x
This commit is contained in:
1 parent
59363616c4
commit
20f43bd58d
13 files changed
+789
-42
No files matched your search
@@ -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
|
||||
}
|
||||
@@ -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" });
|
||||
|
||||
|
||||
@@ -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()
|
||||
}))
|
||||
})
|
||||
});
|
||||
@@ -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" }
|
||||
});
|
||||
});
|
||||
@@ -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())
|
||||
})
|
||||
});
|
||||
Reference in new issue
Block a user