Files
homaroma/apps/backend/src/index.ts
T
2026-06-27 23:59:16 -04:00

148 lines
5.4 KiB
TypeScript

import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";
import { swagger } from "@elysiajs/swagger";
import { jwt } from "@elysiajs/jwt";
import { staticPlugin } from "@elysiajs/static";
import { categoriesRoute } from "./routes/categories";
import { productsRoute } from "./routes/products";
import { authRoute } from "./routes/auth";
import { uploadRoute } from "./routes/upload";
import { ordersRoute } from "./routes/orders";
import { settingsRoute } from "./routes/settings";
import { articlesRoute } from "./routes/articles";
import { tiktokRoute } from "./routes/tiktok";
import { rawMaterialsRoute } from "./routes/raw-materials";
import { shopeeRoute } from "./routes/shopee";
import { tiktokOrderRoute } from "./routes/tiktokOrder";
import { lazadaOrderRoute } from "./routes/lazadaOrder";
import { customersRoute } from "./routes/customers";
import { couponsRoute } from "./routes/coupons";
import { reviewsRoute } from "./routes/reviews";
import { promotionsRoute } from "./routes/promotions";
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";
import { mediaRoute } from "./routes/media";
import { contactsRoute } from "./routes/contacts";
import { productAttributesRoute } from "./routes/product-attributes";
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_brand";
const isDevelopment = process.env.NODE_ENV !== "production";
const isPrivateLanHostname = (hostname: string) => {
return (
hostname === "localhost" ||
hostname === "127.0.0.1" ||
hostname === "0.0.0.0" ||
hostname === "::1" ||
/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname) ||
/^192\.168\.\d{1,3}\.\d{1,3}$/.test(hostname) ||
/^172\.(1[6-9]|2\d|3[0-1])\.\d{1,3}\.\d{1,3}$/.test(hostname)
);
};
const allowLanDevOrigin = (request: Request) => {
const origin = request.headers.get("origin");
if (!origin) return false;
try {
return isPrivateLanHostname(new URL(origin).hostname);
} catch {
return false;
}
};
const app = new Elysia()
.onRequest(({ request }) => {
console.log(`\n➡️ [${request.method}] ${request.url}`);
})
.onAfterHandle(({ request, set }) => {
console.log(`✅ [${request.method}] ${request.url} → ${set.status ?? 200}`);
})
.onError(({ request, error, set }) => {
console.error(`❌ [${request.method}] ${request.url} → ${set.status ?? 500}`, error?.message ?? error);
})
.onParse(async ({ request }) => {
console.log('[onParse] Incoming request URL:', request.url);
if (request.url.includes('/api/line/webhook')) {
const text = await request.text();
console.log('[onParse] Intercepted LINE webhook raw body length:', text.length);
return text;
}
})
.use(
cors({
origin: [
...CORS_ORIGIN.split(",").map((o) => o.trim()),
...(isDevelopment ? [allowLanDevOrigin] : []),
"https://admin.homaroma.shop",
"https://spa.homaroma.shop", // LIFF (SPA)
"https://homaroma.shop", // Main Website
"http://localhost:5174", // Admin
"http://localhost:4173",
"http://localhost:3001", // SSR Next.js
"https://react5173.kitizone.com",
"https://elysia3051.kitizone.com"
],
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
credentials: true,
})
)
.use(
jwt({
name: "jwt",
secret: JWT_SECRET,
exp: "24h",
})
)
.use(
swagger({
documentation: {
info: {
title: "HomAroma API",
version: "1.0.0",
description: "API for HomAroma & Essential Oil Products",
},
tags: [
{ name: "categories", description: "Product categories" },
{ name: "products", description: "Products and variants" },
{ name: "auth", description: "Authentication for Admin" },
],
},
})
)
.use(staticPlugin({
assets: "public",
prefix: "", // use empty string for root
}))
.get("/", () => ({
name: "HomAroma API",
version: "1.0.0",
status: "running",
docs: `/swagger`,
}))
// Explicit route for uploads to guarantee it works even if staticPlugin fails
.get("/uploads/:filename", ({ params: { filename }, set }) => {
const file = Bun.file(`public/uploads/${filename}`);
if (file.size === 0) {
set.status = 404;
return "File not found";
}
return file;
})
.group("/api", (app) =>
app.use(authRoute).use(categoriesRoute).use(productsRoute).use(productAttributesRoute).use(uploadRoute).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).use(mediaRoute).use(contactsRoute)
)
.listen({ port: PORT, hostname: "0.0.0.0" });
console.log(
`🌿 HomAroma API is running at http://${app.server?.hostname}:${app.server?.port}`
);
console.log(
`📖 Swagger docs: http://${app.server?.hostname}:${app.server?.port}/swagger`
);