fix: generate missing migration for Menu and Newsletter tables
This commit is contained in:
1 parent
363be70e11
commit
f5029fc935
123 files changed
+8683
No files matched your search
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import "dotenv/config";
|
||||
declare const _default: import("prisma/config").PrismaConfigInternal;
|
||||
export default _default;
|
||||
//# sourceMappingURL=prisma.config.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"prisma.config.d.ts","sourceRoot":"","sources":["prisma.config.ts"],"names":[],"mappings":"AAGA,OAAO,eAAe,CAAC;;AAGvB,wBASG"}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Prisma 7 Config
|
||||
// Note: prisma CLI uses jiti (Node.js) to load this file, NOT Bun
|
||||
// So we must import dotenv explicitly here to load DATABASE_URL
|
||||
import "dotenv/config";
|
||||
import { defineConfig } from "prisma/config";
|
||||
export default defineConfig({
|
||||
schema: "prisma/schema.prisma",
|
||||
migrations: {
|
||||
path: "prisma/migrations",
|
||||
seed: "bun prisma/seed.cjs",
|
||||
},
|
||||
datasource: {
|
||||
url: process.env["DATABASE_URL"],
|
||||
},
|
||||
});
|
||||
//# sourceMappingURL=prisma.config.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"prisma.config.js","sourceRoot":"","sources":["prisma.config.ts"],"names":[],"mappings":"AAAA,kBAAkB;AAClB,kEAAkE;AAClE,gEAAgE;AAChE,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,eAAe,YAAY,CAAC;IAC1B,MAAM,EAAE,sBAAsB;IAC9B,UAAU,EAAE;QACV,IAAI,EAAE,mBAAmB;QACzB,IAAI,EAAE,qBAAqB;KAC5B;IACD,UAAU,EAAE;QACV,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;KACjC;CACF,CAAC,CAAC"}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Product" ALTER COLUMN "name" DROP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "StoreSettings" ADD COLUMN "facebookUrl" TEXT,
|
||||
ADD COLUMN "footerDescription" TEXT,
|
||||
ADD COLUMN "instagramUrl" TEXT,
|
||||
ADD COLUMN "newsletterButtonText" TEXT,
|
||||
ADD COLUMN "newsletterDescription" TEXT,
|
||||
ADD COLUMN "newsletterPlaceholder" TEXT,
|
||||
ADD COLUMN "newsletterTitle" TEXT,
|
||||
ADD COLUMN "tiktokUrl" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "NewsletterSubscriber" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "NewsletterSubscriber_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Menu" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Menu_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "MenuItem" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"menuId" INTEGER NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"order" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "MenuItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "NewsletterSubscriber_email_key" ON "NewsletterSubscriber"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Menu_name_key" ON "Menu"("name");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "MenuItem" ADD CONSTRAINT "MenuItem_menuId_fkey" FOREIGN KEY ("menuId") REFERENCES "Menu"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -44,6 +44,9 @@ model Product {
|
||||
price Float? @default(0)
|
||||
categoryId Int
|
||||
category Category @relation(fields: [categoryId], references: [id])
|
||||
name String?
|
||||
description String?
|
||||
details String?
|
||||
scentId Int
|
||||
scent Scent @relation(fields: [scentId], references: [id])
|
||||
variants ProductVariant[]
|
||||
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=seed-pages.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"seed-pages.d.ts","sourceRoot":"","sources":["seed-pages.ts"],"names":[],"mappings":""}
|
||||
@@ -0,0 +1,662 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { Pool } from 'pg';
|
||||
import { PrismaPg } from '@prisma/adapter-pg';
|
||||
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
||||
const adapter = new PrismaPg(pool);
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
async function main() {
|
||||
console.log("🌱 Seeding Dynamic Page Builder...");
|
||||
// 1. Create the HomePage
|
||||
const page = await prisma.page.upsert({
|
||||
where: { slug: "home" },
|
||||
update: {},
|
||||
create: {
|
||||
slug: "home",
|
||||
title: "Home Page",
|
||||
isPublished: true,
|
||||
}
|
||||
});
|
||||
console.log(`✅ Created page: ${page.title} (slug: ${page.slug})`);
|
||||
// Clear existing sections for home page so we don't duplicate
|
||||
await prisma.pageSection.deleteMany({
|
||||
where: { pageId: page.id }
|
||||
});
|
||||
// 2. Create default sections: HERO, CATEGORIES, PRODUCTS, BANNER, TEXT
|
||||
// Section 1: Hero
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: page.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"
|
||||
}
|
||||
}
|
||||
});
|
||||
// Section 2: Categories
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: page.id,
|
||||
type: "CATEGORIES",
|
||||
order: 2,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "Our Collections",
|
||||
subtitle: "Discover the perfect scent for every moment"
|
||||
}
|
||||
}
|
||||
});
|
||||
// Section 3: Products (Featured Picks)
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: page.id,
|
||||
type: "PRODUCTS",
|
||||
order: 3,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "Featured Picks",
|
||||
subtitle: "Hand-picked fragrances recommended just for you",
|
||||
type: "featured",
|
||||
limit: 3
|
||||
}
|
||||
}
|
||||
});
|
||||
// Section 4: Banner
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: page.id,
|
||||
type: "BANNER",
|
||||
order: 4,
|
||||
isActive: true,
|
||||
payload: {
|
||||
imageUrl: "/images/oil_ozone.png",
|
||||
linkUrl: "/category/1",
|
||||
title: "Special Offer",
|
||||
description: "Get 10% off on your first purchase of Oil Ozone collection",
|
||||
buttonText: "Shop Now"
|
||||
}
|
||||
}
|
||||
});
|
||||
// Section 5: Text info
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: page.id,
|
||||
type: "TEXT",
|
||||
order: 5,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "About HomAroma",
|
||||
content: "HomAroma is dedicated to sourcing the finest raw materials to formulate high-quality essential oils, reed diffusers, and room sprays. Our goal is to bring nature's purity directly into your life.",
|
||||
textAlign: "center"
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log("✅ Seeded 5 default home page sections successfully!");
|
||||
// 3. Create the Shop Page Layout
|
||||
const shopPage = await prisma.page.upsert({
|
||||
where: { slug: "shop-layout" },
|
||||
update: {},
|
||||
create: {
|
||||
slug: "shop-layout",
|
||||
title: "Shop Page Layout",
|
||||
isPublished: true,
|
||||
}
|
||||
});
|
||||
console.log(`✅ Created page: ${shopPage.title} (slug: ${shopPage.slug})`);
|
||||
// Clear existing sections for shop page so we don't duplicate
|
||||
await prisma.pageSection.deleteMany({
|
||||
where: { pageId: shopPage.id }
|
||||
});
|
||||
// Shop Section 1: Announcement Bar
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: shopPage.id,
|
||||
type: "ANNOUNCEMENT_BAR",
|
||||
order: 1,
|
||||
isActive: true,
|
||||
payload: {
|
||||
slides: ["[FREE SHIPPING] on orders over ฿1,500", "New Premium Collection is out now!"],
|
||||
backgroundColor: "#1F3D2D",
|
||||
textColor: "#FDFDF9",
|
||||
speed: 3000
|
||||
}
|
||||
}
|
||||
});
|
||||
// Shop Section 2: Hero Banner (Set to active by default to showcase the header capabilities)
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: shopPage.id,
|
||||
type: "HERO",
|
||||
order: 2,
|
||||
isActive: true,
|
||||
payload: {
|
||||
tagline: "CURATED BOTANICALS",
|
||||
title: "The [Shop] Collection",
|
||||
description: "Transform your daily space into a luxury escape with our natural aroma range.",
|
||||
imageUrl: "/images/hero.png",
|
||||
buttonText: "Learn More",
|
||||
buttonLink: "/about"
|
||||
}
|
||||
}
|
||||
});
|
||||
// Shop Section 3: Editorial Tile
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: shopPage.id,
|
||||
type: "EDITORIAL_TILE",
|
||||
order: 3,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "Crafted with [Sensory Passion]",
|
||||
description: "Every formulation at HomAroma is a tribute to pure botanical essences, hand-blended to capture nature’s quiet intelligence.",
|
||||
imageUrl: "/images/oil_ozone.png",
|
||||
imagePosition: "right",
|
||||
buttonText: "Read Our Story",
|
||||
buttonLink: "/about",
|
||||
backgroundColor: "#FDFDF9",
|
||||
textColor: "#1F3D2D"
|
||||
}
|
||||
}
|
||||
});
|
||||
// Shop Section 4: Newsletter
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: shopPage.id,
|
||||
type: "NEWSLETTER",
|
||||
order: 4,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "Begin Your Wellness Journey",
|
||||
description: "Subscribe to receive 10% off your first purchase and exclusive updates.",
|
||||
buttonText: "Subscribe",
|
||||
placeholderText: "Enter your email address",
|
||||
showPrivacyCheckbox: true,
|
||||
privacyText: "I agree to the privacy policy."
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log("✅ Seeded default shop-layout page sections successfully!");
|
||||
// 4. Create the Scent Guide Page Layout
|
||||
const scentGuidePage = await prisma.page.upsert({
|
||||
where: { slug: "scent-guide-layout" },
|
||||
update: {},
|
||||
create: {
|
||||
slug: "scent-guide-layout",
|
||||
title: "Scent Guide Layout",
|
||||
isPublished: true,
|
||||
}
|
||||
});
|
||||
console.log(`✅ Created page: ${scentGuidePage.title} (slug: ${scentGuidePage.slug})`);
|
||||
// Clear existing sections for scent guide page so we don't duplicate
|
||||
await prisma.pageSection.deleteMany({
|
||||
where: { pageId: scentGuidePage.id }
|
||||
});
|
||||
// Scent Guide Section 1: Announcement Bar
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: scentGuidePage.id,
|
||||
type: "ANNOUNCEMENT_BAR",
|
||||
order: 1,
|
||||
isActive: true,
|
||||
payload: {
|
||||
slides: ["Explore our Scent Guide to find your signature fragrance", "Read wellness articles crafted by experts"],
|
||||
backgroundColor: "#1F3D2D",
|
||||
textColor: "#FDFDF9",
|
||||
speed: 3000
|
||||
}
|
||||
}
|
||||
});
|
||||
// Scent Guide Section 2: Hero Banner
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: scentGuidePage.id,
|
||||
type: "HERO",
|
||||
order: 2,
|
||||
isActive: true,
|
||||
payload: {
|
||||
tagline: "JOURNAL & INSIGHTS",
|
||||
title: "The [Scent] Guide",
|
||||
description: "Welcome to the sensory archive of HomAroma. Explore the art and science of botanical aromatherapy.",
|
||||
imageUrl: "/images/hero.png",
|
||||
buttonText: "Read Story",
|
||||
buttonLink: "/about"
|
||||
}
|
||||
}
|
||||
});
|
||||
// Scent Guide Section 3: Newsletter
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: scentGuidePage.id,
|
||||
type: "NEWSLETTER",
|
||||
order: 3,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "Stay Inspired",
|
||||
description: "Subscribe to our journal and get regular wellness guides, blending tips, and exclusive offers.",
|
||||
buttonText: "Subscribe",
|
||||
placeholderText: "Enter your email address",
|
||||
showPrivacyCheckbox: true,
|
||||
privacyText: "I agree to the privacy policy."
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log("✅ Seeded default scent-guide-layout page sections successfully!");
|
||||
// 5. Create Category Page Layout
|
||||
const categoryPage = await prisma.page.upsert({
|
||||
where: { slug: "category-layout" },
|
||||
update: {},
|
||||
create: {
|
||||
slug: "category-layout",
|
||||
title: "Category Page Layout",
|
||||
isPublished: true,
|
||||
}
|
||||
});
|
||||
console.log(`✅ Created page: ${categoryPage.title} (slug: ${categoryPage.slug})`);
|
||||
await prisma.pageSection.deleteMany({
|
||||
where: { pageId: categoryPage.id }
|
||||
});
|
||||
// Announcement Bar for Categories
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: categoryPage.id,
|
||||
type: "ANNOUNCEMENT_BAR",
|
||||
order: 1,
|
||||
isActive: true,
|
||||
payload: {
|
||||
slides: ["Explore our specialized collections for your sanctuary", "Free premium shipping on orders over ฿1,500"],
|
||||
backgroundColor: "#1F3D2D",
|
||||
textColor: "#FDFDF9",
|
||||
speed: 3000
|
||||
}
|
||||
}
|
||||
});
|
||||
// Newsletter at the bottom of Categories
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: categoryPage.id,
|
||||
type: "NEWSLETTER",
|
||||
order: 2,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "Join [HomAroma] Circle",
|
||||
description: "Subscribe to receive announcements of new single-origin botanical releases.",
|
||||
buttonText: "Subscribe",
|
||||
placeholderText: "Enter your email address",
|
||||
showPrivacyCheckbox: true,
|
||||
privacyText: "I agree to the privacy policy."
|
||||
}
|
||||
}
|
||||
});
|
||||
// 6. Create Product Detail Page Layout
|
||||
const productDetailPage = await prisma.page.upsert({
|
||||
where: { slug: "product-detail-layout" },
|
||||
update: {},
|
||||
create: {
|
||||
slug: "product-detail-layout",
|
||||
title: "Product Detail Layout",
|
||||
isPublished: true,
|
||||
}
|
||||
});
|
||||
console.log(`✅ Created page: ${productDetailPage.title} (slug: ${productDetailPage.slug})`);
|
||||
await prisma.pageSection.deleteMany({
|
||||
where: { pageId: productDetailPage.id }
|
||||
});
|
||||
// Announcement Bar for Product Details
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: productDetailPage.id,
|
||||
type: "ANNOUNCEMENT_BAR",
|
||||
order: 1,
|
||||
isActive: true,
|
||||
payload: {
|
||||
slides: ["Special Offer: Get 10% off your first purchase of single-origin oils", "Hand-formulated with pure botanical extracts"],
|
||||
backgroundColor: "#1F3D2D",
|
||||
textColor: "#FDFDF9",
|
||||
speed: 3000
|
||||
}
|
||||
}
|
||||
});
|
||||
// Editorial banner at the bottom of Product Details
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: productDetailPage.id,
|
||||
type: "EDITORIAL_TILE",
|
||||
order: 2,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "The [Botanical] Sourcing",
|
||||
description: "All our raw materials are sustainably harvested at peak potency from verified organic micro-farms worldwide.",
|
||||
imageUrl: "/images/oil_ozone.png",
|
||||
imagePosition: "left",
|
||||
buttonText: "Sourcing Map",
|
||||
buttonLink: "/about",
|
||||
backgroundColor: "#FDFDF9",
|
||||
textColor: "#1F3D2D"
|
||||
}
|
||||
}
|
||||
});
|
||||
// 7. Create FAQ Page Layout
|
||||
const faqPage = await prisma.page.upsert({
|
||||
where: { slug: "faq-layout" },
|
||||
update: {},
|
||||
create: {
|
||||
slug: "faq-layout",
|
||||
title: "FAQ Page Layout",
|
||||
isPublished: true,
|
||||
}
|
||||
});
|
||||
console.log(`✅ Created page: ${faqPage.title} (slug: ${faqPage.slug})`);
|
||||
await prisma.pageSection.deleteMany({
|
||||
where: { pageId: faqPage.id }
|
||||
});
|
||||
// Announcement Bar for FAQ
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: faqPage.id,
|
||||
type: "ANNOUNCEMENT_BAR",
|
||||
order: 1,
|
||||
isActive: true,
|
||||
payload: {
|
||||
slides: ["Need instant help? Reach out on LINE @homaroma", "FAQ updated for Summer 2026 delivery guidelines"],
|
||||
backgroundColor: "#1F3D2D",
|
||||
textColor: "#FDFDF9",
|
||||
speed: 4000
|
||||
}
|
||||
}
|
||||
});
|
||||
// Newsletter at the bottom of FAQ
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: faqPage.id,
|
||||
type: "NEWSLETTER",
|
||||
order: 2,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "Stay informed with [HomAroma]",
|
||||
description: "Sign up to receive shipping updates, care guides, and exclusive collection invites.",
|
||||
buttonText: "Subscribe",
|
||||
placeholderText: "Enter your email address",
|
||||
showPrivacyCheckbox: true,
|
||||
privacyText: "I agree to the privacy policy."
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log("✅ Seeded default category, product-detail, and FAQ layout sections successfully!");
|
||||
// 8. Create Wishlist Page Layout
|
||||
const wishlistPage = await prisma.page.upsert({
|
||||
where: { slug: "wishlist-layout" },
|
||||
update: {},
|
||||
create: {
|
||||
slug: "wishlist-layout",
|
||||
title: "Wishlist Page Layout",
|
||||
isPublished: true,
|
||||
}
|
||||
});
|
||||
console.log(`✅ Created page: ${wishlistPage.title} (slug: ${wishlistPage.slug})`);
|
||||
await prisma.pageSection.deleteMany({ where: { pageId: wishlistPage.id } });
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: wishlistPage.id,
|
||||
type: "ANNOUNCEMENT_BAR",
|
||||
order: 1,
|
||||
isActive: true,
|
||||
payload: {
|
||||
slides: ["Sign up to HomAroma Circle to get 10% off your wishlist items", "Hand-crafted botanicals shipped directly to you"],
|
||||
backgroundColor: "#1F3D2D",
|
||||
textColor: "#FDFDF9",
|
||||
speed: 3000
|
||||
}
|
||||
}
|
||||
});
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: wishlistPage.id,
|
||||
type: "NEWSLETTER",
|
||||
order: 2,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "Don't lose your favorites",
|
||||
description: "Subscribe to receive price drops, back-in-stock alerts, and exclusive discounts on items in your wishlist.",
|
||||
buttonText: "Subscribe",
|
||||
placeholderText: "Enter your email address",
|
||||
showPrivacyCheckbox: true,
|
||||
privacyText: "I agree to the privacy policy."
|
||||
}
|
||||
}
|
||||
});
|
||||
// 9. Create Search Page Layout
|
||||
const searchPage = await prisma.page.upsert({
|
||||
where: { slug: "search-layout" },
|
||||
update: {},
|
||||
create: {
|
||||
slug: "search-layout",
|
||||
title: "Search Page Layout",
|
||||
isPublished: true,
|
||||
}
|
||||
});
|
||||
console.log(`✅ Created page: ${searchPage.title} (slug: ${searchPage.slug})`);
|
||||
await prisma.pageSection.deleteMany({ where: { pageId: searchPage.id } });
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: searchPage.id,
|
||||
type: "ANNOUNCEMENT_BAR",
|
||||
order: 1,
|
||||
isActive: true,
|
||||
payload: {
|
||||
slides: ["Need help finding something? Live Chat with our scent experts on LINE @homaroma"],
|
||||
backgroundColor: "#1F3D2D",
|
||||
textColor: "#FDFDF9",
|
||||
speed: 3000
|
||||
}
|
||||
}
|
||||
});
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: searchPage.id,
|
||||
type: "EDITORIAL_TILE",
|
||||
order: 2,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "Find Your [Signature Scent]",
|
||||
description: "Aromatherapy is the art of tuning your environment. Discover our curation of room sprays, reed diffusers, and essential oils designed for modern sanctuaries.",
|
||||
imageUrl: "/images/hero.png",
|
||||
imagePosition: "right",
|
||||
buttonText: "Explore Guide",
|
||||
buttonLink: "/scent-guide",
|
||||
backgroundColor: "#FDFDF9",
|
||||
textColor: "#1F3D2D"
|
||||
}
|
||||
}
|
||||
});
|
||||
// 10. Create Coupons Page Layout
|
||||
const couponsPage = await prisma.page.upsert({
|
||||
where: { slug: "coupons-layout" },
|
||||
update: {},
|
||||
create: {
|
||||
slug: "coupons-layout",
|
||||
title: "Coupons Page Layout",
|
||||
isPublished: true,
|
||||
}
|
||||
});
|
||||
console.log(`✅ Created page: ${couponsPage.title} (slug: ${couponsPage.slug})`);
|
||||
await prisma.pageSection.deleteMany({ where: { pageId: couponsPage.id } });
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: couponsPage.id,
|
||||
type: "ANNOUNCEMENT_BAR",
|
||||
order: 1,
|
||||
isActive: true,
|
||||
payload: {
|
||||
slides: ["Enjoy limited-time discount codes for your sensory sanctuary"],
|
||||
backgroundColor: "#1F3D2D",
|
||||
textColor: "#FDFDF9",
|
||||
speed: 3000
|
||||
}
|
||||
}
|
||||
});
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: couponsPage.id,
|
||||
type: "NEWSLETTER",
|
||||
order: 2,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "Get Exclusive Secret Codes",
|
||||
description: "Become a subscriber to get weekly private discount codes and early access to new collections.",
|
||||
buttonText: "Subscribe",
|
||||
placeholderText: "Enter your email address",
|
||||
showPrivacyCheckbox: true,
|
||||
privacyText: "I agree to the privacy policy."
|
||||
}
|
||||
}
|
||||
});
|
||||
// 11. Create Contact Page Layout
|
||||
const contactPage = await prisma.page.upsert({
|
||||
where: { slug: "contact-layout" },
|
||||
update: {},
|
||||
create: {
|
||||
slug: "contact-layout",
|
||||
title: "Contact Page Layout",
|
||||
isPublished: true,
|
||||
}
|
||||
});
|
||||
console.log(`✅ Created page: ${contactPage.title} (slug: ${contactPage.slug})`);
|
||||
await prisma.pageSection.deleteMany({ where: { pageId: contactPage.id } });
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: contactPage.id,
|
||||
type: "ANNOUNCEMENT_BAR",
|
||||
order: 1,
|
||||
isActive: true,
|
||||
payload: {
|
||||
slides: ["Customer support is available Mon-Sat, 9:00 - 18:00 on LINE @homaroma"],
|
||||
backgroundColor: "#1F3D2D",
|
||||
textColor: "#FDFDF9",
|
||||
speed: 4000
|
||||
}
|
||||
}
|
||||
});
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: contactPage.id,
|
||||
type: "EDITORIAL_TILE",
|
||||
order: 2,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "Our [Bangkok Studio]",
|
||||
description: "Visit our flagship boutique to experience custom blending sessions and smell our full botanical library in person.",
|
||||
imageUrl: "/images/hero.png",
|
||||
imagePosition: "left",
|
||||
buttonText: "Book Session",
|
||||
buttonLink: "/about",
|
||||
backgroundColor: "#FDFDF9",
|
||||
textColor: "#1F3D2D"
|
||||
}
|
||||
}
|
||||
});
|
||||
// 12. Create Order Success Page Layout
|
||||
const orderSuccessPage = await prisma.page.upsert({
|
||||
where: { slug: "order-success-layout" },
|
||||
update: {},
|
||||
create: {
|
||||
slug: "order-success-layout",
|
||||
title: "Order Success Page Layout",
|
||||
isPublished: true,
|
||||
}
|
||||
});
|
||||
console.log(`✅ Created page: ${orderSuccessPage.title} (slug: ${orderSuccessPage.slug})`);
|
||||
await prisma.pageSection.deleteMany({ where: { pageId: orderSuccessPage.id } });
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: orderSuccessPage.id,
|
||||
type: "ANNOUNCEMENT_BAR",
|
||||
order: 1,
|
||||
isActive: true,
|
||||
payload: {
|
||||
slides: ["Thank you for shopping with HomAroma! Your package is preparing.", "Your order tracking status will be updated here."],
|
||||
backgroundColor: "#1F3D2D",
|
||||
textColor: "#FDFDF9",
|
||||
speed: 3000
|
||||
}
|
||||
}
|
||||
});
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: orderSuccessPage.id,
|
||||
type: "EDITORIAL_TILE",
|
||||
order: 2,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "Crafting Your [Sanctuary]",
|
||||
description: "Every order is carefully packaged by hand using sustainable, biodegradable materials. We ensure your botanicals arrive fresh and ready to enrich your space.",
|
||||
imageUrl: "/images/oil_ozone.png",
|
||||
imagePosition: "right",
|
||||
buttonText: "Our Sustainability Commitments",
|
||||
buttonLink: "/about",
|
||||
backgroundColor: "#FDFDF9",
|
||||
textColor: "#1F3D2D"
|
||||
}
|
||||
}
|
||||
});
|
||||
// 13. Create Track Order Page Layout
|
||||
const trackOrderPage = await prisma.page.upsert({
|
||||
where: { slug: "track-order-layout" },
|
||||
update: {},
|
||||
create: {
|
||||
slug: "track-order-layout",
|
||||
title: "Track Order Page Layout",
|
||||
isPublished: true,
|
||||
}
|
||||
});
|
||||
console.log(`✅ Created page: ${trackOrderPage.title} (slug: ${trackOrderPage.slug})`);
|
||||
await prisma.pageSection.deleteMany({ where: { pageId: trackOrderPage.id } });
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: trackOrderPage.id,
|
||||
type: "ANNOUNCEMENT_BAR",
|
||||
order: 1,
|
||||
isActive: true,
|
||||
payload: {
|
||||
slides: ["Check shipping status live. For any delivery queries, reach LINE @homaroma"],
|
||||
backgroundColor: "#1F3D2D",
|
||||
textColor: "#FDFDF9",
|
||||
speed: 3000
|
||||
}
|
||||
}
|
||||
});
|
||||
await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId: trackOrderPage.id,
|
||||
type: "EDITORIAL_TILE",
|
||||
order: 2,
|
||||
isActive: true,
|
||||
payload: {
|
||||
title: "Scenting Your [Daily Rituals]",
|
||||
description: "While waiting for your order, explore how different botanical notes impact focus, sleep, and emotional balance in our Scent Guide.",
|
||||
imageUrl: "/images/hero.png",
|
||||
imagePosition: "left",
|
||||
buttonText: "Read Scent Guide",
|
||||
buttonLink: "/scent-guide",
|
||||
backgroundColor: "#FDFDF9",
|
||||
textColor: "#1F3D2D"
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log("✅ Seeded default Category, Product Detail, FAQ, Wishlist, Search, Coupons, Contact, Order Success, and Track Order layouts successfully!");
|
||||
}
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error("❌ Error seeding page:", e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
await pool.end();
|
||||
});
|
||||
//# sourceMappingURL=seed-pages.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"seed-pages.js","sourceRoot":"","sources":["seed-pages.ts"],"names":[],"mappings":"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 truncated
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=migrate-i18n.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"migrate-i18n.d.ts","sourceRoot":"","sources":["migrate-i18n.ts"],"names":[],"mappings":""}
|
||||
@@ -0,0 +1,41 @@
|
||||
import prisma from '../src/lib/prisma';
|
||||
async function main() {
|
||||
console.log('Starting i18n migration...');
|
||||
const products = await prisma.product.findMany();
|
||||
console.log(`Found ${products.length} products to migrate.`);
|
||||
for (const product of products) {
|
||||
const existingTranslation = await prisma.productTranslation.findUnique({
|
||||
where: {
|
||||
productId_langCode: {
|
||||
productId: product.id,
|
||||
langCode: 'th',
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!existingTranslation) {
|
||||
await prisma.productTranslation.create({
|
||||
data: {
|
||||
productId: product.id,
|
||||
langCode: 'th',
|
||||
name: product.name,
|
||||
description: product.description,
|
||||
details: product.details,
|
||||
},
|
||||
});
|
||||
console.log(`Created Thai translation for product ID: ${product.id}`);
|
||||
}
|
||||
else {
|
||||
console.log(`Thai translation already exists for product ID: ${product.id}`);
|
||||
}
|
||||
}
|
||||
console.log('Migration completed successfully.');
|
||||
}
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
//# sourceMappingURL=migrate-i18n.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"migrate-i18n.js","sourceRoot":"","sources":["migrate-i18n.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,mBAAmB,CAAC;AAEvC,KAAK,UAAU,IAAI;IACjB,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAE1C,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,SAAS,QAAQ,CAAC,MAAM,uBAAuB,CAAC,CAAC;IAE7D,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,mBAAmB,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,UAAU,CAAC;YACrE,KAAK,EAAE;gBACL,kBAAkB,EAAE;oBAClB,SAAS,EAAE,OAAO,CAAC,EAAE;oBACrB,QAAQ,EAAE,IAAI;iBACf;aACF;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,mBAAmB,EAAE,CAAC;YACzB,MAAM,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC;gBACrC,IAAI,EAAE;oBACJ,SAAS,EAAE,OAAO,CAAC,EAAE;oBACrB,QAAQ,EAAE,IAAI;oBACd,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,WAAW,EAAE,OAAO,CAAC,WAAW;oBAChC,OAAO,EAAE,OAAO,CAAC,OAAO;iBACzB;aACF,CAAC,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,4CAA4C,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QACxE,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,mDAAmD,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;AACnD,CAAC;AAED,IAAI,EAAE;KACH,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IACX,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC;KACD,OAAO,CAAC,KAAK,IAAI,EAAE;IAClB,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;AAC7B,CAAC,CAAC,CAAC"}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":""}
|
||||
@@ -0,0 +1,98 @@
|
||||
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 { heroRoute } from "./routes/hero";
|
||||
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";
|
||||
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";
|
||||
const app = new Elysia()
|
||||
.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()),
|
||||
"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(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" });
|
||||
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`);
|
||||
//# sourceMappingURL=index.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
import * as line from '@line/bot-sdk';
|
||||
export declare const lineConfig: {
|
||||
channelAccessToken: string;
|
||||
channelSecret: string;
|
||||
};
|
||||
export declare const lineClient: line.messagingApi.MessagingApiClient;
|
||||
//# sourceMappingURL=lineClient.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"lineClient.d.ts","sourceRoot":"","sources":["lineClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAKtC,eAAO,MAAM,UAAU;;;CAGtB,CAAC;AAEF,eAAO,MAAM,UAAU,sCAErB,CAAC"}
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as line from '@line/bot-sdk';
|
||||
const channelAccessToken = process.env.LINE_CHANNEL_ACCESS_TOKEN || '';
|
||||
const channelSecret = process.env.LINE_CHANNEL_SECRET || '';
|
||||
export const lineConfig = {
|
||||
channelAccessToken,
|
||||
channelSecret,
|
||||
};
|
||||
export const lineClient = new line.messagingApi.MessagingApiClient({
|
||||
channelAccessToken,
|
||||
});
|
||||
//# sourceMappingURL=lineClient.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"lineClient.js","sourceRoot":"","sources":["lineClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAEtC,MAAM,kBAAkB,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,EAAE,CAAC;AACvE,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE,CAAC;AAE5D,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,kBAAkB;IAClB,aAAa;CACd,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC;IACjE,kBAAkB;CACnB,CAAC,CAAC"}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
export declare function sendOrderCreatedNotification(orderId: number): Promise<void>;
|
||||
export declare function sendPaymentConfirmedNotification(orderId: number): Promise<void>;
|
||||
export declare function sendOrderShippedNotification(orderId: number): Promise<void>;
|
||||
export declare function sendOrderCancelledNotification(orderId: number): Promise<void>;
|
||||
export declare function markOrderAsPaid(orderId: number): Promise<void>;
|
||||
//# sourceMappingURL=notifications.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["notifications.ts"],"names":[],"mappings":"AAGA,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,MAAM,iBAqNjE;AAED,wBAAsB,gCAAgC,CAAC,OAAO,EAAE,MAAM,iBA0HrE;AAED,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,MAAM,iBA2GjE;AAED,wBAAsB,8BAA8B,CAAC,OAAO,EAAE,MAAM,iBAkFnE;AAED,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,iBAoCpD"}
|
||||
@@ -0,0 +1,554 @@
|
||||
import prisma from "./prisma";
|
||||
import { lineClient } from "./lineClient";
|
||||
export async function sendOrderCreatedNotification(orderId) {
|
||||
try {
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id: orderId },
|
||||
include: { items: true, customer: true }
|
||||
});
|
||||
if (!order || !order.customer?.lineUserId) {
|
||||
console.log(`[Notification] Order #${orderId} has no associated lineUserId or does not exist.`);
|
||||
return;
|
||||
}
|
||||
const settings = await prisma.storeSettings.findFirst();
|
||||
const bankName = settings?.bankName || "KASIKORNBANK (KBANK)";
|
||||
const bankAccountNumber = settings?.bankAccountNumber || "012-3-45678-9";
|
||||
const bankAccountName = settings?.bankAccountName || "HomAroma Co., Ltd.";
|
||||
const itemContents = order.items.map(item => ({
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: `${item.productName}${item.variantSize ? ` (${item.variantSize})` : ""} x ${item.quantity}`,
|
||||
size: "sm",
|
||||
color: "#333333",
|
||||
flex: 3,
|
||||
wrap: true
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `฿${item.priceAtPurchase * item.quantity}`,
|
||||
size: "sm",
|
||||
color: "#1C3F3A",
|
||||
weight: "bold",
|
||||
align: "end",
|
||||
flex: 1
|
||||
}
|
||||
]
|
||||
}));
|
||||
const flexMessage = {
|
||||
type: "flex",
|
||||
altText: `HomAroma: คำสั่งซื้อ #${order.id} ได้รับแล้ว กรุณาชำระเงิน`,
|
||||
contents: {
|
||||
type: "bubble",
|
||||
header: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
backgroundColor: "#1C3F3A",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "HomAroma Official",
|
||||
color: "#D4B16A",
|
||||
weight: "bold",
|
||||
size: "xs"
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "ได้รับคำสั่งซื้อของคุณแล้ว",
|
||||
color: "#FFFFFF",
|
||||
weight: "bold",
|
||||
size: "md",
|
||||
margin: "sm"
|
||||
}
|
||||
]
|
||||
},
|
||||
body: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: `หมายเลขออเดอร์: #HM-${order.id}`,
|
||||
weight: "bold",
|
||||
size: "sm",
|
||||
color: "#333333"
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
margin: "md"
|
||||
},
|
||||
{
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
margin: "md",
|
||||
spacing: "sm",
|
||||
contents: [
|
||||
{
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "รายการสินค้า",
|
||||
size: "xs",
|
||||
color: "#888888",
|
||||
flex: 3
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "ยอดรวม",
|
||||
size: "xs",
|
||||
color: "#888888",
|
||||
align: "end",
|
||||
flex: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
...itemContents
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
margin: "md"
|
||||
},
|
||||
{
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
margin: "md",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "ยอดชำระสุทธิ (รวมค่าส่ง)",
|
||||
size: "sm",
|
||||
color: "#555555",
|
||||
weight: "bold",
|
||||
flex: 3
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `฿${order.totalAmount}`,
|
||||
size: "sm",
|
||||
color: "#1C3F3A",
|
||||
weight: "bold",
|
||||
align: "end",
|
||||
flex: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
margin: "md"
|
||||
},
|
||||
{
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
margin: "md",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "🏦 ช่องทางการชำระเงินโอนเงิน:",
|
||||
size: "xs",
|
||||
color: "#555555",
|
||||
weight: "bold"
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `${bankName} (${bankAccountNumber})`,
|
||||
size: "xs",
|
||||
color: "#1C3F3A",
|
||||
margin: "xs",
|
||||
weight: "bold"
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `ชื่อบัญชี: ${bankAccountName}`,
|
||||
size: "xs",
|
||||
color: "#555555"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
footer: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
spacing: "sm",
|
||||
contents: [
|
||||
{
|
||||
type: "button",
|
||||
style: "primary",
|
||||
color: "#1C3F3A",
|
||||
action: {
|
||||
"type": "uri",
|
||||
"label": "แนบสลิปชำระเงิน",
|
||||
"uri": `https://spa.homaroma.shop/order-success?id=${order.id}`
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "button",
|
||||
style: "link",
|
||||
height: "sm",
|
||||
action: {
|
||||
"type": "uri",
|
||||
"label": "ติดตามสถานะออเดอร์",
|
||||
"uri": "https://spa.homaroma.shop/track-order"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
await lineClient.pushMessage({
|
||||
to: order.customer.lineUserId,
|
||||
messages: [flexMessage]
|
||||
});
|
||||
console.log(`[Notification] OrderCreated notification sent for Order #${orderId}`);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`[Notification] Failed to send OrderCreated notification for Order #${orderId}:`, err);
|
||||
}
|
||||
}
|
||||
export async function sendPaymentConfirmedNotification(orderId) {
|
||||
try {
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id: orderId },
|
||||
include: { customer: true }
|
||||
});
|
||||
if (!order || !order.customer?.lineUserId) {
|
||||
console.log(`[Notification] Order #${orderId} has no associated lineUserId or does not exist.`);
|
||||
return;
|
||||
}
|
||||
const flexMessage = {
|
||||
type: "flex",
|
||||
altText: "HomAroma: ได้รับการชำระเงินเรียบร้อยแล้ว",
|
||||
contents: {
|
||||
type: "bubble",
|
||||
body: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: [
|
||||
{
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
alignItems: "center",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "✨ ชำระเงินสำเร็จ ✨",
|
||||
weight: "bold",
|
||||
size: "xl",
|
||||
color: "#1C3F3A"
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `ขอขอบคุณสำหรับคำสั่งซื้อออเดอร์ #HM-${order.id}`,
|
||||
size: "xs",
|
||||
color: "#888888",
|
||||
margin: "xs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
backgroundColor: "#F4F6F5",
|
||||
margin: "lg",
|
||||
paddingAll: "md",
|
||||
cornerRadius: "md",
|
||||
contents: [
|
||||
{
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "ได้รับแต้มครั้งนี้",
|
||||
size: "sm",
|
||||
color: "#555555"
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `+${order.pointsEarned} แต้ม`,
|
||||
weight: "bold",
|
||||
size: "sm",
|
||||
color: "#1C3F3A",
|
||||
align: "end"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
margin: "xs",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "แต้มสะสมรวมล่าสุด",
|
||||
size: "sm",
|
||||
color: "#555555"
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `${order.customer.points} แต้ม`,
|
||||
weight: "bold",
|
||||
size: "sm",
|
||||
color: "#D4B16A",
|
||||
align: "end"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
footer: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: [
|
||||
{
|
||||
type: "button",
|
||||
style: "primary",
|
||||
color: "#1C3F3A",
|
||||
action: {
|
||||
"type": "uri",
|
||||
"label": "แลกของรางวัล & คูปองสะสม",
|
||||
"uri": "https://spa.homaroma.shop/profile"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
await lineClient.pushMessage({
|
||||
to: order.customer.lineUserId,
|
||||
messages: [flexMessage]
|
||||
});
|
||||
console.log(`[Notification] PaymentConfirmed notification sent for Order #${orderId}`);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`[Notification] Failed to send PaymentConfirmed notification for Order #${orderId}:`, err);
|
||||
}
|
||||
}
|
||||
export async function sendOrderShippedNotification(orderId) {
|
||||
try {
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id: orderId },
|
||||
include: { customer: true }
|
||||
});
|
||||
if (!order || !order.customer?.lineUserId) {
|
||||
console.log(`[Notification] Order #${orderId} has no associated lineUserId or does not exist.`);
|
||||
return;
|
||||
}
|
||||
const flexMessage = {
|
||||
type: "flex",
|
||||
altText: "HomAroma: สินค้าของคุณอยู่ระหว่างจัดส่ง",
|
||||
contents: {
|
||||
type: "bubble",
|
||||
header: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
backgroundColor: "#1C3F3A",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "ออเดอร์ของคุณกำลังเดินทาง 🚚",
|
||||
color: "#FFFFFF",
|
||||
weight: "bold",
|
||||
size: "md"
|
||||
}
|
||||
]
|
||||
},
|
||||
body: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
spacing: "md",
|
||||
contents: [
|
||||
{
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "หมายเลขออเดอร์:",
|
||||
size: "sm",
|
||||
color: "#888888",
|
||||
flex: 2
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `#HM-${order.id}`,
|
||||
size: "sm",
|
||||
color: "#333333",
|
||||
weight: "bold",
|
||||
flex: 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "หมายเลขพัสดุ:",
|
||||
size: "sm",
|
||||
color: "#888888",
|
||||
flex: 2
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: order.trackingNo || "อยู่ระหว่างเข้ารับพัสดุ",
|
||||
size: "sm",
|
||||
color: "#1C3F3A",
|
||||
weight: "bold",
|
||||
flex: 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
footer: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: [
|
||||
{
|
||||
type: "button",
|
||||
style: "primary",
|
||||
color: "#1C3F3A",
|
||||
action: {
|
||||
"type": "uri",
|
||||
"label": "เช็คสถานะการจัดส่ง",
|
||||
"uri": "https://spa.homaroma.shop/track-order"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
await lineClient.pushMessage({
|
||||
to: order.customer.lineUserId,
|
||||
messages: [flexMessage]
|
||||
});
|
||||
console.log(`[Notification] OrderShipped notification sent for Order #${orderId}`);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`[Notification] Failed to send OrderShipped notification for Order #${orderId}:`, err);
|
||||
}
|
||||
}
|
||||
export async function sendOrderCancelledNotification(orderId) {
|
||||
try {
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id: orderId },
|
||||
include: { customer: true }
|
||||
});
|
||||
if (!order || !order.customer?.lineUserId) {
|
||||
console.log(`[Notification] Order #${orderId} has no associated lineUserId or does not exist.`);
|
||||
return;
|
||||
}
|
||||
const flexMessage = {
|
||||
type: "flex",
|
||||
altText: "HomAroma: คำสั่งซื้อของคุณถูกยกเลิกแล้ว",
|
||||
contents: {
|
||||
type: "bubble",
|
||||
header: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
backgroundColor: "#8C2A2A",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "คำสั่งซื้อถูกยกเลิก 🚫",
|
||||
color: "#FFFFFF",
|
||||
weight: "bold",
|
||||
size: "md"
|
||||
}
|
||||
]
|
||||
},
|
||||
body: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
spacing: "md",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: `คำสั่งซื้อหมายเลข #HM-${order.id} ได้ถูกยกเลิกแล้ว เนื่องจากเกินกำหนดเวลาชำระเงิน หรือสิทธิประโยชน์บางประการ หรือตามความประสงค์ของคุณ`,
|
||||
size: "sm",
|
||||
color: "#333333",
|
||||
wrap: true
|
||||
},
|
||||
...(order.pointsUsed > 0 ? [
|
||||
{
|
||||
type: "text",
|
||||
text: `* ระบบได้ทำการคืนคะแนนสะสมจำนวน ${order.pointsUsed} แต้มเข้าบัญชีของคุณเรียบร้อยแล้ว`,
|
||||
size: "xs",
|
||||
color: "#8C2A2A",
|
||||
weight: "bold",
|
||||
margin: "md"
|
||||
}
|
||||
] : [])
|
||||
]
|
||||
},
|
||||
footer: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: [
|
||||
{
|
||||
type: "button",
|
||||
style: "primary",
|
||||
color: "#1C3F3A",
|
||||
action: {
|
||||
"type": "uri",
|
||||
"label": "ช้อปต่ออีกครั้ง",
|
||||
"uri": "https://spa.homaroma.shop/"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
await lineClient.pushMessage({
|
||||
to: order.customer.lineUserId,
|
||||
messages: [flexMessage]
|
||||
});
|
||||
console.log(`[Notification] OrderCancelled notification sent for Order #${orderId}`);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`[Notification] Failed to send OrderCancelled notification for Order #${orderId}:`, err);
|
||||
}
|
||||
}
|
||||
export async function markOrderAsPaid(orderId) {
|
||||
try {
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id: orderId }
|
||||
});
|
||||
if (!order) {
|
||||
console.log(`[Payment] Order #${orderId} not found in markOrderAsPaid.`);
|
||||
return;
|
||||
}
|
||||
if (order.status === "PAID") {
|
||||
console.log(`[Payment] Order #${orderId} is already PAID.`);
|
||||
return;
|
||||
}
|
||||
// 1. Update status to PAID
|
||||
await prisma.order.update({
|
||||
where: { id: orderId },
|
||||
data: { status: "PAID" }
|
||||
});
|
||||
// 2. Grant points
|
||||
if (order.customerId && order.pointsEarned > 0) {
|
||||
await prisma.customer.update({
|
||||
where: { id: order.customerId },
|
||||
data: { points: { increment: order.pointsEarned } }
|
||||
});
|
||||
console.log(`[Payment] Granted ${order.pointsEarned} points to Customer #${order.customerId} for Order #${orderId}`);
|
||||
}
|
||||
// 3. Send payment notification
|
||||
await sendPaymentConfirmedNotification(orderId);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`[Payment] Error in markOrderAsPaid for Order #${orderId}:`, err);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=notifications.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["notifications.ts"],"names":[],"mappings":"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 truncated
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { PrismaPg } from "@prisma/adapter-pg";
|
||||
declare const prisma: PrismaClient<{
|
||||
adapter: PrismaPg;
|
||||
}, never, import("@prisma/client/runtime/client").DefaultArgs>;
|
||||
export default prisma;
|
||||
//# sourceMappingURL=prisma.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAS9C,QAAA,MAAM,MAAM;;8DAAgC,CAAC;AAE7C,eAAe,MAAM,CAAC"}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { PrismaPg } from "@prisma/adapter-pg";
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
const connectionString = process.env.DATABASE_URL;
|
||||
const adapter = new PrismaPg({ connectionString });
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
export default prisma;
|
||||
//# sourceMappingURL=prisma.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"prisma.js","sourceRoot":"","sources":["prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,MAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,YAAa,CAAC;AAEnD,MAAM,OAAO,GAAG,IAAI,QAAQ,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC;AAEnD,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;AAE7C,eAAe,MAAM,CAAC"}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Elysia } from "elysia";
|
||||
import { jwt } from "@elysiajs/jwt";
|
||||
export const adminOnly = new Elysia({ name: "adminOnly" })
|
||||
.use(jwt({
|
||||
name: "jwt",
|
||||
secret: process.env.JWT_SECRET || "fallback-secret"
|
||||
}))
|
||||
.onBeforeHandle(async ({ jwt, headers, set }) => {
|
||||
const auth = headers.authorization;
|
||||
if (!auth?.startsWith("Bearer ")) {
|
||||
set.status = 401;
|
||||
return { error: "Unauthorized: Missing or invalid token format" };
|
||||
}
|
||||
const token = auth.substring(7);
|
||||
const payload = await jwt.verify(token);
|
||||
if (!payload) {
|
||||
set.status = 401;
|
||||
return { error: "Unauthorized: Invalid token" };
|
||||
}
|
||||
if (payload.role !== "ADMIN") {
|
||||
set.status = 401;
|
||||
return { error: "Unauthorized: Admin access required" };
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=auth.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.js","sourceRoot":"","sources":["auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAEpC,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;KACvD,GAAG,CACF,GAAG,CAAC;IACF,IAAI,EAAE,KAAK;IACX,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,iBAAiB;CACpD,CAAC,CACH;KACA,cAAc,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE;IAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC;IACnC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACjC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,+CAA+C,EAAE,CAAC;IACpE,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAExC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,6BAA6B,EAAE,CAAC;IAClD,CAAC;IAED,IAAK,OAAe,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACtC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,qCAAqC,EAAE,CAAC;IAC1D,CAAC;AACH,CAAC,CAAC,CAAC"}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const articlesRoute: Elysia<"/articles", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
articles: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
articles: {
|
||||
":slug": {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
slug: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
articles: {
|
||||
id: {
|
||||
":id": {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
articles: {
|
||||
post: {
|
||||
body: {
|
||||
coverImage?: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
isPublished: boolean;
|
||||
content: string;
|
||||
};
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
articles: {
|
||||
":id": {
|
||||
put: {
|
||||
body: {
|
||||
coverImage?: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
isPublished: boolean;
|
||||
content: string;
|
||||
};
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
articles: {
|
||||
":id": {
|
||||
delete: {
|
||||
body: unknown;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
success: boolean;
|
||||
error?: never;
|
||||
} | {
|
||||
error: string;
|
||||
success?: never;
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=articles.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"articles.d.ts","sourceRoot":"","sources":["articles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAGnC,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2GtB,CAAC"}
|
||||
@@ -0,0 +1,116 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
export const articlesRoute = new Elysia({ prefix: "/articles" })
|
||||
.get("/", async ({ query, set }) => {
|
||||
try {
|
||||
const { publishedOnly } = query;
|
||||
const where = publishedOnly === 'true' ? { isPublished: true } : {};
|
||||
return await prisma.article.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to load articles" };
|
||||
}
|
||||
})
|
||||
.get("/:slug", async ({ params: { slug }, set }) => {
|
||||
try {
|
||||
const article = await prisma.article.findUnique({
|
||||
where: { slug }
|
||||
});
|
||||
if (!article) {
|
||||
set.status = 404;
|
||||
return { error: "Article not found" };
|
||||
}
|
||||
return article;
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to fetch article" };
|
||||
}
|
||||
})
|
||||
.get("/id/:id", async ({ params: { id }, set }) => {
|
||||
try {
|
||||
const article = await prisma.article.findUnique({
|
||||
where: { id: parseInt(id, 10) }
|
||||
});
|
||||
if (!article) {
|
||||
set.status = 404;
|
||||
return { error: "Article not found" };
|
||||
}
|
||||
return article;
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to fetch article" };
|
||||
}
|
||||
})
|
||||
.post("/", async ({ body, set }) => {
|
||||
try {
|
||||
const data = body;
|
||||
return await prisma.article.create({
|
||||
data: {
|
||||
title: data.title,
|
||||
slug: data.slug,
|
||||
coverImage: data.coverImage,
|
||||
content: data.content,
|
||||
isPublished: data.isPublished
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to create article. Ensure slug is unique." };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
title: t.String(),
|
||||
slug: t.String(),
|
||||
coverImage: t.Optional(t.String()),
|
||||
content: t.String(),
|
||||
isPublished: t.Boolean()
|
||||
})
|
||||
})
|
||||
.put("/:id", async ({ params: { id }, body, set }) => {
|
||||
try {
|
||||
const data = body;
|
||||
return await prisma.article.update({
|
||||
where: { id: parseInt(id, 10) },
|
||||
data: {
|
||||
title: data.title,
|
||||
slug: data.slug,
|
||||
coverImage: data.coverImage,
|
||||
content: data.content,
|
||||
isPublished: data.isPublished
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to update article" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
title: t.String(),
|
||||
slug: t.String(),
|
||||
coverImage: t.Optional(t.String()),
|
||||
content: t.String(),
|
||||
isPublished: t.Boolean()
|
||||
})
|
||||
})
|
||||
.delete("/:id", async ({ params: { id }, set }) => {
|
||||
try {
|
||||
await prisma.article.delete({
|
||||
where: { id: parseInt(id, 10) }
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to delete article" };
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=articles.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"articles.js","sourceRoot":"","sources":["articles.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import { jwt } from "@elysiajs/jwt";
|
||||
import bcrypt from "bcryptjs";
|
||||
import prisma from "../lib/prisma";
|
||||
export const authRoute = new Elysia({ prefix: "/auth" })
|
||||
.use(jwt({
|
||||
name: "jwt",
|
||||
secret: process.env.JWT_SECRET || "homaroma_secret_2026_scent",
|
||||
exp: "24h",
|
||||
}))
|
||||
.post("/login", async ({ body, jwt, set }) => {
|
||||
const { username, password } = body;
|
||||
// 1. Find user
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { username },
|
||||
});
|
||||
if (!user) {
|
||||
set.status = 401;
|
||||
return { success: false, message: "Invalid credentials" };
|
||||
}
|
||||
// 2. Check password
|
||||
const isMatch = await bcrypt.compare(password, user.password);
|
||||
if (!isMatch) {
|
||||
set.status = 401;
|
||||
return { success: false, message: "Invalid credentials" };
|
||||
}
|
||||
// 3. Generate Token
|
||||
const token = await jwt.sign({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role,
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role,
|
||||
},
|
||||
};
|
||||
}, {
|
||||
body: t.Object({
|
||||
username: t.String(),
|
||||
password: t.String(),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Admin Login",
|
||||
tags: ["auth"],
|
||||
},
|
||||
})
|
||||
.post("/line", async ({ body, jwt, set }) => {
|
||||
const { lineUserId, displayName, pictureUrl, email, phone } = body;
|
||||
// Upsert Customer
|
||||
const customer = await prisma.customer.upsert({
|
||||
where: { lineUserId },
|
||||
update: {
|
||||
displayName,
|
||||
pictureUrl,
|
||||
...(email && { email }),
|
||||
...(phone && { phone }),
|
||||
},
|
||||
create: {
|
||||
lineUserId,
|
||||
displayName,
|
||||
pictureUrl,
|
||||
email,
|
||||
phone,
|
||||
},
|
||||
});
|
||||
// Generate Token
|
||||
const token = await jwt.sign({
|
||||
id: customer.id,
|
||||
lineUserId: customer.lineUserId,
|
||||
role: "CUSTOMER",
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
token,
|
||||
customer,
|
||||
};
|
||||
}, {
|
||||
body: t.Object({
|
||||
lineUserId: t.String(),
|
||||
displayName: t.Optional(t.String()),
|
||||
pictureUrl: t.Optional(t.String()),
|
||||
email: t.Optional(t.String()),
|
||||
phone: t.Optional(t.String()),
|
||||
}),
|
||||
detail: {
|
||||
summary: "LINE Login for Customers",
|
||||
tags: ["auth"],
|
||||
},
|
||||
});
|
||||
//# sourceMappingURL=auth.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.js","sourceRoot":"","sources":["auth.ts"],"names":[],"mappings":"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"}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const categoriesRoute: Elysia<"/categories", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
categories: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
categories: {
|
||||
":id": {
|
||||
products: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
category: {
|
||||
id: any;
|
||||
name: any;
|
||||
imageUrl: any;
|
||||
_count: any;
|
||||
};
|
||||
data: any;
|
||||
meta: {
|
||||
total: any;
|
||||
page: number;
|
||||
limit: number;
|
||||
lastPage: number;
|
||||
};
|
||||
};
|
||||
400: {
|
||||
readonly message: "Invalid category ID";
|
||||
};
|
||||
404: {
|
||||
readonly message: "Category not found";
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
categories: {
|
||||
":id": {
|
||||
translations: {
|
||||
patch: {
|
||||
body: unknown;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
success: boolean;
|
||||
translations: any[];
|
||||
category: any;
|
||||
};
|
||||
400: {
|
||||
readonly message: "Invalid category ID";
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
categories: {
|
||||
post: {
|
||||
body: {
|
||||
imageUrl?: string | null;
|
||||
name: string;
|
||||
};
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
categories: {
|
||||
":id": {
|
||||
put: {
|
||||
body: {
|
||||
name?: string;
|
||||
imageUrl?: string | null;
|
||||
};
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
categories: {
|
||||
":id": {
|
||||
delete: {
|
||||
body: unknown;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
success: boolean;
|
||||
};
|
||||
400: {
|
||||
readonly message: "Invalid category ID";
|
||||
} | {
|
||||
readonly message: "Cannot delete category with existing products";
|
||||
};
|
||||
404: {
|
||||
readonly message: "Category not found";
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
500: {
|
||||
readonly message: "Failed to delete category";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=categories.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"categories.d.ts","sourceRoot":"","sources":["categories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAkBnC,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgNxB,CAAC"}
|
||||
@@ -0,0 +1,222 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko'];
|
||||
/** Resolve category name based on lang */
|
||||
function resolveCatName(cat, lang) {
|
||||
if (lang === 'all') {
|
||||
const nameObj = {};
|
||||
cat.translations?.forEach((tr) => { nameObj[tr.langCode] = tr.name; });
|
||||
return nameObj;
|
||||
}
|
||||
const trans = cat.translations?.find((t) => t.langCode === lang)
|
||||
|| cat.translations?.find((t) => t.langCode === 'th')
|
||||
|| cat.translations?.[0];
|
||||
return trans?.name ?? cat.name;
|
||||
}
|
||||
export const categoriesRoute = new Elysia({ prefix: "/categories" })
|
||||
.get("/", async ({ query }) => {
|
||||
const lang = query.lang || 'th';
|
||||
const categories = await prisma.category.findMany({
|
||||
orderBy: { name: "asc" },
|
||||
include: {
|
||||
translations: true,
|
||||
_count: {
|
||||
select: { products: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
return categories.map((cat) => ({
|
||||
...cat,
|
||||
name: resolveCatName(cat, lang),
|
||||
}));
|
||||
})
|
||||
.get("/:id/products", async ({ params, query, status }) => {
|
||||
const id = parseInt(params.id);
|
||||
const lang = query.lang || 'th';
|
||||
if (isNaN(id))
|
||||
return status(400, { message: "Invalid category ID" });
|
||||
// Pagination params
|
||||
const page = query.page ? parseInt(query.page) : 1;
|
||||
const limit = query.limit ? parseInt(query.limit) : 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const [category, total] = await Promise.all([
|
||||
prisma.category.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
translations: true,
|
||||
products: {
|
||||
include: {
|
||||
scent: true,
|
||||
category: { include: { translations: true } },
|
||||
translations: true,
|
||||
variants: { orderBy: { price: "asc" } },
|
||||
images: { orderBy: { order: "asc" } },
|
||||
},
|
||||
orderBy: { id: "asc" },
|
||||
skip,
|
||||
take: limit,
|
||||
},
|
||||
_count: {
|
||||
select: { products: true }
|
||||
}
|
||||
},
|
||||
}),
|
||||
prisma.product.count({ where: { categoryId: id } }),
|
||||
]);
|
||||
if (!category)
|
||||
return status(404, { message: "Category not found" });
|
||||
return {
|
||||
category: {
|
||||
id: category.id,
|
||||
name: resolveCatName(category, lang),
|
||||
imageUrl: category.imageUrl,
|
||||
_count: category._count,
|
||||
},
|
||||
data: category.products.map((p) => {
|
||||
if (lang === 'all') {
|
||||
const nameObj = {};
|
||||
const descObj = {};
|
||||
const detailsObj = {};
|
||||
p.translations?.forEach((t) => {
|
||||
nameObj[t.langCode] = t.name;
|
||||
descObj[t.langCode] = t.description;
|
||||
detailsObj[t.langCode] = t.details;
|
||||
});
|
||||
return {
|
||||
...p,
|
||||
name: nameObj,
|
||||
description: descObj,
|
||||
details: detailsObj,
|
||||
category: { ...p.category, name: resolveCatName(p.category, lang) },
|
||||
};
|
||||
}
|
||||
else {
|
||||
const trans = p.translations?.find((t) => t.langCode === lang) || p.translations?.find((t) => t.langCode === 'th') || p.translations?.[0];
|
||||
return {
|
||||
...p,
|
||||
name: trans?.name || p.scent?.name || '',
|
||||
description: trans?.description,
|
||||
details: trans?.details,
|
||||
category: { ...p.category, name: resolveCatName(p.category, lang) },
|
||||
};
|
||||
}
|
||||
}),
|
||||
meta: {
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
lastPage: Math.ceil(total / limit),
|
||||
},
|
||||
};
|
||||
})
|
||||
.patch("/:id/translations", async ({ params, body, status }) => {
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id))
|
||||
return status(400, { message: "Invalid category ID" });
|
||||
const payload = body;
|
||||
const results = [];
|
||||
for (const lang of LANGS) {
|
||||
const name = typeof payload.name === 'string'
|
||||
? (lang === 'th' ? payload.name : null)
|
||||
: payload.name?.[lang];
|
||||
if (!name)
|
||||
continue;
|
||||
const existing = await prisma.categoryTranslation.findUnique({
|
||||
where: { categoryId_langCode: { categoryId: id, langCode: lang } }
|
||||
});
|
||||
if (existing) {
|
||||
const updated = await prisma.categoryTranslation.update({
|
||||
where: { id: existing.id },
|
||||
data: { name },
|
||||
});
|
||||
results.push(updated);
|
||||
}
|
||||
else {
|
||||
const created = await prisma.categoryTranslation.create({
|
||||
data: { categoryId: id, langCode: lang, name },
|
||||
});
|
||||
results.push(created);
|
||||
}
|
||||
}
|
||||
const updated = await prisma.category.findUnique({
|
||||
where: { id },
|
||||
include: { translations: true, _count: { select: { products: true } } },
|
||||
});
|
||||
return { success: true, translations: results, category: updated };
|
||||
})
|
||||
.post("/", async ({ body, status }) => {
|
||||
try {
|
||||
const category = await prisma.category.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
imageUrl: body.imageUrl,
|
||||
// Auto-seed Thai translation
|
||||
translations: { create: { langCode: 'th', name: body.name } },
|
||||
},
|
||||
});
|
||||
return category;
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code === "P2002") {
|
||||
return status(400, { message: "Category name already exists" });
|
||||
}
|
||||
return status(500, { message: "Failed to create category" });
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
imageUrl: t.Optional(t.Union([t.String(), t.Null()])),
|
||||
})
|
||||
})
|
||||
.put("/:id", async ({ params, body, status }) => {
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id))
|
||||
return status(400, { message: "Invalid category ID" });
|
||||
try {
|
||||
const category = await prisma.category.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: body.name,
|
||||
imageUrl: body.imageUrl,
|
||||
},
|
||||
});
|
||||
return category;
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code === "P2025") {
|
||||
return status(404, { message: "Category not found" });
|
||||
}
|
||||
if (err.code === "P2002") {
|
||||
return status(400, { message: "Category name already exists" });
|
||||
}
|
||||
console.error("Update category error:", err);
|
||||
return status(500, { message: "Failed to update category" });
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
name: t.Optional(t.String()),
|
||||
imageUrl: t.Optional(t.Union([t.String(), t.Null()])),
|
||||
})
|
||||
})
|
||||
.delete("/:id", async ({ params, status }) => {
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id))
|
||||
return status(400, { message: "Invalid category ID" });
|
||||
try {
|
||||
const category = await prisma.category.findUnique({
|
||||
where: { id },
|
||||
include: { _count: { select: { products: true } } }
|
||||
});
|
||||
if (!category)
|
||||
return status(404, { message: "Category not found" });
|
||||
if (category._count.products > 0) {
|
||||
return status(400, { message: "Cannot delete category with existing products" });
|
||||
}
|
||||
await prisma.category.delete({ where: { id } });
|
||||
return { success: true };
|
||||
}
|
||||
catch (err) {
|
||||
return status(500, { message: "Failed to delete category" });
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=categories.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"categories.js","sourceRoot":"","sources":["categories.ts"],"names":[],"mappings":"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 truncated
|
||||
Vendored
+185
@@ -0,0 +1,185 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const couponsRoute: Elysia<"/coupons", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
coupons: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
coupons: {
|
||||
active: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
coupons: {
|
||||
post: {
|
||||
body: {
|
||||
isActive?: boolean;
|
||||
minPurchase?: number;
|
||||
maxDiscount?: number | null;
|
||||
usageLimit?: number | null;
|
||||
expiresAt?: string | null;
|
||||
type: string;
|
||||
value: number;
|
||||
code: string;
|
||||
};
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
coupons: {
|
||||
":id": {
|
||||
patch: {
|
||||
body: {
|
||||
type?: string;
|
||||
isActive?: boolean;
|
||||
value?: number;
|
||||
code?: string;
|
||||
minPurchase?: number;
|
||||
maxDiscount?: number | null;
|
||||
usageLimit?: number | null;
|
||||
expiresAt?: string | null;
|
||||
};
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
coupons: {
|
||||
":id": {
|
||||
delete: {
|
||||
body: unknown;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
success: boolean;
|
||||
error?: never;
|
||||
} | {
|
||||
error: string;
|
||||
success?: never;
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
coupons: {
|
||||
validate: {
|
||||
post: {
|
||||
body: {
|
||||
code: string;
|
||||
cartTotal: number;
|
||||
};
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
valid: boolean;
|
||||
error: string;
|
||||
coupon?: never;
|
||||
discountAmount?: never;
|
||||
isFreeShipping?: never;
|
||||
} | {
|
||||
valid: boolean;
|
||||
coupon: any;
|
||||
discountAmount: number;
|
||||
isFreeShipping: boolean;
|
||||
error?: never;
|
||||
} | {
|
||||
error: string;
|
||||
valid?: never;
|
||||
coupon?: never;
|
||||
discountAmount?: never;
|
||||
isFreeShipping?: never;
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=coupons.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"coupons.d.ts","sourceRoot":"","sources":["coupons.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAGnC,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6LrB,CAAC"}
|
||||
@@ -0,0 +1,186 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
export const couponsRoute = new Elysia({ prefix: "/coupons" })
|
||||
// Get all coupons (Admin)
|
||||
.get("/", async () => {
|
||||
return await prisma.coupon.findMany({
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
})
|
||||
// Get active coupons (Storefront)
|
||||
.get("/active", async () => {
|
||||
const now = new Date();
|
||||
return await prisma.coupon.findMany({
|
||||
where: {
|
||||
isActive: true,
|
||||
OR: [
|
||||
{ expiresAt: null },
|
||||
{ expiresAt: { gt: now } }
|
||||
]
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
})
|
||||
// Create new coupon (Admin)
|
||||
.post("/", async ({ body, set }) => {
|
||||
try {
|
||||
const data = body;
|
||||
// Check if code already exists
|
||||
const existing = await prisma.coupon.findUnique({
|
||||
where: { code: data.code.toUpperCase() }
|
||||
});
|
||||
if (existing) {
|
||||
set.status = 400;
|
||||
return { error: "Coupon code already exists" };
|
||||
}
|
||||
const coupon = await prisma.coupon.create({
|
||||
data: {
|
||||
code: data.code.toUpperCase(),
|
||||
type: data.type,
|
||||
value: data.value,
|
||||
minPurchase: data.minPurchase || 0,
|
||||
maxDiscount: data.maxDiscount || null,
|
||||
usageLimit: data.usageLimit || null,
|
||||
expiresAt: data.expiresAt ? new Date(data.expiresAt) : null,
|
||||
isActive: data.isActive !== undefined ? data.isActive : true
|
||||
}
|
||||
});
|
||||
return coupon;
|
||||
}
|
||||
catch (err) {
|
||||
console.error("[Coupons] Create Error:", err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to create coupon" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
code: t.String(),
|
||||
type: t.String(),
|
||||
value: t.Number(),
|
||||
minPurchase: t.Optional(t.Number()),
|
||||
maxDiscount: t.Optional(t.Nullable(t.Number())),
|
||||
usageLimit: t.Optional(t.Nullable(t.Number())),
|
||||
expiresAt: t.Optional(t.Nullable(t.String())),
|
||||
isActive: t.Optional(t.Boolean())
|
||||
})
|
||||
})
|
||||
// Update coupon (Admin)
|
||||
.patch("/:id", async ({ params: { id }, body, set }) => {
|
||||
try {
|
||||
const data = body;
|
||||
const coupon = await prisma.coupon.update({
|
||||
where: { id: Number(id) },
|
||||
data: {
|
||||
...(data.code && { code: data.code.toUpperCase() }),
|
||||
...(data.type && { type: data.type }),
|
||||
...(data.value !== undefined && { value: data.value }),
|
||||
...(data.minPurchase !== undefined && { minPurchase: data.minPurchase }),
|
||||
...(data.maxDiscount !== undefined && { maxDiscount: data.maxDiscount }),
|
||||
...(data.usageLimit !== undefined && { usageLimit: data.usageLimit }),
|
||||
...(data.expiresAt !== undefined && { expiresAt: data.expiresAt ? new Date(data.expiresAt) : null }),
|
||||
...(data.isActive !== undefined && { isActive: data.isActive })
|
||||
}
|
||||
});
|
||||
return coupon;
|
||||
}
|
||||
catch (err) {
|
||||
console.error("[Coupons] Update Error:", err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to update coupon" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
code: t.Optional(t.String()),
|
||||
type: t.Optional(t.String()),
|
||||
value: t.Optional(t.Number()),
|
||||
minPurchase: t.Optional(t.Number()),
|
||||
maxDiscount: t.Optional(t.Nullable(t.Number())),
|
||||
usageLimit: t.Optional(t.Nullable(t.Number())),
|
||||
expiresAt: t.Optional(t.Nullable(t.String())),
|
||||
isActive: t.Optional(t.Boolean())
|
||||
})
|
||||
})
|
||||
// Delete coupon (Admin)
|
||||
.delete("/:id", async ({ params: { id }, set }) => {
|
||||
try {
|
||||
await prisma.coupon.delete({
|
||||
where: { id: Number(id) }
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to delete coupon" };
|
||||
}
|
||||
})
|
||||
// Validate coupon (Storefront)
|
||||
.post("/validate", async ({ body, set }) => {
|
||||
try {
|
||||
const { code, cartTotal } = body;
|
||||
const upperCode = code.toUpperCase();
|
||||
const coupon = await prisma.coupon.findUnique({
|
||||
where: { code: upperCode }
|
||||
});
|
||||
if (!coupon) {
|
||||
set.status = 404;
|
||||
return { valid: false, error: "Coupon not found" };
|
||||
}
|
||||
if (!coupon.isActive) {
|
||||
set.status = 400;
|
||||
return { valid: false, error: "Coupon is no longer active" };
|
||||
}
|
||||
if (coupon.expiresAt && new Date() > coupon.expiresAt) {
|
||||
set.status = 400;
|
||||
return { valid: false, error: "Coupon has expired" };
|
||||
}
|
||||
if (coupon.usageLimit && coupon.usedCount >= coupon.usageLimit) {
|
||||
set.status = 400;
|
||||
return { valid: false, error: "Coupon usage limit reached" };
|
||||
}
|
||||
if (cartTotal < coupon.minPurchase) {
|
||||
set.status = 400;
|
||||
return { valid: false, error: `Minimum purchase of ฿${coupon.minPurchase} required` };
|
||||
}
|
||||
// Calculate discount amount
|
||||
let discountAmount = 0;
|
||||
if (coupon.type === "PERCENTAGE") {
|
||||
discountAmount = cartTotal * (coupon.value / 100);
|
||||
if (coupon.maxDiscount && discountAmount > coupon.maxDiscount) {
|
||||
discountAmount = coupon.maxDiscount;
|
||||
}
|
||||
}
|
||||
else if (coupon.type === "FIXED_AMOUNT") {
|
||||
discountAmount = coupon.value;
|
||||
}
|
||||
else if (coupon.type === "FREE_SHIPPING") {
|
||||
// We handle free shipping separately, returning discountAmount = 0 but a flag
|
||||
return {
|
||||
valid: true,
|
||||
coupon,
|
||||
discountAmount: 0,
|
||||
isFreeShipping: true
|
||||
};
|
||||
}
|
||||
// Ensure discount doesn't exceed cart total
|
||||
if (discountAmount > cartTotal) {
|
||||
discountAmount = cartTotal;
|
||||
}
|
||||
return {
|
||||
valid: true,
|
||||
coupon,
|
||||
discountAmount,
|
||||
isFreeShipping: false
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
console.error("[Coupons] Validate Error:", err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to validate coupon" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
code: t.String(),
|
||||
cartTotal: t.Number()
|
||||
})
|
||||
});
|
||||
//# sourceMappingURL=coupons.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"coupons.js","sourceRoot":"","sources":["coupons.ts"],"names":[],"mappings":"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 truncated
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const customersRoute: Elysia<"/customers", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
customers: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
customers: {
|
||||
":id": {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
customers: {
|
||||
":id": {
|
||||
points: {
|
||||
patch: {
|
||||
body: {
|
||||
reason?: string;
|
||||
pointsDelta: number;
|
||||
};
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=customers.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"customers.d.ts","sourceRoot":"","sources":["customers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAGnC,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiEvB,CAAC"}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
export const customersRoute = new Elysia({ prefix: "/customers" })
|
||||
.get("/", async ({ query }) => {
|
||||
const search = query.search;
|
||||
const whereClause = search ? {
|
||||
OR: [
|
||||
{ displayName: { contains: search, mode: "insensitive" } },
|
||||
{ email: { contains: search, mode: "insensitive" } },
|
||||
{ phone: { contains: search, mode: "insensitive" } },
|
||||
{ lineUserId: { contains: search, mode: "insensitive" } }
|
||||
]
|
||||
} : {};
|
||||
return await prisma.customer.findMany({
|
||||
where: whereClause,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: {
|
||||
_count: {
|
||||
select: { orders: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.get("/:id", async ({ params: { id }, set }) => {
|
||||
const customer = await prisma.customer.findUnique({
|
||||
where: { id: Number(id) },
|
||||
include: { orders: { orderBy: { createdAt: 'desc' } } }
|
||||
});
|
||||
if (!customer) {
|
||||
set.status = 404;
|
||||
return { error: "Customer not found" };
|
||||
}
|
||||
return customer;
|
||||
})
|
||||
.patch("/:id/points", async ({ params: { id }, body, set }) => {
|
||||
try {
|
||||
const { pointsDelta, reason } = body;
|
||||
const customer = await prisma.customer.findUnique({
|
||||
where: { id: Number(id) }
|
||||
});
|
||||
if (!customer) {
|
||||
set.status = 404;
|
||||
return { error: "Customer not found" };
|
||||
}
|
||||
const newPoints = Math.max(0, customer.points + pointsDelta);
|
||||
const updatedCustomer = await prisma.customer.update({
|
||||
where: { id: Number(id) },
|
||||
data: { points: newPoints }
|
||||
});
|
||||
return updatedCustomer;
|
||||
}
|
||||
catch (err) {
|
||||
console.error("[Customers] Adjust Points Error:", err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to adjust points" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
pointsDelta: t.Number(),
|
||||
reason: t.Optional(t.String())
|
||||
})
|
||||
});
|
||||
//# sourceMappingURL=customers.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"customers.js","sourceRoot":"","sources":["customers.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+147
@@ -0,0 +1,147 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const heroRoute: Elysia<"/hero", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
hero: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
hero: {
|
||||
all: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
hero: {
|
||||
post: {
|
||||
body: {
|
||||
id?: number;
|
||||
title: string;
|
||||
isActive: boolean;
|
||||
tagline: string;
|
||||
description: string;
|
||||
imageUrl: string;
|
||||
buttonText: string;
|
||||
buttonLink: string;
|
||||
};
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
hero: {
|
||||
":id": {
|
||||
activate: {
|
||||
patch: {
|
||||
body: unknown;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
success: boolean;
|
||||
error?: never;
|
||||
} | {
|
||||
error: string;
|
||||
success?: never;
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
hero: {
|
||||
":id": {
|
||||
delete: {
|
||||
body: unknown;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
success: boolean;
|
||||
error?: never;
|
||||
} | {
|
||||
error: string;
|
||||
success?: never;
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=hero.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"hero.d.ts","sourceRoot":"","sources":["hero.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAGnC,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyElB,CAAC"}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
export const heroRoute = new Elysia({ prefix: "/hero" })
|
||||
.get("/", async () => {
|
||||
return await prisma.hero.findFirst({
|
||||
where: { isActive: true },
|
||||
orderBy: { updatedAt: 'desc' }
|
||||
}) || await prisma.hero.findFirst({
|
||||
orderBy: { updatedAt: 'desc' }
|
||||
});
|
||||
})
|
||||
.get("/all", async () => {
|
||||
return await prisma.hero.findMany({
|
||||
orderBy: { updatedAt: 'desc' }
|
||||
});
|
||||
})
|
||||
.post("/", async ({ body, set }) => {
|
||||
try {
|
||||
const data = body;
|
||||
const { id, ...heroData } = data;
|
||||
if (id) {
|
||||
return await prisma.hero.update({
|
||||
where: { id: Number(id) },
|
||||
data: heroData
|
||||
});
|
||||
}
|
||||
else {
|
||||
return await prisma.hero.create({
|
||||
data: heroData
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error("[Hero] Error:", err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to save hero data" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
id: t.Optional(t.Number()),
|
||||
tagline: t.String(),
|
||||
title: t.String(),
|
||||
description: t.String(),
|
||||
imageUrl: t.String(),
|
||||
buttonText: t.String(),
|
||||
buttonLink: t.String(),
|
||||
isActive: t.Boolean()
|
||||
})
|
||||
})
|
||||
.patch("/:id/activate", async ({ params: { id }, set }) => {
|
||||
try {
|
||||
await prisma.$transaction([
|
||||
prisma.hero.updateMany({
|
||||
data: { isActive: false }
|
||||
}),
|
||||
prisma.hero.update({
|
||||
where: { id: Number(id) },
|
||||
data: { isActive: true }
|
||||
})
|
||||
]);
|
||||
return { success: true };
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to activate hero" };
|
||||
}
|
||||
})
|
||||
.delete("/:id", async ({ params: { id }, set }) => {
|
||||
try {
|
||||
await prisma.hero.delete({
|
||||
where: { id: Number(id) }
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to delete hero" };
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=hero.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"hero.js","sourceRoot":"","sources":["hero.ts"],"names":[],"mappings":"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"}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const lazadaOrderRoute: Elysia<"/lazada-order", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
"lazada-order": {
|
||||
import: {
|
||||
post: {
|
||||
body: {
|
||||
shop?: string;
|
||||
rows: any[];
|
||||
};
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
success: boolean;
|
||||
message: string;
|
||||
ordersCount: number;
|
||||
rowsCount: number;
|
||||
} | {
|
||||
success: boolean;
|
||||
message: any;
|
||||
ordersCount?: never;
|
||||
rowsCount?: never;
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"lazada-order": {
|
||||
clear: {
|
||||
delete: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
success: boolean;
|
||||
message: string;
|
||||
count: any;
|
||||
} | {
|
||||
success: boolean;
|
||||
message: any;
|
||||
count?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"lazada-order": {
|
||||
orders: {
|
||||
":orderNumber": {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
orderNumber: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"lazada-order": {
|
||||
orders: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
items: any;
|
||||
total: any;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"lazada-order": {
|
||||
summary: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
orderCount: any;
|
||||
totalAmount: any;
|
||||
totalShipping: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"lazada-order": {
|
||||
analytics: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
monthly: {
|
||||
month: string;
|
||||
revenue: number;
|
||||
orders: number;
|
||||
cancelled: number;
|
||||
items: number;
|
||||
platformDiscount: number;
|
||||
sellerDiscount: number;
|
||||
}[];
|
||||
statusBreakdown: {
|
||||
status: string;
|
||||
count: number;
|
||||
}[];
|
||||
paymentBreakdown: {
|
||||
method: string;
|
||||
count: number;
|
||||
}[];
|
||||
topProducts: {
|
||||
name: string;
|
||||
variation: string;
|
||||
qty: number;
|
||||
revenue: number;
|
||||
}[];
|
||||
summary: {
|
||||
totalRevenue: any;
|
||||
totalShipping: any;
|
||||
totalPlatformDisc: any;
|
||||
totalSellerDisc: any;
|
||||
netRevenue: number;
|
||||
totalOrders: any;
|
||||
completedOrders: number;
|
||||
cancelledOrders: any;
|
||||
cancelRate: number;
|
||||
aov: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"lazada-order": {
|
||||
"analytics-overview": {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
shops: {
|
||||
shop: string;
|
||||
orders: number;
|
||||
completed: number;
|
||||
cancelled: number;
|
||||
revenue: number;
|
||||
shippingFee: number;
|
||||
platformDiscount: number;
|
||||
sellerDiscount: number;
|
||||
items: number;
|
||||
}[];
|
||||
monthly: any[];
|
||||
topProducts: {
|
||||
name: string;
|
||||
variation: string;
|
||||
qty: number;
|
||||
revenue: number;
|
||||
}[];
|
||||
summary: {
|
||||
totalRevenue: number;
|
||||
totalShipping: number;
|
||||
totalPlatformDisc: number;
|
||||
totalSellerDisc: number;
|
||||
netRevenue: number;
|
||||
totalOrders: number;
|
||||
completedOrders: number;
|
||||
cancelledOrders: number;
|
||||
cancelRate: number;
|
||||
aov: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=lazadaOrder.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"lazadaOrder.d.ts","sourceRoot":"","sources":["lazadaOrder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AA2BnC,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCA4KiB,MAAM;qCAAW,MAAM;oCAAU,MAAM;uCAAa,MAAM;mCAAS,MAAM;8CAAoB,MAAM;4CAAkB,MAAM;;;;;;;;;;;kCA+B1H,MAAM;uCAAa,MAAM;iCAAO,MAAM;qCAAW,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAwD1F,MAAM;oCAAU,MAAM;uCAAa,MAAM;uCAAa,MAAM;qCACzD,MAAM;yCAAe,MAAM;8CAAoB,MAAM;4CAAkB,MAAM;mCAAS,MAAM;;;;kCAiD5D,MAAM;uCAAa,MAAM;iCAAO,MAAM;qCAAW,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsClG,CAAC"}
|
||||
@@ -0,0 +1,348 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
const toNum = (val) => {
|
||||
if (val === null || val === undefined || val === "")
|
||||
return 0;
|
||||
const s = String(val).replace(/[^0-9.\-]/g, "");
|
||||
const n = parseFloat(s);
|
||||
return isNaN(n) ? 0 : n;
|
||||
};
|
||||
const str = (val) => String(val ?? "").trim();
|
||||
const MONTHS = { Jan: '01', Feb: '02', Mar: '03', Apr: '04', May: '05', Jun: '06', Jul: '07', Aug: '08', Sep: '09', Oct: '10', Nov: '11', Dec: '12' };
|
||||
const parseLazadaDate = (val) => {
|
||||
const d = str(val);
|
||||
if (!d)
|
||||
return "";
|
||||
// e.g. "25 Apr 2026 23:24"
|
||||
const parts = d.split(" ");
|
||||
if (parts.length >= 4) {
|
||||
const [day, monthStr, year, time] = parts;
|
||||
const month = MONTHS[monthStr] || "01";
|
||||
return `${year}-${month}-${day.padStart(2, "0")} ${time}:00`;
|
||||
}
|
||||
return d;
|
||||
};
|
||||
export const lazadaOrderRoute = new Elysia({ prefix: "/lazada-order" })
|
||||
// ── Import (Excel rows as JSON) ───────────────────────────────────────────
|
||||
.post("/import", async ({ body, set }) => {
|
||||
try {
|
||||
const { rows, shop } = body;
|
||||
const shopName = shop || "hom-aroma";
|
||||
console.log(`[LazadaOrder] Received ${rows.length} raw rows for shop: ${shopName}`);
|
||||
// Group rows by orderNumber
|
||||
const orderMap = new Map();
|
||||
for (const row of rows) {
|
||||
const orderNumber = str(row["orderNumber"]);
|
||||
if (!orderNumber)
|
||||
continue;
|
||||
if (!orderMap.has(orderNumber))
|
||||
orderMap.set(orderNumber, { meta: row, items: [] });
|
||||
orderMap.get(orderNumber).items.push(row);
|
||||
}
|
||||
console.log(`[LazadaOrder] Grouped into ${orderMap.size} unique orders`);
|
||||
const CHUNK = 30;
|
||||
const entries = Array.from(orderMap.entries());
|
||||
let upserted = 0;
|
||||
for (let i = 0; i < entries.length; i += CHUNK) {
|
||||
const chunk = entries.slice(i, i + CHUNK);
|
||||
await prisma.$transaction(async (tx) => {
|
||||
for (const [orderNumber, { meta, items }] of chunk) {
|
||||
// Sync strategy: delete old items
|
||||
await tx.lazadaOrderItem.deleteMany({ where: { orderNumber } });
|
||||
let totalPaidPrice = 0;
|
||||
let totalShippingFee = 0;
|
||||
const itemsData = items.map((item) => {
|
||||
const paidPrice = toNum(item["paidPrice"]);
|
||||
const shippingFee = toNum(item["shippingFee"]);
|
||||
totalPaidPrice += paidPrice;
|
||||
totalShippingFee += shippingFee;
|
||||
return {
|
||||
orderItemId: str(item["orderItemId"]),
|
||||
sellerSku: str(item["sellerSku"]),
|
||||
lazadaSku: str(item["lazadaSku"]),
|
||||
itemName: str(item["itemName"]),
|
||||
variation: str(item["variation"]),
|
||||
unitPrice: toNum(item["unitPrice"]),
|
||||
paidPrice,
|
||||
sellerDiscountTotal: toNum(item["sellerDiscountTotal"]),
|
||||
platformDiscountTotal: toNum(item["platformDiscountTotal"]),
|
||||
shippingFee,
|
||||
status: str(item["status"]),
|
||||
};
|
||||
});
|
||||
// Decide order status based on items (or first item)
|
||||
// We'll just use the first item's status if they share it
|
||||
const orderStatus = str(meta["status"]);
|
||||
const orderData = {
|
||||
shop: shopName,
|
||||
createTime: parseLazadaDate(meta["createTime"]),
|
||||
updateTime: parseLazadaDate(meta["updateTime"]),
|
||||
deliveredDate: parseLazadaDate(meta["deliveredDate"]),
|
||||
customerName: str(meta["customerName"]),
|
||||
shippingCity: str(meta["shippingCity"]),
|
||||
shippingPostCode: str(meta["shippingPostCode"]),
|
||||
shippingRegion: str(meta["shippingRegion"]),
|
||||
payMethod: str(meta["payMethod"]),
|
||||
status: orderStatus,
|
||||
trackingCode: str(meta["trackingCode"]),
|
||||
shippingProvider: str(meta["shippingProvider"]),
|
||||
orderAmount: totalPaidPrice,
|
||||
shippingFee: totalShippingFee,
|
||||
};
|
||||
await tx.lazadaOrder.upsert({
|
||||
where: { orderNumber },
|
||||
update: { ...orderData, items: { create: itemsData } },
|
||||
create: { orderNumber, ...orderData, items: { create: itemsData } },
|
||||
});
|
||||
upserted++;
|
||||
}
|
||||
}, { timeout: 30000 });
|
||||
console.log(`[LazadaOrder] Progress: ${Math.min(i + CHUNK, entries.length)}/${entries.length}`);
|
||||
}
|
||||
console.log(`[LazadaOrder] ✅ Done — ${upserted} orders upserted`);
|
||||
return { success: true, message: `นำเข้าสำเร็จ ${upserted} คำสั่งซื้อ (${rows.length} แถว)`, ordersCount: upserted, rowsCount: rows.length };
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`[LazadaOrder] ❌ Error:`, error.message);
|
||||
set.status = 500;
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
}, { body: t.Object({ rows: t.Array(t.Any()), shop: t.Optional(t.String()) }) })
|
||||
// ── Clear Shop Data ───────────────────────────────────────────────────────
|
||||
.delete("/clear", async ({ query, set }) => {
|
||||
try {
|
||||
const shop = query.shop || "hom-aroma";
|
||||
const result = await prisma.lazadaOrder.deleteMany({ where: { shop } });
|
||||
return { success: true, message: `ลบข้อมูลร้าน ${shop} สำเร็จ (${result.count} รายการ)`, count: result.count };
|
||||
}
|
||||
catch (error) {
|
||||
set.status = 500;
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
})
|
||||
// ── GET Single Order ──────────────────────────────────────────────────────
|
||||
.get("/orders/:orderNumber", async ({ params, set }) => {
|
||||
const order = await prisma.lazadaOrder.findUnique({ where: { orderNumber: params.orderNumber }, include: { items: true } });
|
||||
if (!order) {
|
||||
set.status = 404;
|
||||
return { error: "Order not found" };
|
||||
}
|
||||
return order;
|
||||
})
|
||||
// ── GET Orders (paginated) ────────────────────────────────────────────────
|
||||
.get("/orders", async ({ query }) => {
|
||||
const page = Number(query.page ?? 1);
|
||||
const limit = Number(query.limit ?? 30);
|
||||
const skip = (page - 1) * limit;
|
||||
const shop = query.shop || "hom-aroma";
|
||||
const where = { shop };
|
||||
const [items, total] = await Promise.all([
|
||||
prisma.lazadaOrder.findMany({ where, include: { items: true }, orderBy: { createTime: "desc" }, skip, take: limit }),
|
||||
prisma.lazadaOrder.count({ where }),
|
||||
]);
|
||||
return { items, total, page, limit, totalPages: Math.ceil(total / limit) };
|
||||
})
|
||||
// ── GET Summary ───────────────────────────────────────────────────────────
|
||||
.get("/summary", async ({ query }) => {
|
||||
const shop = query.shop || "hom-aroma";
|
||||
const where = { shop };
|
||||
const [orderCount, agg] = await Promise.all([
|
||||
prisma.lazadaOrder.count({ where }),
|
||||
prisma.lazadaOrder.aggregate({ where, _sum: { orderAmount: true, shippingFee: true } }),
|
||||
]);
|
||||
return {
|
||||
orderCount,
|
||||
totalAmount: agg._sum.orderAmount ?? 0,
|
||||
totalShipping: agg._sum.shippingFee ?? 0,
|
||||
};
|
||||
})
|
||||
// ── GET Analytics (Single Shop) ───────────────────────────────────────────
|
||||
.get("/analytics", async ({ query }) => {
|
||||
const shop = query.shop || "hom-aroma";
|
||||
const dateFrom = query.dateFrom;
|
||||
const dateTo = query.dateTo;
|
||||
const where = { shop };
|
||||
if (dateFrom || dateTo) {
|
||||
where.createTime = {};
|
||||
if (dateFrom)
|
||||
where.createTime.gte = dateFrom;
|
||||
if (dateTo)
|
||||
where.createTime.lte = dateTo + " 23:59";
|
||||
}
|
||||
const allOrders = await prisma.lazadaOrder.findMany({
|
||||
where,
|
||||
select: {
|
||||
orderNumber: true, status: true, createTime: true,
|
||||
orderAmount: true, shippingFee: true, payMethod: true,
|
||||
items: { select: { itemName: true, variation: true, sellerDiscountTotal: true, platformDiscountTotal: true, paidPrice: true, sellerSku: true } },
|
||||
},
|
||||
});
|
||||
// Monthly aggregation
|
||||
const monthMap = new Map();
|
||||
for (const o of allOrders) {
|
||||
const month = (o.createTime ?? "").slice(0, 7) || "Unknown";
|
||||
if (!monthMap.has(month))
|
||||
monthMap.set(month, { month, revenue: 0, orders: 0, cancelled: 0, items: 0, platformDiscount: 0, sellerDiscount: 0 });
|
||||
const m = monthMap.get(month);
|
||||
m.orders += 1;
|
||||
m.revenue += o.orderAmount;
|
||||
m.platformDiscount += o.items.reduce((s, i) => s + Math.abs(i.platformDiscountTotal), 0);
|
||||
m.sellerDiscount += o.items.reduce((s, i) => s + Math.abs(i.sellerDiscountTotal), 0);
|
||||
if (o.status?.toLowerCase().includes("canceled") || o.status?.toLowerCase().includes("failed"))
|
||||
m.cancelled += 1;
|
||||
m.items += o.items.length;
|
||||
}
|
||||
const monthly = Array.from(monthMap.values()).sort((a, b) => a.month.localeCompare(b.month));
|
||||
// Status breakdown
|
||||
const statusMap = new Map();
|
||||
for (const o of allOrders) {
|
||||
const key = o.status || "Unknown";
|
||||
statusMap.set(key, (statusMap.get(key) ?? 0) + 1);
|
||||
}
|
||||
const statusBreakdown = Array.from(statusMap.entries()).map(([status, count]) => ({ status, count })).sort((a, b) => b.count - a.count);
|
||||
// Payment method breakdown
|
||||
const payMap = new Map();
|
||||
for (const o of allOrders) {
|
||||
const key = o.payMethod || "Unknown";
|
||||
payMap.set(key, (payMap.get(key) ?? 0) + 1);
|
||||
}
|
||||
const paymentBreakdown = Array.from(payMap.entries()).map(([method, count]) => ({ method, count })).sort((a, b) => b.count - a.count);
|
||||
// Top products
|
||||
const productMap = new Map();
|
||||
for (const o of allOrders) {
|
||||
for (const item of o.items) {
|
||||
const key = `${item.itemName}__${item.variation ?? ""}`;
|
||||
if (!productMap.has(key))
|
||||
productMap.set(key, { name: item.itemName, variation: item.variation ?? "", qty: 0, revenue: 0 });
|
||||
const p = productMap.get(key);
|
||||
p.qty += 1; // Lazada export is 1 row per item quantity usually, so we add 1
|
||||
p.revenue += item.paidPrice;
|
||||
}
|
||||
}
|
||||
const topProducts = Array.from(productMap.values()).sort((a, b) => b.revenue - a.revenue).slice(0, 10);
|
||||
// Summary
|
||||
const totalRevenue = allOrders.reduce((s, o) => s + o.orderAmount, 0);
|
||||
const totalShipping = allOrders.reduce((s, o) => s + o.shippingFee, 0);
|
||||
const totalPlatformDisc = allOrders.flatMap(o => o.items).reduce((s, i) => s + Math.abs(i.platformDiscountTotal), 0);
|
||||
const totalSellerDisc = allOrders.flatMap(o => o.items).reduce((s, i) => s + Math.abs(i.sellerDiscountTotal), 0);
|
||||
const totalOrders = allOrders.length;
|
||||
const cancelledOrders = allOrders.filter(o => o.status?.toLowerCase().includes("canceled")).length;
|
||||
const cancelRate = totalOrders > 0 ? (cancelledOrders / totalOrders) * 100 : 0;
|
||||
const completedOrders = totalOrders - cancelledOrders;
|
||||
const aov = completedOrders > 0 ? totalRevenue / completedOrders : 0;
|
||||
return {
|
||||
monthly, statusBreakdown, paymentBreakdown, topProducts,
|
||||
summary: {
|
||||
totalRevenue, totalShipping, totalPlatformDisc, totalSellerDisc,
|
||||
netRevenue: totalRevenue - totalShipping, // Simple net (excluding lazada fees which aren't in this report usually)
|
||||
totalOrders, completedOrders, cancelledOrders, cancelRate, aov,
|
||||
},
|
||||
};
|
||||
})
|
||||
// ── GET Analytics Overview (ALL shops combined) ──────────────────────────
|
||||
.get("/analytics-overview", async ({ query }) => {
|
||||
const dateFrom = query.dateFrom;
|
||||
const dateTo = query.dateTo;
|
||||
const where = {};
|
||||
if (dateFrom || dateTo) {
|
||||
where.createTime = {};
|
||||
if (dateFrom)
|
||||
where.createTime.gte = dateFrom;
|
||||
if (dateTo)
|
||||
where.createTime.lte = dateTo + " 23:59";
|
||||
}
|
||||
const allOrders = await prisma.lazadaOrder.findMany({
|
||||
where,
|
||||
select: {
|
||||
orderNumber: true, shop: true, status: true, createTime: true,
|
||||
orderAmount: true, shippingFee: true,
|
||||
items: { select: { platformDiscountTotal: true, sellerDiscountTotal: true, paidPrice: true, itemName: true, variation: true } },
|
||||
},
|
||||
});
|
||||
// Per-shop summary
|
||||
const shopMap = new Map();
|
||||
for (const o of allOrders) {
|
||||
if (!shopMap.has(o.shop)) {
|
||||
shopMap.set(o.shop, { shop: o.shop, orders: 0, completed: 0, cancelled: 0, revenue: 0, shippingFee: 0, platformDiscount: 0, sellerDiscount: 0, items: 0 });
|
||||
}
|
||||
const s = shopMap.get(o.shop);
|
||||
s.orders += 1;
|
||||
s.revenue += o.orderAmount;
|
||||
s.shippingFee += o.shippingFee;
|
||||
s.platformDiscount += o.items.reduce((acc, i) => acc + Math.abs(i.platformDiscountTotal), 0);
|
||||
s.sellerDiscount += o.items.reduce((acc, i) => acc + Math.abs(i.sellerDiscountTotal), 0);
|
||||
s.items += o.items.length;
|
||||
if (o.status?.toLowerCase().includes("canceled"))
|
||||
s.cancelled += 1;
|
||||
else
|
||||
s.completed += 1;
|
||||
}
|
||||
const shops = Array.from(shopMap.values());
|
||||
// Monthly stacked by shop
|
||||
const monthShopMap = new Map();
|
||||
for (const o of allOrders) {
|
||||
const month = (o.createTime ?? "").slice(0, 7) || "Unknown";
|
||||
if (!monthShopMap.has(month))
|
||||
monthShopMap.set(month, new Map());
|
||||
const sm = monthShopMap.get(month);
|
||||
if (!sm.has(o.shop))
|
||||
sm.set(o.shop, { revenue: 0, orders: 0 });
|
||||
const ms = sm.get(o.shop);
|
||||
ms.revenue += o.orderAmount;
|
||||
ms.orders += 1;
|
||||
}
|
||||
const shopKeys = Array.from(shopMap.keys());
|
||||
const monthly = Array.from(monthShopMap.entries())
|
||||
.map(([month, sm]) => {
|
||||
const row = { month };
|
||||
for (const shop of shopKeys) {
|
||||
const d = sm.get(shop) ?? { revenue: 0, orders: 0 };
|
||||
row[`${shop}_revenue`] = d.revenue;
|
||||
row[`${shop}_orders`] = d.orders;
|
||||
}
|
||||
row.totalRevenue = Array.from(sm.values()).reduce((s, v) => s + v.revenue, 0);
|
||||
row.totalOrders = Array.from(sm.values()).reduce((s, v) => s + v.orders, 0);
|
||||
row.revenue = row.totalRevenue; // Provide fallback for global view
|
||||
return row;
|
||||
})
|
||||
.sort((a, b) => a.month.localeCompare(b.month));
|
||||
// Top Products Global
|
||||
const productMap = new Map();
|
||||
for (const o of allOrders) {
|
||||
for (const item of o.items) {
|
||||
const key = `${item.itemName}__${item.variation ?? ""}`;
|
||||
if (!productMap.has(key))
|
||||
productMap.set(key, { name: item.itemName, variation: item.variation ?? "", qty: 0, revenue: 0 });
|
||||
const p = productMap.get(key);
|
||||
p.qty += 1;
|
||||
p.revenue += item.paidPrice;
|
||||
}
|
||||
}
|
||||
const topProducts = Array.from(productMap.values()).sort((a, b) => b.revenue - a.revenue).slice(0, 10);
|
||||
// Global summary
|
||||
const totalRevenue = shops.reduce((s, sh) => s + sh.revenue, 0);
|
||||
const totalShipping = shops.reduce((s, sh) => s + sh.shippingFee, 0);
|
||||
const totalPlatformDisc = shops.reduce((s, sh) => s + sh.platformDiscount, 0);
|
||||
const totalSellerDisc = shops.reduce((s, sh) => s + sh.sellerDiscount, 0);
|
||||
const totalOrders = shops.reduce((s, sh) => s + sh.orders, 0);
|
||||
const completedOrders = shops.reduce((s, sh) => s + sh.completed, 0);
|
||||
const cancelledOrders = shops.reduce((s, sh) => s + sh.cancelled, 0);
|
||||
return {
|
||||
shops,
|
||||
monthly,
|
||||
topProducts,
|
||||
summary: {
|
||||
totalRevenue,
|
||||
totalShipping,
|
||||
totalPlatformDisc,
|
||||
totalSellerDisc,
|
||||
netRevenue: totalRevenue - totalShipping,
|
||||
totalOrders,
|
||||
completedOrders,
|
||||
cancelledOrders,
|
||||
cancelRate: totalOrders > 0 ? (cancelledOrders / totalOrders) * 100 : 0,
|
||||
aov: completedOrders > 0 ? totalRevenue / completedOrders : 0,
|
||||
}
|
||||
};
|
||||
});
|
||||
//# sourceMappingURL=lazadaOrder.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"lazadaOrder.js","sourceRoot":"","sources":["lazadaOrder.ts"],"names":[],"mappings":"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 truncated
|
||||
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
import { Elysia } from 'elysia';
|
||||
export declare const lineRoutes: Elysia<"/line", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
line: {
|
||||
webhook: {
|
||||
post: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
error: string;
|
||||
success?: never;
|
||||
} | {
|
||||
success: boolean;
|
||||
error?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
line: {
|
||||
webhook: {
|
||||
post: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
error: string;
|
||||
success?: never;
|
||||
} | {
|
||||
success: boolean;
|
||||
error?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=line.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"line.d.ts","sourceRoot":"","sources":["line.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAkEhC,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEa,CAAC"}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Elysia } from 'elysia';
|
||||
import * as line from '@line/bot-sdk';
|
||||
import { lineConfig, lineClient } from '../lib/lineClient';
|
||||
import crypto from 'crypto';
|
||||
const webhookHandler = async ({ request, body, set }) => {
|
||||
console.log('[LINE Webhook] Handler body type:', typeof body);
|
||||
console.log('[LINE Webhook] Channel Secret configured:', !!lineConfig.channelSecret, lineConfig.channelSecret ? lineConfig.channelSecret.substring(0, 4) + '...' : 'NONE');
|
||||
// 1. Verify Signature
|
||||
const signature = request.headers.get('x-line-signature');
|
||||
if (!signature) {
|
||||
set.status = 400;
|
||||
return { error: 'Missing Signature' };
|
||||
}
|
||||
// xxIn our global onParse hook, we intercepted the body and returned it as a raw text string.
|
||||
const rawBody = typeof body === 'string' ? body : JSON.stringify(body);
|
||||
if (!rawBody) {
|
||||
set.status = 400;
|
||||
return { error: 'Empty Body' };
|
||||
}
|
||||
const hash = crypto
|
||||
.createHmac('SHA256', lineConfig.channelSecret)
|
||||
.update(rawBody)
|
||||
.digest('base64');
|
||||
console.log('[LINE Webhook] Signature Check - Received:', signature, 'Calculated:', hash);
|
||||
if (hash !== signature) {
|
||||
console.error('[LINE Webhook] Invalid Signature Blocked');
|
||||
set.status = 401;
|
||||
return { error: 'Invalid Signature' };
|
||||
}
|
||||
try {
|
||||
const parsedBody = typeof body === 'string' ? JSON.parse(body) : body;
|
||||
const events = parsedBody.events || [];
|
||||
for (const event of events) {
|
||||
if (event.type === 'message' && event.message.type === 'text') {
|
||||
// Ignore verification dummy tokens
|
||||
if (event.replyToken === '00000000000000000000000000000000' || event.replyToken === 'ffffffffffffffffffffffffffffffff') {
|
||||
continue;
|
||||
}
|
||||
// Simple echo bot for now
|
||||
await lineClient.replyMessage({
|
||||
replyToken: event.replyToken,
|
||||
messages: [{
|
||||
type: 'text',
|
||||
text: `คุณส่งข้อความว่า: ${event.message.text}`
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
catch (err) {
|
||||
console.error('[LINE Webhook] Error:', err);
|
||||
set.status = 500;
|
||||
return { error: 'Webhook processing failed' };
|
||||
}
|
||||
};
|
||||
export const lineRoutes = new Elysia({ prefix: '/line' })
|
||||
.post('/webhook', webhookHandler)
|
||||
.post('/webhook/', webhookHandler);
|
||||
//# sourceMappingURL=line.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"line.js","sourceRoot":"","sources":["line.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const menusRoute: Elysia<"/menus", {
|
||||
decorator: any;
|
||||
store: any;
|
||||
derive: any;
|
||||
resolve: any;
|
||||
}, any, any, {
|
||||
menus: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
menus: {
|
||||
":name": {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
name: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
menus: any;
|
||||
} & {
|
||||
menus: {
|
||||
post: any;
|
||||
};
|
||||
} & {
|
||||
menus: {
|
||||
":name": {
|
||||
put: any;
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, any>;
|
||||
//# sourceMappingURL=menus.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"menus.d.ts","sourceRoot":"","sources":["menus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAInC,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6FnB,CAAC"}
|
||||
@@ -0,0 +1,97 @@
|
||||
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" }
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.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;
|
||||
})
|
||||
.use(adminOnly)
|
||||
.post("/", async ({ body, set }) => {
|
||||
try {
|
||||
const { name, items } = body;
|
||||
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;
|
||||
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()
|
||||
}))
|
||||
})
|
||||
});
|
||||
//# sourceMappingURL=menus.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"menus.js","sourceRoot":"","sources":["menus.ts"],"names":[],"mappings":"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"}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const newsletterRoute: Elysia<"/newsletter", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
newsletter: {
|
||||
subscribe: {
|
||||
post: {
|
||||
body: {
|
||||
email: string;
|
||||
};
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
message: string;
|
||||
error?: never;
|
||||
} | {
|
||||
error: string;
|
||||
message?: never;
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
newsletter: {
|
||||
subscribers: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=newsletter.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"newsletter.d.ts","sourceRoot":"","sources":["newsletter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAGnC,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgCxB,CAAC"}
|
||||
@@ -0,0 +1,33 @@
|
||||
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;
|
||||
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" }
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=newsletter.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"newsletter.js","sourceRoot":"","sources":["newsletter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,MAAM,MAAM,eAAe,CAAC;AAEnC,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;KACjE,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE;IAC1C,IAAI,CAAC;QACH,MAAM,EAAE,KAAK,EAAE,GAAG,IAAyB,CAAC;QAE5C,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,UAAU,CAAC;YAC5D,KAAK,EAAE,EAAE,KAAK,EAAE;SACjB,CAAC,CAAC;QAEH,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC;QACjD,CAAC;QAED,MAAM,MAAM,CAAC,oBAAoB,CAAC,MAAM,CAAC;YACvC,IAAI,EAAE,EAAE,KAAK,EAAE;SAChB,CAAC,CAAC;QAEH,OAAO,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC;IAChD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnB,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC;IAC1C,CAAC;AACH,CAAC,EAAE;IACD,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACb,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;KACrC,CAAC;CACH,CAAC;KACD,GAAG,CAAC,cAAc,EAAE,KAAK,IAAI,EAAE;IAC9B,OAAO,MAAM,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC;QAChD,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;KAC/B,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
||||
Vendored
+222
@@ -0,0 +1,222 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const ordersRoute: Elysia<"/orders", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
orders: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
orders: {
|
||||
":id": {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
orders: {
|
||||
post: {
|
||||
body: {
|
||||
discountAmount?: number;
|
||||
customerId?: number | null;
|
||||
subtotal?: number;
|
||||
promoLevel1Name?: string | null;
|
||||
promoLevel1Disc?: number;
|
||||
promoLevel2Name?: string | null;
|
||||
promoLevel2Disc?: number;
|
||||
couponCode?: string | null;
|
||||
pointsUsed?: number;
|
||||
pointsDiscount?: number;
|
||||
items: {
|
||||
variantId?: number;
|
||||
variantSize?: string;
|
||||
productImage?: string;
|
||||
productId: number;
|
||||
quantity: number;
|
||||
priceAtPurchase: number;
|
||||
productName: string;
|
||||
}[];
|
||||
totalAmount: number;
|
||||
customerName: string;
|
||||
shippingFee: number;
|
||||
customerEmail: string;
|
||||
customerPhone: string;
|
||||
shippingAddress: string;
|
||||
};
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
orders: {
|
||||
":id": {
|
||||
status: {
|
||||
patch: {
|
||||
body: {
|
||||
status: string;
|
||||
};
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
orders: {
|
||||
":id": {
|
||||
slip: {
|
||||
patch: {
|
||||
body: {
|
||||
paymentSlipUrl: string;
|
||||
};
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
orders: {
|
||||
":id": {
|
||||
tracking: {
|
||||
patch: {
|
||||
body: {
|
||||
trackingNo: string;
|
||||
};
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
orders: {
|
||||
customer: {
|
||||
":customerId": {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
customerId: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
orders: {
|
||||
track: {
|
||||
post: {
|
||||
body: {
|
||||
phone: string;
|
||||
orderId: number;
|
||||
};
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
error: string;
|
||||
success?: never;
|
||||
orderId?: never;
|
||||
} | {
|
||||
success: boolean;
|
||||
orderId: any;
|
||||
error?: never;
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=orders.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"orders.d.ts","sourceRoot":"","sources":["orders.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAUnC,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuPpB,CAAC"}
|
||||
@@ -0,0 +1,247 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
import { sendOrderCreatedNotification, sendPaymentConfirmedNotification, sendOrderShippedNotification, sendOrderCancelledNotification, markOrderAsPaid } from "../lib/notifications";
|
||||
export const ordersRoute = new Elysia({ prefix: "/orders" })
|
||||
.get("/", async () => {
|
||||
return await prisma.order.findMany({
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: { items: true, customer: true }
|
||||
});
|
||||
})
|
||||
.get("/:id", async ({ params: { id }, set }) => {
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id: Number(id) },
|
||||
include: { items: true, customer: true }
|
||||
});
|
||||
if (!order) {
|
||||
set.status = 404;
|
||||
return { error: "Order not found" };
|
||||
}
|
||||
return order;
|
||||
})
|
||||
.post("/", async ({ body, set }) => {
|
||||
try {
|
||||
const data = body;
|
||||
const settings = await prisma.storeSettings.findFirst();
|
||||
const earnRate = settings?.pointsEarnRate || 1.0;
|
||||
const netAmount = data.totalAmount - data.shippingFee;
|
||||
const pointsEarned = Math.floor(Math.max(0, netAmount) * earnRate);
|
||||
if (data.customerId && data.pointsUsed && data.pointsUsed > 0) {
|
||||
// Verify customer has enough points
|
||||
const customer = await prisma.customer.findUnique({ where: { id: data.customerId } });
|
||||
if (!customer || customer.points < data.pointsUsed) {
|
||||
set.status = 400;
|
||||
return { error: "Insufficient points" };
|
||||
}
|
||||
// Deduct points immediately
|
||||
await prisma.customer.update({
|
||||
where: { id: data.customerId },
|
||||
data: { points: { decrement: data.pointsUsed } }
|
||||
});
|
||||
}
|
||||
const order = await prisma.order.create({
|
||||
data: {
|
||||
customerId: data.customerId,
|
||||
customerName: data.customerName,
|
||||
customerEmail: data.customerEmail,
|
||||
customerPhone: data.customerPhone,
|
||||
shippingAddress: data.shippingAddress,
|
||||
subtotal: data.subtotal || 0,
|
||||
promoLevel1Name: data.promoLevel1Name,
|
||||
promoLevel1Disc: data.promoLevel1Disc || 0,
|
||||
promoLevel2Name: data.promoLevel2Name,
|
||||
promoLevel2Disc: data.promoLevel2Disc || 0,
|
||||
couponCode: data.couponCode || null,
|
||||
discountAmount: data.discountAmount || 0,
|
||||
pointsUsed: data.pointsUsed || 0,
|
||||
pointsDiscount: data.pointsDiscount || 0,
|
||||
pointsEarned: pointsEarned,
|
||||
shippingFee: data.shippingFee,
|
||||
totalAmount: data.totalAmount,
|
||||
items: {
|
||||
create: data.items.map((item) => ({
|
||||
productId: item.productId,
|
||||
variantId: item.variantId,
|
||||
quantity: item.quantity,
|
||||
priceAtPurchase: item.priceAtPurchase,
|
||||
productName: item.productName,
|
||||
variantSize: item.variantSize,
|
||||
productImage: item.productImage
|
||||
}))
|
||||
}
|
||||
},
|
||||
include: { items: true }
|
||||
});
|
||||
// If a coupon was used, increment its usedCount
|
||||
if (data.couponCode) {
|
||||
try {
|
||||
await prisma.coupon.update({
|
||||
where: { code: data.couponCode.toUpperCase() },
|
||||
data: { usedCount: { increment: 1 } }
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
console.error("Failed to increment coupon usedCount:", e);
|
||||
}
|
||||
}
|
||||
// Trigger LINE notification asynchronously
|
||||
sendOrderCreatedNotification(order.id).catch(e => {
|
||||
console.error("Failed to trigger sendOrderCreatedNotification:", e);
|
||||
});
|
||||
return order;
|
||||
}
|
||||
catch (err) {
|
||||
console.error("[Orders] Error:", err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to create order" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
customerId: t.Optional(t.Nullable(t.Number())),
|
||||
customerName: t.String(),
|
||||
customerEmail: t.String(),
|
||||
customerPhone: t.String(),
|
||||
shippingAddress: t.String(),
|
||||
totalAmount: t.Number(),
|
||||
shippingFee: t.Number(),
|
||||
subtotal: t.Optional(t.Number()),
|
||||
promoLevel1Name: t.Optional(t.Nullable(t.String())),
|
||||
promoLevel1Disc: t.Optional(t.Number()),
|
||||
promoLevel2Name: t.Optional(t.Nullable(t.String())),
|
||||
promoLevel2Disc: t.Optional(t.Number()),
|
||||
couponCode: t.Optional(t.Nullable(t.String())),
|
||||
discountAmount: t.Optional(t.Number()),
|
||||
pointsUsed: t.Optional(t.Number()),
|
||||
pointsDiscount: t.Optional(t.Number()),
|
||||
items: t.Array(t.Object({
|
||||
productId: t.Number(),
|
||||
variantId: t.Optional(t.Number()),
|
||||
quantity: t.Number(),
|
||||
priceAtPurchase: t.Number(),
|
||||
productName: t.String(),
|
||||
variantSize: t.Optional(t.String()),
|
||||
productImage: t.Optional(t.String())
|
||||
}))
|
||||
})
|
||||
})
|
||||
.patch("/:id/status", async ({ params: { id }, body, set }) => {
|
||||
try {
|
||||
const { status } = body;
|
||||
const order = await prisma.order.findUnique({ where: { id: Number(id) } });
|
||||
if (!order) {
|
||||
set.status = 404;
|
||||
return { error: "Order not found" };
|
||||
}
|
||||
if (status === 'PAID') {
|
||||
await markOrderAsPaid(order.id);
|
||||
}
|
||||
else {
|
||||
await prisma.order.update({
|
||||
where: { id: order.id },
|
||||
data: { status }
|
||||
});
|
||||
// If status becomes CANCELLED, refund the points used and notify
|
||||
if (status === 'CANCELLED' && order.status !== 'CANCELLED') {
|
||||
if (order.customerId && order.pointsUsed > 0) {
|
||||
await prisma.customer.update({
|
||||
where: { id: order.customerId },
|
||||
data: { points: { increment: order.pointsUsed } }
|
||||
});
|
||||
}
|
||||
sendOrderCancelledNotification(order.id).catch(e => console.error(e));
|
||||
}
|
||||
// If status becomes SHIPPED, notify
|
||||
if (status === 'SHIPPED' && order.status !== 'SHIPPED') {
|
||||
sendOrderShippedNotification(order.id).catch(e => console.error(e));
|
||||
}
|
||||
}
|
||||
return await prisma.order.findUnique({
|
||||
where: { id: order.id },
|
||||
include: { items: true, customer: true }
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to update order status" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
status: t.String()
|
||||
})
|
||||
})
|
||||
.patch("/:id/slip", async ({ params: { id }, body, set }) => {
|
||||
try {
|
||||
const { paymentSlipUrl } = body;
|
||||
return await prisma.order.update({
|
||||
where: { id: Number(id) },
|
||||
data: { paymentSlipUrl },
|
||||
include: { items: true }
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to upload slip" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
paymentSlipUrl: t.String()
|
||||
})
|
||||
})
|
||||
.patch("/:id/tracking", async ({ params: { id }, body, set }) => {
|
||||
try {
|
||||
const { trackingNo } = body;
|
||||
const order = await prisma.order.update({
|
||||
where: { id: Number(id) },
|
||||
data: { trackingNo, status: "SHIPPED" },
|
||||
include: { items: true }
|
||||
});
|
||||
// Trigger notification asynchronously
|
||||
sendOrderShippedNotification(order.id).catch(e => {
|
||||
console.error("Failed to trigger sendOrderShippedNotification:", e);
|
||||
});
|
||||
return order;
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to update tracking number" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
trackingNo: t.String()
|
||||
})
|
||||
})
|
||||
.get("/customer/:customerId", async ({ params: { customerId } }) => {
|
||||
return await prisma.order.findMany({
|
||||
where: { customerId: Number(customerId) },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: { items: true }
|
||||
});
|
||||
})
|
||||
.post("/track", async ({ body, set }) => {
|
||||
try {
|
||||
const { orderId, phone } = body;
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id: Number(orderId) }
|
||||
});
|
||||
if (!order) {
|
||||
set.status = 404;
|
||||
return { error: "Order not found" };
|
||||
}
|
||||
const sanitizePhone = (p) => p.replace(/[^0-9]/g, '');
|
||||
if (sanitizePhone(order.customerPhone) !== sanitizePhone(phone)) {
|
||||
set.status = 401;
|
||||
return { error: "Authentication failed. Phone number does not match." };
|
||||
}
|
||||
return { success: true, orderId: order.id };
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Invalid tracking request" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
orderId: t.Number(),
|
||||
phone: t.String()
|
||||
})
|
||||
});
|
||||
//# sourceMappingURL=orders.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"orders.js","sourceRoot":"","sources":["orders.ts"],"names":[],"mappings":"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 truncated
|
||||
Vendored
+151
@@ -0,0 +1,151 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const pagesRoute: Elysia<"/pages", {
|
||||
decorator: any;
|
||||
store: any;
|
||||
derive: any;
|
||||
resolve: any;
|
||||
}, any, any, {
|
||||
pages: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
pages: {
|
||||
":slug": {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
slug: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
pages: {
|
||||
":slug": {
|
||||
sections: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
slug: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
pages: {
|
||||
newsletter: {
|
||||
post: {
|
||||
body: {
|
||||
email: string;
|
||||
};
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
error: string;
|
||||
success?: never;
|
||||
data?: never;
|
||||
} | {
|
||||
success: boolean;
|
||||
data: any;
|
||||
error?: never;
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
pages: any;
|
||||
} & {
|
||||
pages: {
|
||||
post: any;
|
||||
};
|
||||
} & {
|
||||
pages: {
|
||||
":slug": {
|
||||
delete: any;
|
||||
};
|
||||
};
|
||||
} & {
|
||||
pages: {
|
||||
":slug": {
|
||||
sections: {
|
||||
post: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
pages: {
|
||||
sections: {
|
||||
":sectionId": {
|
||||
put: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
pages: {
|
||||
":slug": {
|
||||
sections: {
|
||||
reorder: {
|
||||
put: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
pages: {
|
||||
sections: {
|
||||
":sectionId": {
|
||||
delete: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, any>;
|
||||
//# sourceMappingURL=pages.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["pages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAkBnC,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA0UnB,CAAC"}
|
||||
@@ -0,0 +1,484 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
import { HeroArticlePayloadSchema, RichTextPayloadSchema, ScentNotesBreakdownPayloadSchema, QuoteCalloutPayloadSchema, ProductCarouselPayloadSchema } from "@homaroma/types";
|
||||
import { adminOnly } from "../middleware/auth";
|
||||
const RESERVED_SLUGS = [
|
||||
"shop", "category", "products", "cart", "checkout", "account", "search",
|
||||
"contact", "login", "admin", "wishlist", "track", "coupons", "scent-guide",
|
||||
"faq", "about", "profile", "article", "pages", "orders", "customers",
|
||||
"settings", "menus", "auth", "api"
|
||||
];
|
||||
export const pagesRoute = new Elysia({ prefix: "/pages" })
|
||||
.get("/", async () => {
|
||||
return await prisma.page.findMany({
|
||||
include: {
|
||||
sections: {
|
||||
orderBy: { order: "asc" }
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.get("/:slug", async ({ params: { slug }, query, set }) => {
|
||||
const includeInactive = String(query?.includeInactive || "").toLowerCase() === "true";
|
||||
const lang = query?.lang || 'th';
|
||||
const page = await prisma.page.findUnique({
|
||||
where: { slug },
|
||||
include: {
|
||||
sections: {
|
||||
where: includeInactive ? undefined : { isActive: true },
|
||||
orderBy: { order: "asc" }
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!page) {
|
||||
set.status = 404;
|
||||
return { error: "Page not found" };
|
||||
}
|
||||
const populatedSections = await Promise.all(page.sections.map(async (section) => {
|
||||
const payload = section.payload;
|
||||
if (!payload)
|
||||
return section;
|
||||
const populatedPayload = { ...payload };
|
||||
if (section.type === 'PRODUCTS') {
|
||||
let products = [];
|
||||
const limit = payload.limit || 3;
|
||||
const productInclude = {
|
||||
category: true,
|
||||
scent: true,
|
||||
translations: true,
|
||||
variants: {
|
||||
include: {
|
||||
bomItems: { include: { rawMaterial: true } }
|
||||
},
|
||||
orderBy: { price: "asc" }
|
||||
},
|
||||
images: {
|
||||
orderBy: { order: "asc" }
|
||||
}
|
||||
};
|
||||
if (payload.type === 'featured') {
|
||||
products = await prisma.product.findMany({
|
||||
where: { categoryId: 1 },
|
||||
include: productInclude,
|
||||
take: limit
|
||||
});
|
||||
}
|
||||
else if (payload.type === 'scent' && payload.scentId) {
|
||||
products = await prisma.product.findMany({
|
||||
where: { scentId: Number(payload.scentId) },
|
||||
include: productInclude,
|
||||
take: limit
|
||||
});
|
||||
}
|
||||
else if (payload.type === 'custom' && Array.isArray(payload.productIds)) {
|
||||
const ids = payload.productIds.map(Number);
|
||||
products = await prisma.product.findMany({
|
||||
where: { id: { in: ids } },
|
||||
include: productInclude
|
||||
});
|
||||
products.sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id));
|
||||
}
|
||||
populatedPayload.products = products.map((p) => {
|
||||
if (lang === 'all') {
|
||||
const nameObj = {};
|
||||
const descObj = {};
|
||||
const detailsObj = {};
|
||||
p.translations?.forEach((t) => {
|
||||
nameObj[t.langCode] = t.name;
|
||||
descObj[t.langCode] = t.description;
|
||||
detailsObj[t.langCode] = t.details;
|
||||
});
|
||||
return { ...p, name: nameObj, description: descObj, details: detailsObj };
|
||||
}
|
||||
else {
|
||||
const trans = p.translations?.find((t) => t.langCode === lang) || p.translations?.find((t) => t.langCode === 'th') || p.translations?.[0];
|
||||
return { ...p, name: trans?.name || p.scent?.name || '', description: trans?.description, details: trans?.details };
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (section.type === 'CATEGORIES') {
|
||||
let categories = [];
|
||||
if (Array.isArray(payload.categoryIds) && payload.categoryIds.length > 0) {
|
||||
const ids = payload.categoryIds.map(Number);
|
||||
categories = await prisma.category.findMany({
|
||||
where: { id: { in: ids } },
|
||||
include: {
|
||||
_count: {
|
||||
select: { products: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
categories.sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id));
|
||||
}
|
||||
else {
|
||||
categories = await prisma.category.findMany({
|
||||
include: {
|
||||
_count: {
|
||||
select: { products: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
populatedPayload.categories = categories;
|
||||
}
|
||||
return {
|
||||
...section,
|
||||
payload: populatedPayload
|
||||
};
|
||||
}));
|
||||
return {
|
||||
...page,
|
||||
sections: populatedSections
|
||||
};
|
||||
})
|
||||
.get("/:slug/sections", async ({ params: { slug }, set }) => {
|
||||
try {
|
||||
return await prisma.pageSection.findMany({
|
||||
where: { pageId: Number(slug) },
|
||||
orderBy: { order: "asc" }
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to fetch page sections" };
|
||||
}
|
||||
})
|
||||
.post("/newsletter", async ({ body, set }) => {
|
||||
try {
|
||||
const { email } = body;
|
||||
if (!email || !email.includes("@")) {
|
||||
set.status = 400;
|
||||
return { error: "Invalid email address" };
|
||||
}
|
||||
const sub = await prisma.newsletterSubscription.upsert({
|
||||
where: { email },
|
||||
update: {},
|
||||
create: { email }
|
||||
});
|
||||
return { success: true, data: sub };
|
||||
}
|
||||
catch (err) {
|
||||
console.error("[Newsletter] Error:", err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to subscribe" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
email: t.String()
|
||||
})
|
||||
})
|
||||
.use(adminOnly)
|
||||
.post("/", async ({ body, set }) => {
|
||||
try {
|
||||
const data = body;
|
||||
const { id, slug, title, isPublished, status, metadata } = data;
|
||||
if (typeof slug === 'string' && RESERVED_SLUGS.includes(slug.toLowerCase())) {
|
||||
set.status = 400;
|
||||
return { error: "Reserved slug cannot be used" };
|
||||
}
|
||||
let finalIsPublished = true;
|
||||
if (status !== undefined) {
|
||||
finalIsPublished = status === "published";
|
||||
}
|
||||
else if (isPublished !== undefined) {
|
||||
finalIsPublished = isPublished;
|
||||
}
|
||||
if (id) {
|
||||
return await prisma.page.update({
|
||||
where: { id: Number(id) },
|
||||
data: { slug, title, isPublished: finalIsPublished }
|
||||
});
|
||||
}
|
||||
else {
|
||||
return await prisma.page.create({
|
||||
data: { slug, title, isPublished: finalIsPublished }
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error("[Pages] Error:", err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to save page" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
id: t.Optional(t.Number()),
|
||||
slug: t.String(),
|
||||
title: t.String(),
|
||||
isPublished: t.Optional(t.Boolean()),
|
||||
status: t.Optional(t.String()),
|
||||
metadata: t.Optional(t.Any())
|
||||
})
|
||||
})
|
||||
.delete("/:slug", async ({ params: { slug }, set }) => {
|
||||
try {
|
||||
await prisma.page.delete({
|
||||
where: { id: Number(slug) }
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to delete page" };
|
||||
}
|
||||
})
|
||||
.post("/:slug/sections", async ({ params: { slug }, body, set }) => {
|
||||
try {
|
||||
const pageId = Number(slug);
|
||||
const { type, order, payload, isActive } = body;
|
||||
const validationError = validateSectionPayload(type, payload);
|
||||
if (validationError) {
|
||||
set.status = 400;
|
||||
return { error: validationError };
|
||||
}
|
||||
let finalOrder = order;
|
||||
if (finalOrder === undefined) {
|
||||
const lastSection = await prisma.pageSection.findFirst({
|
||||
where: { pageId },
|
||||
orderBy: { order: "desc" }
|
||||
});
|
||||
finalOrder = lastSection ? lastSection.order + 1 : 1;
|
||||
}
|
||||
return await prisma.pageSection.create({
|
||||
data: {
|
||||
pageId,
|
||||
type: type.toUpperCase(),
|
||||
order: finalOrder,
|
||||
payload,
|
||||
isActive: isActive !== false
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
console.error("[PageSections] Error:", err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to add section" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
type: t.String(),
|
||||
order: t.Optional(t.Number()),
|
||||
payload: t.Any(),
|
||||
isActive: t.Optional(t.Boolean())
|
||||
})
|
||||
})
|
||||
.put("/sections/:sectionId", async ({ params: { sectionId }, body, set }) => {
|
||||
try {
|
||||
const id = Number(sectionId);
|
||||
const { type, order, payload, isActive } = body;
|
||||
if (payload && type) {
|
||||
const validationError = validateSectionPayload(type, payload);
|
||||
if (validationError) {
|
||||
set.status = 400;
|
||||
return { error: validationError };
|
||||
}
|
||||
}
|
||||
return await prisma.pageSection.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type: type ? type.toUpperCase() : undefined,
|
||||
order,
|
||||
payload,
|
||||
isActive
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
console.error("[PageSections] Update Error:", err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to update section" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
type: t.Optional(t.String()),
|
||||
order: t.Optional(t.Number()),
|
||||
payload: t.Optional(t.Any()),
|
||||
isActive: t.Optional(t.Boolean())
|
||||
})
|
||||
})
|
||||
.put("/:slug/sections/reorder", async ({ params: { slug }, body, set }) => {
|
||||
try {
|
||||
const pageId = Number(slug);
|
||||
const { sectionOrders } = body;
|
||||
await prisma.$transaction(sectionOrders.map((item) => prisma.pageSection.update({
|
||||
where: { id: item.id, pageId },
|
||||
data: { order: item.order }
|
||||
})));
|
||||
return { success: true };
|
||||
}
|
||||
catch (err) {
|
||||
console.error("[PageSections] Reorder Error:", err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to reorder sections" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
sectionOrders: t.Array(t.Object({
|
||||
id: t.Number(),
|
||||
order: t.Number()
|
||||
}))
|
||||
})
|
||||
})
|
||||
.delete("/sections/:sectionId", async ({ params: { sectionId }, set }) => {
|
||||
try {
|
||||
await prisma.pageSection.delete({
|
||||
where: { id: Number(sectionId) }
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to delete section" };
|
||||
}
|
||||
});
|
||||
function validateSectionPayload(type, payload) {
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
return "Payload must be a JSON object";
|
||||
}
|
||||
switch (type.toUpperCase()) {
|
||||
case 'HERO':
|
||||
if (typeof payload.title !== 'string' || !payload.title.trim())
|
||||
return "Hero payload must contain a non-empty string title";
|
||||
if (typeof payload.description !== 'string')
|
||||
return "Hero payload must contain a string description";
|
||||
if (typeof payload.imageUrl !== 'string' || !payload.imageUrl.trim())
|
||||
return "Hero payload must contain a non-empty string imageUrl";
|
||||
if (typeof payload.tagline !== 'string')
|
||||
return "Hero payload must contain a string tagline";
|
||||
if (typeof payload.buttonText !== 'string')
|
||||
return "Hero payload must contain a string buttonText";
|
||||
if (typeof payload.buttonLink !== 'string')
|
||||
return "Hero payload must contain a string buttonLink";
|
||||
break;
|
||||
case 'CATEGORIES':
|
||||
if (typeof payload.title !== 'string' || !payload.title.trim())
|
||||
return "Categories payload must contain a non-empty string title";
|
||||
if (payload.subtitle !== undefined && typeof payload.subtitle !== 'string')
|
||||
return "Subtitle must be a string";
|
||||
if (payload.categoryIds !== undefined && !Array.isArray(payload.categoryIds)) {
|
||||
return "categoryIds must be an array of numbers";
|
||||
}
|
||||
break;
|
||||
case 'PRODUCTS':
|
||||
if (typeof payload.title !== 'string' || !payload.title.trim())
|
||||
return "Products payload must contain a non-empty string title";
|
||||
if (payload.subtitle !== undefined && typeof payload.subtitle !== 'string')
|
||||
return "Subtitle must be a string";
|
||||
if (!['featured', 'scent', 'custom'].includes(payload.type)) {
|
||||
return "Products type must be 'featured', 'scent', or 'custom'";
|
||||
}
|
||||
if (payload.type === 'scent' && typeof payload.scentId !== 'number') {
|
||||
return "Scent products section requires a numeric scentId";
|
||||
}
|
||||
if (payload.type === 'custom' && !Array.isArray(payload.productIds)) {
|
||||
return "Custom products section requires an array of productIds";
|
||||
}
|
||||
if (payload.limit !== undefined && typeof payload.limit !== 'number') {
|
||||
return "Limit must be a number";
|
||||
}
|
||||
if (payload.layout !== undefined && !['grid', 'carousel'].includes(payload.layout)) {
|
||||
return "layout must be 'grid' or 'carousel'";
|
||||
}
|
||||
if (payload.enableQuickShop !== undefined && typeof payload.enableQuickShop !== 'boolean') {
|
||||
return "enableQuickShop must be a boolean";
|
||||
}
|
||||
break;
|
||||
case 'BANNER':
|
||||
if (typeof payload.imageUrl !== 'string' || !payload.imageUrl.trim())
|
||||
return "Banner payload must contain a non-empty string imageUrl";
|
||||
if (payload.linkUrl !== undefined && typeof payload.linkUrl !== 'string')
|
||||
return "linkUrl must be a string";
|
||||
if (payload.title !== undefined && typeof payload.title !== 'string')
|
||||
return "title must be a string";
|
||||
if (payload.description !== undefined && typeof payload.description !== 'string')
|
||||
return "description must be a string";
|
||||
if (payload.buttonText !== undefined && typeof payload.buttonText !== 'string')
|
||||
return "buttonText must be a string";
|
||||
break;
|
||||
case 'TEXT':
|
||||
if (typeof payload.content !== 'string' || !payload.content.trim())
|
||||
return "Text payload must contain a non-empty string content";
|
||||
if (payload.title !== undefined && typeof payload.title !== 'string')
|
||||
return "title must be a string";
|
||||
if (payload.textAlign !== undefined && !['left', 'center', 'right'].includes(payload.textAlign)) {
|
||||
return "textAlign must be 'left', 'center', or 'right'";
|
||||
}
|
||||
break;
|
||||
case 'ANNOUNCEMENT_BAR':
|
||||
if (!Array.isArray(payload.slides) || payload.slides.length === 0)
|
||||
return "Announcement Bar requires an array of slides";
|
||||
if (payload.slides.some((s) => typeof s !== 'string'))
|
||||
return "All slides must be strings";
|
||||
break;
|
||||
case 'NEWSLETTER':
|
||||
if (typeof payload.title !== 'string' || !payload.title.trim())
|
||||
return "Newsletter payload must contain a non-empty string title";
|
||||
if (typeof payload.description !== 'string')
|
||||
return "Newsletter description must be a string";
|
||||
if (typeof payload.buttonText !== 'string' || !payload.buttonText.trim())
|
||||
return "Newsletter buttonText must be a non-empty string";
|
||||
if (typeof payload.showPrivacyCheckbox !== 'boolean')
|
||||
return "showPrivacyCheckbox must be a boolean";
|
||||
break;
|
||||
case 'EDITORIAL_TILE':
|
||||
if (typeof payload.title !== 'string' || !payload.title.trim())
|
||||
return "Editorial Tile payload must contain a non-empty string title";
|
||||
if (typeof payload.description !== 'string')
|
||||
return "Editorial Tile description must be a string";
|
||||
if (typeof payload.imageUrl !== 'string' || !payload.imageUrl.trim())
|
||||
return "Editorial Tile imageUrl must be a non-empty string";
|
||||
if (!['left', 'right'].includes(payload.imagePosition))
|
||||
return "imagePosition must be 'left' or 'right'";
|
||||
break;
|
||||
case 'EDITORIAL_CAROUSEL':
|
||||
if (typeof payload.title !== 'string' || !payload.title.trim())
|
||||
return "Editorial Carousel payload must contain a non-empty string title";
|
||||
if (!Array.isArray(payload.slides) || payload.slides.length === 0)
|
||||
return "Editorial Carousel requires an array of slides";
|
||||
for (const [idx, slide] of payload.slides.entries()) {
|
||||
if (typeof slide.title !== 'string' || !slide.title.trim())
|
||||
return `Slide #${idx + 1} must contain a non-empty title`;
|
||||
if (typeof slide.description !== 'string')
|
||||
return `Slide #${idx + 1} must contain a description string`;
|
||||
if (typeof slide.imageUrl !== 'string' || !slide.imageUrl.trim())
|
||||
return `Slide #${idx + 1} must contain a non-empty imageUrl`;
|
||||
if (typeof slide.category !== 'string')
|
||||
return `Slide #${idx + 1} must contain a category string`;
|
||||
}
|
||||
break;
|
||||
case 'HERO_ARTICLE': {
|
||||
const res = HeroArticlePayloadSchema.safeParse(payload);
|
||||
if (!res.success)
|
||||
return res.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');
|
||||
break;
|
||||
}
|
||||
case 'RICH_TEXT': {
|
||||
const res = RichTextPayloadSchema.safeParse(payload);
|
||||
if (!res.success)
|
||||
return res.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');
|
||||
break;
|
||||
}
|
||||
case 'SCENT_NOTES_BREAKDOWN': {
|
||||
const res = ScentNotesBreakdownPayloadSchema.safeParse(payload);
|
||||
if (!res.success)
|
||||
return res.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');
|
||||
break;
|
||||
}
|
||||
case 'QUOTE_CALLOUT': {
|
||||
const res = QuoteCalloutPayloadSchema.safeParse(payload);
|
||||
if (!res.success)
|
||||
return res.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');
|
||||
break;
|
||||
}
|
||||
case 'PRODUCT_CAROUSEL': {
|
||||
const res = ProductCarouselPayloadSchema.safeParse(payload);
|
||||
if (!res.success)
|
||||
return res.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return `Unknown section type: ${type}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
//# sourceMappingURL=pages.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"pages.js","sourceRoot":"","sources":["pages.ts"],"names":[],"mappings":"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 truncated
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const paymentsRoute: Elysia<"/payments", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
payments: {
|
||||
charge: {
|
||||
post: {
|
||||
body: {
|
||||
token?: string;
|
||||
type: "card" | "promptpay";
|
||||
orderId: number;
|
||||
amount: number;
|
||||
};
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
error: any;
|
||||
success?: never;
|
||||
status?: never;
|
||||
chargeId?: never;
|
||||
authorizeUri?: never;
|
||||
qrCodeUrl?: never;
|
||||
} | {
|
||||
success: boolean;
|
||||
status: any;
|
||||
chargeId: any;
|
||||
authorizeUri: any;
|
||||
error?: never;
|
||||
qrCodeUrl?: never;
|
||||
} | {
|
||||
success: boolean;
|
||||
status: any;
|
||||
chargeId: any;
|
||||
qrCodeUrl: any;
|
||||
error?: never;
|
||||
authorizeUri?: never;
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
payments: {
|
||||
webhook: {
|
||||
post: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
received: boolean;
|
||||
error?: never;
|
||||
} | {
|
||||
error: string;
|
||||
received?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=payments.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"payments.d.ts","sourceRoot":"","sources":["payments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAMnC,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqKtB,CAAC"}
|
||||
@@ -0,0 +1,152 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
import { markOrderAsPaid } from "../lib/notifications";
|
||||
const OMISE_SECRET_KEY = process.env.OMISE_SECRET_KEY || "";
|
||||
export const paymentsRoute = new Elysia({ prefix: "/payments" })
|
||||
.post("/charge", async ({ body, set }) => {
|
||||
console.log("[Payment Charge] Incoming request body:", JSON.stringify(body));
|
||||
try {
|
||||
const { orderId, amount, type, token } = body;
|
||||
if (!OMISE_SECRET_KEY) {
|
||||
console.error("[Payment Charge] Error: OMISE_SECRET_KEY is empty");
|
||||
set.status = 500;
|
||||
return { error: "Omise secret key is not configured" };
|
||||
}
|
||||
// 1. Check if order exists
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id: orderId }
|
||||
});
|
||||
if (!order) {
|
||||
set.status = 404;
|
||||
return { error: "Order not found" };
|
||||
}
|
||||
// Convert amount to satang (integer)
|
||||
const amountInSatang = Math.round(amount * 100);
|
||||
// Setup Basic Auth header for Omise
|
||||
const authHeader = "Basic " + Buffer.from(OMISE_SECRET_KEY + ":").toString("base64");
|
||||
if (type === "card") {
|
||||
if (!token) {
|
||||
set.status = 400;
|
||||
return { error: "Card token is required for card payments" };
|
||||
}
|
||||
// Create a card charge directly
|
||||
const chargeRes = await fetch("https://api.omise.co/charges", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": authHeader,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
amount: amountInSatang,
|
||||
currency: "THB",
|
||||
card: token,
|
||||
metadata: {
|
||||
orderId: String(orderId)
|
||||
}
|
||||
})
|
||||
});
|
||||
const charge = await chargeRes.json();
|
||||
if (charge.error) {
|
||||
set.status = 400;
|
||||
return { error: charge.message || "Failed to create credit card charge" };
|
||||
}
|
||||
// If card payment is immediately successful (some cards don't require 3DS)
|
||||
if (charge.status === "successful") {
|
||||
await markOrderAsPaid(orderId);
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
status: charge.status,
|
||||
chargeId: charge.id,
|
||||
authorizeUri: charge.authorize_uri || null // For 3DS card validation redirect
|
||||
};
|
||||
}
|
||||
else {
|
||||
// PromptPay:
|
||||
// 1. Create source
|
||||
const sourceRes = await fetch("https://api.omise.co/sources", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": authHeader,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: "promptpay",
|
||||
amount: amountInSatang,
|
||||
currency: "THB"
|
||||
})
|
||||
});
|
||||
const source = await sourceRes.json();
|
||||
console.log("[Payment Charge] Omise PromptPay Source response:", JSON.stringify(source));
|
||||
if (source.error) {
|
||||
set.status = 400;
|
||||
return { error: source.message || "Failed to create PromptPay source" };
|
||||
}
|
||||
// 2. Create charge
|
||||
const chargeRes = await fetch("https://api.omise.co/charges", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": authHeader,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
amount: amountInSatang,
|
||||
currency: "THB",
|
||||
source: source.id,
|
||||
metadata: {
|
||||
orderId: String(orderId)
|
||||
}
|
||||
})
|
||||
});
|
||||
const charge = await chargeRes.json();
|
||||
console.log("[Payment Charge] Omise PromptPay Charge response:", JSON.stringify(charge));
|
||||
if (charge.error) {
|
||||
set.status = 400;
|
||||
return { error: charge.message || "Failed to create PromptPay charge" };
|
||||
}
|
||||
// Get QR Code Image URL from scannable_code or references
|
||||
const qrCodeUrl = charge.source?.scannable_code?.image?.download_uri || charge.source?.references?.barcode || null;
|
||||
return {
|
||||
success: true,
|
||||
status: charge.status,
|
||||
chargeId: charge.id,
|
||||
qrCodeUrl: qrCodeUrl
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error("[Payment Charge] Error:", err);
|
||||
set.status = 500;
|
||||
return { error: err.message || "Failed to process payment request" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
orderId: t.Number(),
|
||||
amount: t.Number(),
|
||||
type: t.Union([t.Literal("card"), t.Literal("promptpay")]),
|
||||
token: t.Optional(t.String())
|
||||
})
|
||||
})
|
||||
.post("/webhook", async ({ body, set }) => {
|
||||
try {
|
||||
const event = body;
|
||||
console.log(`[Omise Webhook] Received event: ${event.key}`);
|
||||
if (event.key === "charge.complete") {
|
||||
const charge = event.data;
|
||||
const status = charge.status;
|
||||
const orderIdStr = charge.metadata?.orderId;
|
||||
if (orderIdStr && status === "successful") {
|
||||
const orderId = parseInt(orderIdStr, 10);
|
||||
console.log(`[Omise Webhook] Payment successful for Order #${orderId}`);
|
||||
await markOrderAsPaid(orderId);
|
||||
}
|
||||
}
|
||||
return { received: true };
|
||||
}
|
||||
catch (err) {
|
||||
console.error("[Omise Webhook] Error processing event:", err);
|
||||
set.status = 500;
|
||||
return { error: "Failed to process webhook" };
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=payments.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"payments.js","sourceRoot":"","sources":["payments.ts"],"names":[],"mappings":"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"}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const productsRoute: Elysia<"/products", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
products: {
|
||||
scents: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
products: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
data: any;
|
||||
meta: {
|
||||
total: any;
|
||||
page: number;
|
||||
limit: number;
|
||||
lastPage: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
products: {
|
||||
":id": {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
products: {
|
||||
":id": {
|
||||
put: {
|
||||
body: any;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
success: boolean;
|
||||
product: any;
|
||||
};
|
||||
400: {
|
||||
readonly message: "Invalid product ID";
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
500: {
|
||||
readonly success: false;
|
||||
readonly message: "Failed to update product";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
products: {
|
||||
":id": {
|
||||
patch: {
|
||||
body: any;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
success: boolean;
|
||||
product: any;
|
||||
};
|
||||
400: {
|
||||
readonly message: "Invalid product ID";
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
500: {
|
||||
readonly success: false;
|
||||
readonly message: "Failed to patch product";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=products.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"products.d.ts","sourceRoot":"","sources":["products.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAGnC,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8RvB,CAAC"}
|
||||
@@ -0,0 +1,279 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
export const productsRoute = new Elysia({ prefix: "/products" })
|
||||
.get("/scents", async () => {
|
||||
return await prisma.scent.findMany({
|
||||
orderBy: { name: "asc" }
|
||||
});
|
||||
})
|
||||
.get("/", async ({ query }) => {
|
||||
const categoryId = query.categoryId ? parseInt(query.categoryId) : undefined;
|
||||
const scentId = query.scentId ? parseInt(query.scentId) : undefined;
|
||||
const search = query.search?.trim() || undefined;
|
||||
const lang = query.lang || 'th';
|
||||
// Pagination params
|
||||
const page = query.page ? parseInt(query.page) : 1;
|
||||
const limit = query.limit ? parseInt(query.limit) : 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const where = {
|
||||
...(categoryId && { categoryId }),
|
||||
...(scentId && { scentId }),
|
||||
...(search && {
|
||||
OR: [
|
||||
{ translations: { some: { name: { contains: search, mode: "insensitive" } } } },
|
||||
{ scent: { name: { contains: search, mode: "insensitive" } } },
|
||||
{ category: { name: { contains: search, mode: "insensitive" } } },
|
||||
],
|
||||
}),
|
||||
};
|
||||
const [products, total] = await Promise.all([
|
||||
prisma.product.findMany({
|
||||
where,
|
||||
include: {
|
||||
category: { include: { translations: true } },
|
||||
scent: true,
|
||||
translations: true,
|
||||
variants: {
|
||||
include: {
|
||||
bomItems: { include: { rawMaterial: true } }
|
||||
},
|
||||
orderBy: { price: "asc" }
|
||||
},
|
||||
images: { orderBy: { order: "asc" } },
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
}),
|
||||
prisma.product.count({ where }),
|
||||
]);
|
||||
// Map translations
|
||||
const mappedProducts = products.map(p => {
|
||||
if (lang === 'all') {
|
||||
const nameObj = {};
|
||||
const descObj = {};
|
||||
const detailsObj = {};
|
||||
p.translations.forEach(t => {
|
||||
nameObj[t.langCode] = t.name;
|
||||
descObj[t.langCode] = t.description;
|
||||
detailsObj[t.langCode] = t.details;
|
||||
});
|
||||
return { ...p, name: nameObj, description: descObj, details: detailsObj, category: { ...p.category, name: p.category.name } }; // Actually for 'all', keep as is, but wait, category name needs resolving if we want it? Let's just resolve it simply below.
|
||||
}
|
||||
else {
|
||||
const trans = p.translations.find(t => t.langCode === lang) || p.translations.find(t => t.langCode === 'th') || p.translations[0];
|
||||
const catTrans = p.category.translations?.find((t) => t.langCode === lang) || p.category.translations?.find((t) => t.langCode === 'th') || p.category.translations?.[0];
|
||||
return {
|
||||
...p,
|
||||
name: trans?.name || '',
|
||||
description: trans?.description,
|
||||
details: trans?.details,
|
||||
category: { ...p.category, name: catTrans?.name || p.category.name }
|
||||
};
|
||||
}
|
||||
});
|
||||
mappedProducts.sort((a, b) => {
|
||||
const aName = typeof a.name === 'string' ? a.name : (a.name?.th || '');
|
||||
const bName = typeof b.name === 'string' ? b.name : (b.name?.th || '');
|
||||
return aName.localeCompare(bName);
|
||||
});
|
||||
return {
|
||||
data: mappedProducts,
|
||||
meta: {
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
lastPage: Math.ceil(total / limit),
|
||||
},
|
||||
};
|
||||
})
|
||||
.get("/:id", async ({ params, query, status }) => {
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id))
|
||||
return status(400, { message: "Invalid product ID" });
|
||||
const lang = query.lang || 'th';
|
||||
const product = await prisma.product.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
category: { include: { translations: true } },
|
||||
scent: true,
|
||||
translations: true,
|
||||
variants: {
|
||||
include: {
|
||||
bomItems: { include: { rawMaterial: true } }
|
||||
},
|
||||
orderBy: { price: "asc" }
|
||||
},
|
||||
images: { orderBy: { order: "asc" } },
|
||||
},
|
||||
});
|
||||
if (!product)
|
||||
return status(404, { message: "Product not found" });
|
||||
if (lang === 'all') {
|
||||
const nameObj = {};
|
||||
const descObj = {};
|
||||
const detailsObj = {};
|
||||
product.translations.forEach(t => {
|
||||
nameObj[t.langCode] = t.name;
|
||||
descObj[t.langCode] = t.description;
|
||||
detailsObj[t.langCode] = t.details;
|
||||
});
|
||||
return { ...product, name: nameObj, description: descObj, details: detailsObj };
|
||||
}
|
||||
else {
|
||||
const trans = product.translations.find(t => t.langCode === lang) || product.translations.find(t => t.langCode === 'th') || product.translations[0];
|
||||
const catTrans = product.category.translations?.find((t) => t.langCode === lang) || product.category.translations?.find((t) => t.langCode === 'th') || product.category.translations?.[0];
|
||||
return {
|
||||
...product,
|
||||
name: trans?.name || '',
|
||||
description: trans?.description,
|
||||
details: trans?.details,
|
||||
category: { ...product.category, name: catTrans?.name || product.category.name }
|
||||
};
|
||||
}
|
||||
})
|
||||
.put("/:id", async ({ params, body, status }) => {
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id))
|
||||
return status(400, { message: "Invalid product ID" });
|
||||
const { categoryId, description, details, variants, images } = body;
|
||||
try {
|
||||
const updatedProduct = await prisma.$transaction(async (tx) => {
|
||||
// 1. Update basic product info
|
||||
const prod = await tx.product.update({
|
||||
where: { id },
|
||||
data: { categoryId },
|
||||
});
|
||||
// 2. Sync Variants
|
||||
await tx.productVariant.deleteMany({
|
||||
where: { productId: id },
|
||||
});
|
||||
if (variants && variants.length > 0) {
|
||||
for (const v of variants) {
|
||||
await tx.productVariant.create({
|
||||
data: {
|
||||
productId: id,
|
||||
size: v.size,
|
||||
price: v.price,
|
||||
bomItems: v.bomItems && v.bomItems.length > 0 ? {
|
||||
create: v.bomItems.map((b) => ({
|
||||
rawMaterialId: b.rawMaterialId,
|
||||
quantity: b.quantity,
|
||||
}))
|
||||
} : undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// 3. Sync Images
|
||||
await tx.productImage.deleteMany({
|
||||
where: { productId: id },
|
||||
});
|
||||
if (images && images.length > 0) {
|
||||
for (const [idx, img] of images.entries()) {
|
||||
await tx.productImage.create({
|
||||
data: {
|
||||
productId: id,
|
||||
url: img.url,
|
||||
order: img.order ?? (typeof img.order === 'number' ? img.order : idx),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return prod;
|
||||
});
|
||||
return { success: true, product: updatedProduct };
|
||||
}
|
||||
catch (err) {
|
||||
console.error("Update product error:", err);
|
||||
return status(500, { success: false, message: "Failed to update product" });
|
||||
}
|
||||
}, {
|
||||
body: t.Any(),
|
||||
detail: {
|
||||
summary: "Update Product",
|
||||
tags: ["products"],
|
||||
},
|
||||
})
|
||||
.patch("/:id", async ({ params, body, status }) => {
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id))
|
||||
return status(400, { message: "Invalid product ID" });
|
||||
try {
|
||||
const payload = body;
|
||||
const prodData = {};
|
||||
if (payload.categoryId !== undefined)
|
||||
prodData.categoryId = payload.categoryId;
|
||||
if (payload.scentId !== undefined)
|
||||
prodData.scentId = payload.scentId;
|
||||
if (payload.price !== undefined)
|
||||
prodData.price = payload.price;
|
||||
const prod = await prisma.product.update({
|
||||
where: { id },
|
||||
data: prodData,
|
||||
});
|
||||
// Update translations
|
||||
// If name, description, details are strings, treat as 'th'
|
||||
// If they are objects, iterate over them
|
||||
const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko'];
|
||||
for (const lang of languages) {
|
||||
const updateData = {};
|
||||
let shouldUpdate = false;
|
||||
if (payload.name) {
|
||||
const val = typeof payload.name === 'string' ? (lang === 'th' ? payload.name : null) : payload.name[lang];
|
||||
if (val !== undefined && val !== null) {
|
||||
updateData.name = val;
|
||||
shouldUpdate = true;
|
||||
}
|
||||
}
|
||||
if (payload.description) {
|
||||
const val = typeof payload.description === 'string' ? (lang === 'th' ? payload.description : null) : payload.description[lang];
|
||||
if (val !== undefined && val !== null) {
|
||||
updateData.description = val;
|
||||
shouldUpdate = true;
|
||||
}
|
||||
}
|
||||
if (payload.details) {
|
||||
const val = typeof payload.details === 'string' ? (lang === 'th' ? payload.details : null) : payload.details[lang];
|
||||
if (val !== undefined && val !== null) {
|
||||
updateData.details = val;
|
||||
shouldUpdate = true;
|
||||
}
|
||||
}
|
||||
if (shouldUpdate) {
|
||||
// First check if translation exists to create or update
|
||||
const existing = await prisma.productTranslation.findUnique({
|
||||
where: { productId_langCode: { productId: id, langCode: lang } }
|
||||
});
|
||||
if (existing) {
|
||||
await prisma.productTranslation.update({
|
||||
where: { id: existing.id },
|
||||
data: updateData
|
||||
});
|
||||
}
|
||||
else if (updateData.name) { // name is required for creation
|
||||
await prisma.productTranslation.create({
|
||||
data: {
|
||||
productId: id,
|
||||
langCode: lang,
|
||||
name: updateData.name,
|
||||
description: updateData.description,
|
||||
details: updateData.details
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return { success: true, product: prod };
|
||||
}
|
||||
catch (err) {
|
||||
console.error("Patch product error:", err);
|
||||
return status(500, { success: false, message: "Failed to patch product" });
|
||||
}
|
||||
}, {
|
||||
body: t.Any(),
|
||||
detail: {
|
||||
summary: "Partial Update Product",
|
||||
tags: ["products"],
|
||||
},
|
||||
});
|
||||
//# sourceMappingURL=products.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"products.js","sourceRoot":"","sources":["products.ts"],"names":[],"mappings":"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 truncated
|
||||
Vendored
+211
@@ -0,0 +1,211 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const profileRoute: Elysia<"/profile", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
profile: {
|
||||
":customerId": {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
customerId: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
profile: {
|
||||
":customerId": {
|
||||
put: {
|
||||
body: {
|
||||
email?: string | null;
|
||||
phone?: string | null;
|
||||
};
|
||||
params: {
|
||||
customerId: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
profile: {
|
||||
":customerId": {
|
||||
addresses: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
customerId: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
profile: {
|
||||
":customerId": {
|
||||
addresses: {
|
||||
post: {
|
||||
body: {
|
||||
phone: string;
|
||||
recipientName: string;
|
||||
addressLine: string;
|
||||
subdistrict: string;
|
||||
district: string;
|
||||
province: string;
|
||||
zipcode: string;
|
||||
isDefault: boolean;
|
||||
};
|
||||
params: {
|
||||
customerId: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
profile: {
|
||||
":customerId": {
|
||||
addresses: {
|
||||
":addressId": {
|
||||
put: {
|
||||
body: {
|
||||
phone: string;
|
||||
recipientName: string;
|
||||
addressLine: string;
|
||||
subdistrict: string;
|
||||
district: string;
|
||||
province: string;
|
||||
zipcode: string;
|
||||
isDefault: boolean;
|
||||
};
|
||||
params: {
|
||||
customerId: string;
|
||||
addressId: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
profile: {
|
||||
":customerId": {
|
||||
addresses: {
|
||||
":addressId": {
|
||||
delete: {
|
||||
body: unknown;
|
||||
params: {
|
||||
customerId: string;
|
||||
addressId: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
success: boolean;
|
||||
error?: never;
|
||||
} | {
|
||||
error: any;
|
||||
success?: never;
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
profile: {
|
||||
":customerId": {
|
||||
addresses: {
|
||||
":addressId": {
|
||||
default: {
|
||||
patch: {
|
||||
body: unknown;
|
||||
params: {
|
||||
customerId: string;
|
||||
addressId: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=profile.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAGnC,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8JrB,CAAC"}
|
||||
@@ -0,0 +1,161 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
export const profileRoute = new Elysia({ prefix: "/profile" })
|
||||
.get("/:customerId", async ({ params: { customerId }, set }) => {
|
||||
const customer = await prisma.customer.findUnique({
|
||||
where: { id: Number(customerId) },
|
||||
include: { addresses: true }
|
||||
});
|
||||
if (!customer) {
|
||||
set.status = 404;
|
||||
return { error: "Customer not found" };
|
||||
}
|
||||
return customer;
|
||||
})
|
||||
.put("/:customerId", async ({ params: { customerId }, body, set }) => {
|
||||
try {
|
||||
const { email, phone } = body;
|
||||
const customer = await prisma.customer.update({
|
||||
where: { id: Number(customerId) },
|
||||
data: {
|
||||
email: email || null,
|
||||
phone: phone || null
|
||||
}
|
||||
});
|
||||
return customer;
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: err.message || "Failed to update customer profile" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
email: t.Optional(t.Nullable(t.String())),
|
||||
phone: t.Optional(t.Nullable(t.String()))
|
||||
})
|
||||
})
|
||||
.get("/:customerId/addresses", async ({ params: { customerId } }) => {
|
||||
return await prisma.customerAddress.findMany({
|
||||
where: { customerId: Number(customerId) },
|
||||
orderBy: { isDefault: 'desc' }
|
||||
});
|
||||
})
|
||||
.post("/:customerId/addresses", async ({ params: { customerId }, body, set }) => {
|
||||
try {
|
||||
const { recipientName, phone, addressLine, subdistrict, district, province, zipcode, isDefault } = body;
|
||||
const cId = Number(customerId);
|
||||
// If isDefault is true, unset other defaults first
|
||||
if (isDefault) {
|
||||
await prisma.customerAddress.updateMany({
|
||||
where: { customerId: cId },
|
||||
data: { isDefault: false }
|
||||
});
|
||||
}
|
||||
const address = await prisma.customerAddress.create({
|
||||
data: {
|
||||
customerId: cId,
|
||||
recipientName,
|
||||
phone,
|
||||
addressLine,
|
||||
subdistrict,
|
||||
district,
|
||||
province,
|
||||
zipcode,
|
||||
isDefault: !!isDefault
|
||||
}
|
||||
});
|
||||
return address;
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: err.message || "Failed to create address" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
recipientName: t.String(),
|
||||
phone: t.String(),
|
||||
addressLine: t.String(),
|
||||
subdistrict: t.String(),
|
||||
district: t.String(),
|
||||
province: t.String(),
|
||||
zipcode: t.String(),
|
||||
isDefault: t.Boolean()
|
||||
})
|
||||
})
|
||||
.put("/:customerId/addresses/:addressId", async ({ params: { customerId, addressId }, body, set }) => {
|
||||
try {
|
||||
const { recipientName, phone, addressLine, subdistrict, district, province, zipcode, isDefault } = body;
|
||||
const cId = Number(customerId);
|
||||
const aId = Number(addressId);
|
||||
// If setting as default, unset other defaults first
|
||||
if (isDefault) {
|
||||
await prisma.customerAddress.updateMany({
|
||||
where: { customerId: cId },
|
||||
data: { isDefault: false }
|
||||
});
|
||||
}
|
||||
const address = await prisma.customerAddress.update({
|
||||
where: { id: aId },
|
||||
data: {
|
||||
recipientName,
|
||||
phone,
|
||||
addressLine,
|
||||
subdistrict,
|
||||
district,
|
||||
province,
|
||||
zipcode,
|
||||
isDefault: !!isDefault
|
||||
}
|
||||
});
|
||||
return address;
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: err.message || "Failed to update address" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
recipientName: t.String(),
|
||||
phone: t.String(),
|
||||
addressLine: t.String(),
|
||||
subdistrict: t.String(),
|
||||
district: t.String(),
|
||||
province: t.String(),
|
||||
zipcode: t.String(),
|
||||
isDefault: t.Boolean()
|
||||
})
|
||||
})
|
||||
.delete("/:customerId/addresses/:addressId", async ({ params: { addressId }, set }) => {
|
||||
try {
|
||||
await prisma.customerAddress.delete({
|
||||
where: { id: Number(addressId) }
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: err.message || "Failed to delete address" };
|
||||
}
|
||||
})
|
||||
.patch("/:customerId/addresses/:addressId/default", async ({ params: { customerId, addressId }, set }) => {
|
||||
try {
|
||||
const cId = Number(customerId);
|
||||
const aId = Number(addressId);
|
||||
// Unset other defaults
|
||||
await prisma.customerAddress.updateMany({
|
||||
where: { customerId: cId },
|
||||
data: { isDefault: false }
|
||||
});
|
||||
// Set this one as default
|
||||
const address = await prisma.customerAddress.update({
|
||||
where: { id: aId },
|
||||
data: { isDefault: true }
|
||||
});
|
||||
return address;
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: err.message || "Failed to set default address" };
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=profile.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"profile.js","sourceRoot":"","sources":["profile.ts"],"names":[],"mappings":"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 truncated
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const promotionsRoute: Elysia<"/promotions", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
promotions: {
|
||||
active: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
promotions: {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
promotions: {
|
||||
post: {
|
||||
body: {
|
||||
startDate?: string | null;
|
||||
endDate?: string | null;
|
||||
name: string;
|
||||
type: string;
|
||||
isActive: boolean;
|
||||
value: number;
|
||||
minPurchase: number;
|
||||
level: number;
|
||||
};
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
promotions: {
|
||||
":id": {
|
||||
put: {
|
||||
body: {
|
||||
startDate?: string | null;
|
||||
endDate?: string | null;
|
||||
name: string;
|
||||
type: string;
|
||||
isActive: boolean;
|
||||
value: number;
|
||||
minPurchase: number;
|
||||
level: number;
|
||||
};
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
promotions: {
|
||||
":id": {
|
||||
delete: {
|
||||
body: unknown;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
success: boolean;
|
||||
error?: never;
|
||||
} | {
|
||||
error: string;
|
||||
success?: never;
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=promotions.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"promotions.d.ts","sourceRoot":"","sources":["promotions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAGnC,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2ExB,CAAC"}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
export const promotionsRoute = new Elysia({ prefix: "/promotions" })
|
||||
.get("/active", async () => {
|
||||
const now = new Date();
|
||||
return await prisma.promotion.findMany({
|
||||
where: {
|
||||
isActive: true,
|
||||
OR: [
|
||||
{ startDate: null },
|
||||
{ startDate: { lte: now } }
|
||||
],
|
||||
AND: [
|
||||
{ OR: [{ endDate: null }, { endDate: { gte: now } }] }
|
||||
]
|
||||
},
|
||||
orderBy: { level: 'asc' }
|
||||
});
|
||||
})
|
||||
.get("/", async () => {
|
||||
return await prisma.promotion.findMany({
|
||||
orderBy: [{ level: 'asc' }, { createdAt: 'desc' }]
|
||||
});
|
||||
})
|
||||
.post("/", async ({ body, set }) => {
|
||||
try {
|
||||
const data = body;
|
||||
return await prisma.promotion.create({ data });
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to create promotion" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
level: t.Number(),
|
||||
type: t.String(),
|
||||
value: t.Number(),
|
||||
minPurchase: t.Number(),
|
||||
isActive: t.Boolean(),
|
||||
startDate: t.Optional(t.Nullable(t.String())),
|
||||
endDate: t.Optional(t.Nullable(t.String()))
|
||||
})
|
||||
})
|
||||
.put("/:id", async ({ params: { id }, body, set }) => {
|
||||
try {
|
||||
const data = body;
|
||||
return await prisma.promotion.update({
|
||||
where: { id: Number(id) },
|
||||
data
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to update promotion" };
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
level: t.Number(),
|
||||
type: t.String(),
|
||||
value: t.Number(),
|
||||
minPurchase: t.Number(),
|
||||
isActive: t.Boolean(),
|
||||
startDate: t.Optional(t.Nullable(t.String())),
|
||||
endDate: t.Optional(t.Nullable(t.String()))
|
||||
})
|
||||
})
|
||||
.delete("/:id", async ({ params: { id }, set }) => {
|
||||
try {
|
||||
await prisma.promotion.delete({
|
||||
where: { id: Number(id) }
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
catch (err) {
|
||||
set.status = 500;
|
||||
return { error: "Failed to delete promotion" };
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=promotions.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"promotions.js","sourceRoot":"","sources":["promotions.ts"],"names":[],"mappings":"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"}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const rawMaterialsRoute: Elysia<"/raw-materials", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
"raw-materials": {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"raw-materials": {
|
||||
":id": {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"raw-materials": {
|
||||
post: {
|
||||
body: {
|
||||
name: string;
|
||||
unit: string;
|
||||
pricePerUnit: number;
|
||||
};
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
201: any;
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
500: {
|
||||
readonly message: "Failed to create raw material";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"raw-materials": {
|
||||
":id": {
|
||||
put: {
|
||||
body: {
|
||||
name: string;
|
||||
unit: string;
|
||||
pricePerUnit: number;
|
||||
};
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"raw-materials": {
|
||||
":id": {
|
||||
delete: {
|
||||
body: unknown;
|
||||
params: {
|
||||
id: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
200: {
|
||||
success: boolean;
|
||||
};
|
||||
400: {
|
||||
readonly message: "Invalid ID";
|
||||
};
|
||||
422: {
|
||||
type: "validation";
|
||||
on: string;
|
||||
summary?: string;
|
||||
message?: string;
|
||||
found?: unknown;
|
||||
property?: string;
|
||||
expected?: string;
|
||||
};
|
||||
500: {
|
||||
readonly message: "Failed to delete raw material";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=raw-materials.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"raw-materials.d.ts","sourceRoot":"","sources":["raw-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAGnC,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyE1B,CAAC"}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
export const rawMaterialsRoute = new Elysia({ prefix: "/raw-materials" })
|
||||
.get("/", async () => {
|
||||
const rawMaterials = await prisma.rawMaterial.findMany({
|
||||
orderBy: { name: "asc" },
|
||||
});
|
||||
return rawMaterials;
|
||||
})
|
||||
.get("/:id", async ({ params, status }) => {
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id))
|
||||
return status(400, { message: "Invalid ID" });
|
||||
const rawMaterial = await prisma.rawMaterial.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
if (!rawMaterial)
|
||||
return status(404, { message: "Raw material not found" });
|
||||
return rawMaterial;
|
||||
})
|
||||
.post("/", async ({ body, status }) => {
|
||||
try {
|
||||
const rawMaterial = await prisma.rawMaterial.create({
|
||||
data: body,
|
||||
});
|
||||
return status(201, rawMaterial);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return status(500, { message: "Failed to create raw material" });
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
unit: t.String(),
|
||||
pricePerUnit: t.Number(),
|
||||
}),
|
||||
detail: { tags: ["raw-materials"] }
|
||||
})
|
||||
.put("/:id", async ({ params, body, status }) => {
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id))
|
||||
return status(400, { message: "Invalid ID" });
|
||||
try {
|
||||
const rawMaterial = await prisma.rawMaterial.update({
|
||||
where: { id },
|
||||
data: body,
|
||||
});
|
||||
return rawMaterial;
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return status(500, { message: "Failed to update raw material" });
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
unit: t.String(),
|
||||
pricePerUnit: t.Number(),
|
||||
}),
|
||||
detail: { tags: ["raw-materials"] }
|
||||
})
|
||||
.delete("/:id", async ({ params, status }) => {
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id))
|
||||
return status(400, { message: "Invalid ID" });
|
||||
try {
|
||||
await prisma.rawMaterial.delete({
|
||||
where: { id },
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return status(500, { message: "Failed to delete raw material" });
|
||||
}
|
||||
}, {
|
||||
detail: { tags: ["raw-materials"] }
|
||||
});
|
||||
//# sourceMappingURL=raw-materials.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"raw-materials.js","sourceRoot":"","sources":["raw-materials.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
import { Elysia } from "elysia";
|
||||
export declare const reviewsRoute: Elysia<"/reviews", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
response: {};
|
||||
}, {
|
||||
reviews: {
|
||||
product: {
|
||||
":productId": {
|
||||
get: {
|
||||
body: unknown;
|
||||
params: {
|
||||
productId: string;
|
||||
} & {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} & {
|
||||
reviews: {
|
||||
post: {
|
||||
body: {
|
||||
customerName?: string;
|
||||
customerId?: number | null;
|
||||
comment?: string;
|
||||
productId: number;
|
||||
rating: number;
|
||||
};
|
||||
params: {};
|
||||
query: unknown;
|
||||
headers: unknown;
|
||||
response: {
|
||||
[x: string]: any;
|
||||
[x: number]: any;
|
||||
[x: symbol]: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
response: {};
|
||||
}>;
|
||||
//# sourceMappingURL=reviews.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"reviews.d.ts","sourceRoot":"","sources":["reviews.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAGnC,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6CrB,CAAC"}
|
||||
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user