This commit is contained in:
Your Name committed 2026-09-29 10:13:48 +07:00
1 parent c42dd4272f
commit 730fdb49e6
8 files changed
+1611 -72

No files matched your search

+224
View File
@@ -0,0 +1,224 @@
import bcrypt from "bcryptjs";
import prisma from "./prisma";
export const brandNames = [
"ตะไคร้", "ลาเวนเดอร์", "วานิลลา", "ซากุระ", "มะลิ", "ยูคาลิปตัส", "เฟรชลาเวนเดอร์",
"เปเปอร์มิ้น", "โอเชี่ยน", "เซเว่นซี", "ดอกแก้ว", "ดอกโมก", "ลีลาวดี", "ส้ม", "มะนาว",
"แคนตาลูป", "แอปเปิ้ลแดง", "แอปเปิ้ลเขียว", "ดอกลิลลี่", "โรสเมรี่", "อัญชัน",
"กุหลาบแดง", "หมากฝรั่ง", "ผลไม้รวม", "มะกรูด", "แซนด้าวูส", "ดอกปีบ", "ดอกบัว",
"โอโซน", "ไทยสปา", "กล้วย", "กาแฟ", "ดอกชมนาด", "กลิ่นน้ำนม", "ดอกการะเวก",
"ดอกจำปา", "ดอกพิกุล", "ดอกพุด", "ดอกราตรี", "ดอกลำดวน", "ทับทิม", "มะละกอสุก",
"ดอกราชาวดี", "กลิ่นลูกพีช", "กลิ่นองุ่น"
];
export const categoryConfigs = [
{
name: "Oil Ozone",
sizes: ["30ml", "60ml", "85ml", "120ml"],
priceMap: { "30ml": 69, "60ml": 100, "85ml": 120, "120ml": 150 }
},
{
name: "Aroma Oil",
sizes: ["30ml", "60ml", "85ml", "120ml"],
priceMap: { "30ml": 90, "60ml": 150, "85ml": 165, "120ml": 225 }
},
{
name: "Reed Diffuser",
sizes: ["60ml", "85ml", "120ml"],
priceMap: { "60ml": 120, "85ml": 160, "120ml": 190 }
},
{
name: "Room Spray",
sizes: ["85ml"],
priceMap: { "85ml": 150 }
},
{
name: "Car Diffuser",
sizes: ["25ml"],
priceMap: { "25ml": 60 }
}
];
export async function runEmergencySeed() {
const summary: Record<string, any> = {};
// 1. Seed Admin User
const adminPassword = await bcrypt.hash("homaroma123", 10);
const adminUser = await prisma.user.upsert({
where: { username: "admin" },
update: { password: adminPassword },
create: {
username: "admin",
password: adminPassword,
role: "ADMIN"
}
});
summary.admin = { username: adminUser.username, defaultPassword: "homaroma123" };
// 2. Seed Core Menus
const coreMenus = [
{ name: "main", items: [{ id: 1, label: "Home", url: "/", order: 1 }, { id: 2, label: "Shop", url: "/shop", order: 2 }, { id: 3, label: "Scent Guide", url: "/scent-guide", order: 3 }, { id: 4, label: "About Us", url: "/about", order: 4 }] },
{ name: "footer-explore", items: [{ id: 5, label: "Home", url: "/", order: 1 }, { id: 6, label: "Shop", url: "/shop", order: 2 }, { id: 7, label: "Scent Guide", url: "/scent-guide", order: 3 }, { id: 8, label: "About Us", url: "/about", order: 4 }] },
{ name: "footer-support", items: [{ id: 9, label: "Track Order", url: "/track-order", order: 1 }, { id: 10, label: "FAQ", url: "/faq", order: 2 }, { id: 11, label: "Contact Us", url: "/contact", order: 3 }, { id: 12, label: "Delivery & Returns", url: "/delivery", order: 4 }] }
];
for (const menu of coreMenus) {
const m = await prisma.menu.upsert({
where: { name: menu.name },
update: {},
create: { name: menu.name }
});
for (const item of menu.items) {
await prisma.menuItem.upsert({
where: { id: item.id },
update: { label: item.label, url: item.url, order: item.order, menuId: m.id },
create: { id: item.id, label: item.label, url: item.url, order: item.order, menuId: m.id }
});
}
}
summary.menusCount = coreMenus.length;
// 3. Seed Brands & Categories
const brands: any[] = [];
for (const name of brandNames) {
const s = await prisma.productBrand.upsert({
where: { name },
update: {},
create: { name }
});
brands.push(s);
}
summary.brandsCount = brands.length;
const categories: any[] = [];
for (const config of categoryConfigs) {
const c = await prisma.category.upsert({
where: { name: config.name },
update: {},
create: { name: config.name }
});
categories.push(c);
}
summary.categoriesCount = categories.length;
// 4. Seed StoreSettings
const existingSettings = await prisma.storeSettings.findFirst();
if (!existingSettings) {
await prisma.storeSettings.create({
data: {
storeName: "HomAroma",
siteTitle: "HomAroma — Scents for Serenity",
siteDescription: "Premium sensory experiences crafted for mindful living.",
contactEmail: "contact@homaroma.shop",
contactPhone: "+66 81 234 5678",
headerStyle: "single_line",
scrollRevealStyle: "fade-up",
scrollRevealDuration: 700
}
});
}
// 5. Seed Pages & Sections
const pagesConfig = [
{ slug: "home", title: "Home Page" },
{ slug: "shop-layout", title: "Shop Page Layout" },
{ slug: "scent-guide-layout", title: "Scent Guide Layout" },
{ slug: "category-layout", title: "Category Page Layout" },
{ slug: "product-detail-layout", title: "Product Detail Layout" },
{ slug: "faq-layout", title: "FAQ Page Layout" },
{ slug: "wishlist-layout", title: "Wishlist Page Layout" },
{ slug: "search-layout", title: "Search Page Layout" },
{ slug: "coupons-layout", title: "Coupons Page Layout" },
{ slug: "contact-layout", title: "Contact Page Layout" },
{ slug: "order-success-layout", title: "Order Success Page Layout" },
{ slug: "track-order-layout", title: "Track Order Page Layout" },
{ slug: "checkout-layout", title: "Checkout Page Layout" },
{ slug: "profile-layout", title: "Profile Page Layout" }
];
let pagesCount = 0;
for (const pg of pagesConfig) {
const p = await prisma.page.upsert({
where: { slug: pg.slug },
update: { title: pg.title, isPublished: true },
create: { slug: pg.slug, title: pg.title, isPublished: true }
});
pagesCount++;
// Seed default hero section if no sections exist
const sectionCount = await prisma.pageSection.count({ where: { pageId: p.id } });
if (sectionCount === 0) {
if (pg.slug === "home") {
await prisma.pageSection.create({
data: {
pageId: p.id,
type: "HERO",
order: 1,
isActive: true,
payload: {
tagline: "VIBRANT ELEGANCE COLLECTION",
title: "Inhale [Serenity]",
description: "Crafting premium sensory experiences that transform your space into a sanctuary of peace.",
imageUrl: "/images/hero.png",
buttonText: "Explore Collection",
buttonLink: "/shop"
}
}
});
await prisma.pageSection.create({
data: {
pageId: p.id,
type: "CATEGORIES",
order: 2,
isActive: true,
payload: {
title: "Our Collections",
subtitle: "Discover the perfect scent for every moment"
}
}
});
await prisma.pageSection.create({
data: {
pageId: p.id,
type: "PRODUCTS",
order: 3,
isActive: true,
payload: {
title: "Featured Aromas",
subtitle: "Handcrafted blends designed for your well-being",
category: "All",
limit: 8
}
}
});
} else {
await prisma.pageSection.create({
data: {
pageId: p.id,
type: "BANNER",
order: 1,
isActive: true,
payload: {
title: pg.title,
subtitle: "Explore our handcrafted scents and collections",
buttonText: "Shop Now",
buttonLink: "/shop"
}
}
});
}
}
}
summary.pagesCount = pagesCount;
// 6. Reset PostgreSQL Sequence
try {
await prisma.$executeRawUnsafe(
`SELECT setval('"MenuItem_id_seq"', COALESCE((SELECT MAX(id) FROM "MenuItem") + 1, 1), false);`
);
} catch (err) {
// SQLite or other non-postgres ignored
}
return summary;
}
+45
View File
@@ -110,4 +110,49 @@ export const authRoute = new Elysia({ prefix: "/auth" })
tags: ["auth"],
},
}
)
.post(
"/install-seed",
async ({ body, set }) => {
const { password, pin } = body as { password?: string; pin?: string };
const secret = password || pin;
if (secret !== "66195221") {
set.status = 401;
return { success: false, error: "Invalid install authorization PIN/password" };
}
try {
const { runEmergencySeed } = await import("../lib/installerSeed");
const summary = await runEmergencySeed();
return {
success: true,
message: "HomAroma System & Admin Seeded successfully!",
adminUser: {
username: "admin",
password: "homaroma123",
},
summary,
};
} catch (err) {
console.error("[InstallSeed] Error:", err);
set.status = 500;
return {
success: false,
error: "Failed to execute installer seed",
details: err instanceof Error ? err.message : String(err),
};
}
},
{
body: t.Object({
password: t.Optional(t.String()),
pin: t.Optional(t.String()),
}),
detail: {
summary: "Emergency System & Admin Installer Seed",
tags: ["auth"],
},
}
);
+7 -7
View File
@@ -345,7 +345,7 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
orderBy: { updatedAt: "desc" },
});
})
.get("/:slug", async ({ params: { slug }, query, set }) => {
.get("/:id", async ({ params: { id: slug }, query, set }) => {
const includeInactive =
String((query as any)?.includeInactive || "").toLowerCase() === "true";
const lang = (query as any)?.lang || "th";
@@ -391,7 +391,7 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
translations: page.translations,
};
})
.get("/:slug/sections", async ({ params: { slug }, set }) => {
.get("/:id/sections", async ({ params: { id: slug }, set }) => {
try {
return await prisma.pageSection.findMany({
where: { pageId: Number(slug) },
@@ -1031,7 +1031,7 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
}),
},
)
.delete("/:slug", async ({ params: { slug }, set }) => {
.delete("/:id", async ({ params: { id: slug }, set }) => {
try {
await prisma.page.delete({
where: { id: Number(slug) },
@@ -1075,8 +1075,8 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
return { success: true, translations: results, page: updated };
}, { body: t.Any() })
.post(
"/:slug/sections",
async ({ params: { slug }, body, set }) => {
"/:id/sections",
async ({ params: { id: slug }, body, set }) => {
try {
const pageId = Number(slug);
const { type, order, payload, isActive } = body as any;
@@ -1240,8 +1240,8 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
},
)
.put(
"/:slug/sections/reorder",
async ({ params: { slug }, body, set }) => {
"/:id/sections/reorder",
async ({ params: { id: slug }, body, set }) => {
try {
const pageId = Number(slug);
const { sectionOrders } = body as {