diff --git a/apps/backend/prisma.config.d.ts b/apps/backend/prisma.config.d.ts new file mode 100644 index 0000000..6febae4 --- /dev/null +++ b/apps/backend/prisma.config.d.ts @@ -0,0 +1,4 @@ +import "dotenv/config"; +declare const _default: import("prisma/config").PrismaConfigInternal; +export default _default; +//# sourceMappingURL=prisma.config.d.ts.map \ No newline at end of file diff --git a/apps/backend/prisma.config.d.ts.map b/apps/backend/prisma.config.d.ts.map new file mode 100644 index 0000000..66c4a58 --- /dev/null +++ b/apps/backend/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"} \ No newline at end of file diff --git a/apps/backend/prisma.config.js b/apps/backend/prisma.config.js new file mode 100644 index 0000000..15c851b --- /dev/null +++ b/apps/backend/prisma.config.js @@ -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 \ No newline at end of file diff --git a/apps/backend/prisma.config.js.map b/apps/backend/prisma.config.js.map new file mode 100644 index 0000000..3384319 --- /dev/null +++ b/apps/backend/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"} \ No newline at end of file diff --git a/apps/backend/prisma/migrations/20260614041441_fix_missing_production_tables/migration.sql b/apps/backend/prisma/migrations/20260614041441_fix_missing_production_tables/migration.sql new file mode 100644 index 0000000..601dcdc --- /dev/null +++ b/apps/backend/prisma/migrations/20260614041441_fix_missing_production_tables/migration.sql @@ -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; diff --git a/apps/backend/prisma/schema.prisma b/apps/backend/prisma/schema.prisma index 8d85e79..b734a8a 100644 --- a/apps/backend/prisma/schema.prisma +++ b/apps/backend/prisma/schema.prisma @@ -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[] diff --git a/apps/backend/prisma/seed-pages.d.ts b/apps/backend/prisma/seed-pages.d.ts new file mode 100644 index 0000000..38c399b --- /dev/null +++ b/apps/backend/prisma/seed-pages.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=seed-pages.d.ts.map \ No newline at end of file diff --git a/apps/backend/prisma/seed-pages.d.ts.map b/apps/backend/prisma/seed-pages.d.ts.map new file mode 100644 index 0000000..ea6c642 --- /dev/null +++ b/apps/backend/prisma/seed-pages.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"seed-pages.d.ts","sourceRoot":"","sources":["seed-pages.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/apps/backend/prisma/seed-pages.js b/apps/backend/prisma/seed-pages.js new file mode 100644 index 0000000..0c1f29f --- /dev/null +++ b/apps/backend/prisma/seed-pages.js @@ -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 \ No newline at end of file diff --git a/apps/backend/prisma/seed-pages.js.map b/apps/backend/prisma/seed-pages.js.map new file mode 100644 index 0000000..ddd407a --- /dev/null +++ b/apps/backend/prisma/seed-pages.js.map @@ -0,0 +1 @@ +{"version":3,"file":"seed-pages.js","sourceRoot":"","sources":["seed-pages.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/scripts/migrate-i18n.d.ts b/apps/backend/scripts/migrate-i18n.d.ts new file mode 100644 index 0000000..9876ce5 --- /dev/null +++ b/apps/backend/scripts/migrate-i18n.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=migrate-i18n.d.ts.map \ No newline at end of file diff --git a/apps/backend/scripts/migrate-i18n.d.ts.map b/apps/backend/scripts/migrate-i18n.d.ts.map new file mode 100644 index 0000000..ddf2d38 --- /dev/null +++ b/apps/backend/scripts/migrate-i18n.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"migrate-i18n.d.ts","sourceRoot":"","sources":["migrate-i18n.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/apps/backend/scripts/migrate-i18n.js b/apps/backend/scripts/migrate-i18n.js new file mode 100644 index 0000000..a09ebac --- /dev/null +++ b/apps/backend/scripts/migrate-i18n.js @@ -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 \ No newline at end of file diff --git a/apps/backend/scripts/migrate-i18n.js.map b/apps/backend/scripts/migrate-i18n.js.map new file mode 100644 index 0000000..ddc7fd9 --- /dev/null +++ b/apps/backend/scripts/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"} \ No newline at end of file diff --git a/apps/backend/src/index.d.ts b/apps/backend/src/index.d.ts new file mode 100644 index 0000000..e26a57a --- /dev/null +++ b/apps/backend/src/index.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/apps/backend/src/index.d.ts.map b/apps/backend/src/index.d.ts.map new file mode 100644 index 0000000..82335e7 --- /dev/null +++ b/apps/backend/src/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/apps/backend/src/index.js b/apps/backend/src/index.js new file mode 100644 index 0000000..ca6d184 --- /dev/null +++ b/apps/backend/src/index.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/index.js.map b/apps/backend/src/index.js.map new file mode 100644 index 0000000..c905022 --- /dev/null +++ b/apps/backend/src/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/lib/lineClient.d.ts b/apps/backend/src/lib/lineClient.d.ts new file mode 100644 index 0000000..275d0c5 --- /dev/null +++ b/apps/backend/src/lib/lineClient.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/lib/lineClient.d.ts.map b/apps/backend/src/lib/lineClient.d.ts.map new file mode 100644 index 0000000..a4067ce --- /dev/null +++ b/apps/backend/src/lib/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"} \ No newline at end of file diff --git a/apps/backend/src/lib/lineClient.js b/apps/backend/src/lib/lineClient.js new file mode 100644 index 0000000..1b18695 --- /dev/null +++ b/apps/backend/src/lib/lineClient.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/lib/lineClient.js.map b/apps/backend/src/lib/lineClient.js.map new file mode 100644 index 0000000..9dd7b7d --- /dev/null +++ b/apps/backend/src/lib/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"} \ No newline at end of file diff --git a/apps/backend/src/lib/notifications.d.ts b/apps/backend/src/lib/notifications.d.ts new file mode 100644 index 0000000..13a3877 --- /dev/null +++ b/apps/backend/src/lib/notifications.d.ts @@ -0,0 +1,6 @@ +export declare function sendOrderCreatedNotification(orderId: number): Promise; +export declare function sendPaymentConfirmedNotification(orderId: number): Promise; +export declare function sendOrderShippedNotification(orderId: number): Promise; +export declare function sendOrderCancelledNotification(orderId: number): Promise; +export declare function markOrderAsPaid(orderId: number): Promise; +//# sourceMappingURL=notifications.d.ts.map \ No newline at end of file diff --git a/apps/backend/src/lib/notifications.d.ts.map b/apps/backend/src/lib/notifications.d.ts.map new file mode 100644 index 0000000..81d82fc --- /dev/null +++ b/apps/backend/src/lib/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"} \ No newline at end of file diff --git a/apps/backend/src/lib/notifications.js b/apps/backend/src/lib/notifications.js new file mode 100644 index 0000000..79a7040 --- /dev/null +++ b/apps/backend/src/lib/notifications.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/lib/notifications.js.map b/apps/backend/src/lib/notifications.js.map new file mode 100644 index 0000000..9be74fd --- /dev/null +++ b/apps/backend/src/lib/notifications.js.map @@ -0,0 +1 @@ +{"version":3,"file":"notifications.js","sourceRoot":"","sources":["notifications.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/lib/prisma.d.ts b/apps/backend/src/lib/prisma.d.ts new file mode 100644 index 0000000..16cc61f --- /dev/null +++ b/apps/backend/src/lib/prisma.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/lib/prisma.d.ts.map b/apps/backend/src/lib/prisma.d.ts.map new file mode 100644 index 0000000..9829588 --- /dev/null +++ b/apps/backend/src/lib/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"} \ No newline at end of file diff --git a/apps/backend/src/lib/prisma.js b/apps/backend/src/lib/prisma.js new file mode 100644 index 0000000..562154f --- /dev/null +++ b/apps/backend/src/lib/prisma.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/lib/prisma.js.map b/apps/backend/src/lib/prisma.js.map new file mode 100644 index 0000000..beaea23 --- /dev/null +++ b/apps/backend/src/lib/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"} \ No newline at end of file diff --git a/apps/backend/src/middleware/auth.js b/apps/backend/src/middleware/auth.js new file mode 100644 index 0000000..71eca8b --- /dev/null +++ b/apps/backend/src/middleware/auth.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/middleware/auth.js.map b/apps/backend/src/middleware/auth.js.map new file mode 100644 index 0000000..e8bd83d --- /dev/null +++ b/apps/backend/src/middleware/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/articles.d.ts b/apps/backend/src/routes/articles.d.ts new file mode 100644 index 0000000..3b7c7a5 --- /dev/null +++ b/apps/backend/src/routes/articles.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/articles.d.ts.map b/apps/backend/src/routes/articles.d.ts.map new file mode 100644 index 0000000..a3b8d37 --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/articles.js b/apps/backend/src/routes/articles.js new file mode 100644 index 0000000..49eaf69 --- /dev/null +++ b/apps/backend/src/routes/articles.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/articles.js.map b/apps/backend/src/routes/articles.js.map new file mode 100644 index 0000000..533137a --- /dev/null +++ b/apps/backend/src/routes/articles.js.map @@ -0,0 +1 @@ +{"version":3,"file":"articles.js","sourceRoot":"","sources":["articles.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/auth.js b/apps/backend/src/routes/auth.js new file mode 100644 index 0000000..e1e4f66 --- /dev/null +++ b/apps/backend/src/routes/auth.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/auth.js.map b/apps/backend/src/routes/auth.js.map new file mode 100644 index 0000000..66f5523 --- /dev/null +++ b/apps/backend/src/routes/auth.js.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.js","sourceRoot":"","sources":["auth.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/categories.d.ts b/apps/backend/src/routes/categories.d.ts new file mode 100644 index 0000000..9a1883f --- /dev/null +++ b/apps/backend/src/routes/categories.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/categories.d.ts.map b/apps/backend/src/routes/categories.d.ts.map new file mode 100644 index 0000000..e5f2e90 --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/categories.js b/apps/backend/src/routes/categories.js new file mode 100644 index 0000000..2fdf265 --- /dev/null +++ b/apps/backend/src/routes/categories.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/categories.js.map b/apps/backend/src/routes/categories.js.map new file mode 100644 index 0000000..07eaecb --- /dev/null +++ b/apps/backend/src/routes/categories.js.map @@ -0,0 +1 @@ +{"version":3,"file":"categories.js","sourceRoot":"","sources":["categories.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/coupons.d.ts b/apps/backend/src/routes/coupons.d.ts new file mode 100644 index 0000000..5f2b5c7 --- /dev/null +++ b/apps/backend/src/routes/coupons.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/coupons.d.ts.map b/apps/backend/src/routes/coupons.d.ts.map new file mode 100644 index 0000000..283a33f --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/coupons.js b/apps/backend/src/routes/coupons.js new file mode 100644 index 0000000..81c61b8 --- /dev/null +++ b/apps/backend/src/routes/coupons.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/coupons.js.map b/apps/backend/src/routes/coupons.js.map new file mode 100644 index 0000000..5a96d44 --- /dev/null +++ b/apps/backend/src/routes/coupons.js.map @@ -0,0 +1 @@ +{"version":3,"file":"coupons.js","sourceRoot":"","sources":["coupons.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/customers.d.ts b/apps/backend/src/routes/customers.d.ts new file mode 100644 index 0000000..535c9b6 --- /dev/null +++ b/apps/backend/src/routes/customers.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/customers.d.ts.map b/apps/backend/src/routes/customers.d.ts.map new file mode 100644 index 0000000..8aef8fb --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/customers.js b/apps/backend/src/routes/customers.js new file mode 100644 index 0000000..182eea1 --- /dev/null +++ b/apps/backend/src/routes/customers.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/customers.js.map b/apps/backend/src/routes/customers.js.map new file mode 100644 index 0000000..ae17789 --- /dev/null +++ b/apps/backend/src/routes/customers.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customers.js","sourceRoot":"","sources":["customers.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/hero.d.ts b/apps/backend/src/routes/hero.d.ts new file mode 100644 index 0000000..8dfeab6 --- /dev/null +++ b/apps/backend/src/routes/hero.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/hero.d.ts.map b/apps/backend/src/routes/hero.d.ts.map new file mode 100644 index 0000000..cfb3982 --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/hero.js b/apps/backend/src/routes/hero.js new file mode 100644 index 0000000..c903ec7 --- /dev/null +++ b/apps/backend/src/routes/hero.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/hero.js.map b/apps/backend/src/routes/hero.js.map new file mode 100644 index 0000000..fb12a76 --- /dev/null +++ b/apps/backend/src/routes/hero.js.map @@ -0,0 +1 @@ +{"version":3,"file":"hero.js","sourceRoot":"","sources":["hero.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/lazadaOrder.d.ts b/apps/backend/src/routes/lazadaOrder.d.ts new file mode 100644 index 0000000..19b7379 --- /dev/null +++ b/apps/backend/src/routes/lazadaOrder.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/lazadaOrder.d.ts.map b/apps/backend/src/routes/lazadaOrder.d.ts.map new file mode 100644 index 0000000..0d8fec7 --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/lazadaOrder.js b/apps/backend/src/routes/lazadaOrder.js new file mode 100644 index 0000000..c7c229a --- /dev/null +++ b/apps/backend/src/routes/lazadaOrder.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/lazadaOrder.js.map b/apps/backend/src/routes/lazadaOrder.js.map new file mode 100644 index 0000000..080faaf --- /dev/null +++ b/apps/backend/src/routes/lazadaOrder.js.map @@ -0,0 +1 @@ +{"version":3,"file":"lazadaOrder.js","sourceRoot":"","sources":["lazadaOrder.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/line.d.ts b/apps/backend/src/routes/line.d.ts new file mode 100644 index 0000000..981fa71 --- /dev/null +++ b/apps/backend/src/routes/line.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/line.d.ts.map b/apps/backend/src/routes/line.d.ts.map new file mode 100644 index 0000000..8304b45 --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/line.js b/apps/backend/src/routes/line.js new file mode 100644 index 0000000..dffda6b --- /dev/null +++ b/apps/backend/src/routes/line.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/line.js.map b/apps/backend/src/routes/line.js.map new file mode 100644 index 0000000..4ab6112 --- /dev/null +++ b/apps/backend/src/routes/line.js.map @@ -0,0 +1 @@ +{"version":3,"file":"line.js","sourceRoot":"","sources":["line.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/menus.d.ts b/apps/backend/src/routes/menus.d.ts new file mode 100644 index 0000000..470bc47 --- /dev/null +++ b/apps/backend/src/routes/menus.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/menus.d.ts.map b/apps/backend/src/routes/menus.d.ts.map new file mode 100644 index 0000000..1b2540c --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/menus.js b/apps/backend/src/routes/menus.js new file mode 100644 index 0000000..6516db2 --- /dev/null +++ b/apps/backend/src/routes/menus.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/menus.js.map b/apps/backend/src/routes/menus.js.map new file mode 100644 index 0000000..8004760 --- /dev/null +++ b/apps/backend/src/routes/menus.js.map @@ -0,0 +1 @@ +{"version":3,"file":"menus.js","sourceRoot":"","sources":["menus.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/newsletter.d.ts b/apps/backend/src/routes/newsletter.d.ts new file mode 100644 index 0000000..07b88f1 --- /dev/null +++ b/apps/backend/src/routes/newsletter.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/newsletter.d.ts.map b/apps/backend/src/routes/newsletter.d.ts.map new file mode 100644 index 0000000..fefd670 --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/newsletter.js b/apps/backend/src/routes/newsletter.js new file mode 100644 index 0000000..9131460 --- /dev/null +++ b/apps/backend/src/routes/newsletter.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/newsletter.js.map b/apps/backend/src/routes/newsletter.js.map new file mode 100644 index 0000000..e22203f --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/orders.d.ts b/apps/backend/src/routes/orders.d.ts new file mode 100644 index 0000000..21abf9e --- /dev/null +++ b/apps/backend/src/routes/orders.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/orders.d.ts.map b/apps/backend/src/routes/orders.d.ts.map new file mode 100644 index 0000000..c6e5e66 --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/orders.js b/apps/backend/src/routes/orders.js new file mode 100644 index 0000000..f0ebdf7 --- /dev/null +++ b/apps/backend/src/routes/orders.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/orders.js.map b/apps/backend/src/routes/orders.js.map new file mode 100644 index 0000000..a37974f --- /dev/null +++ b/apps/backend/src/routes/orders.js.map @@ -0,0 +1 @@ +{"version":3,"file":"orders.js","sourceRoot":"","sources":["orders.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/pages.d.ts b/apps/backend/src/routes/pages.d.ts new file mode 100644 index 0000000..47a44cf --- /dev/null +++ b/apps/backend/src/routes/pages.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/pages.d.ts.map b/apps/backend/src/routes/pages.d.ts.map new file mode 100644 index 0000000..efaaa3c --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/pages.js b/apps/backend/src/routes/pages.js new file mode 100644 index 0000000..6b7dd24 --- /dev/null +++ b/apps/backend/src/routes/pages.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/pages.js.map b/apps/backend/src/routes/pages.js.map new file mode 100644 index 0000000..a7f45eb --- /dev/null +++ b/apps/backend/src/routes/pages.js.map @@ -0,0 +1 @@ +{"version":3,"file":"pages.js","sourceRoot":"","sources":["pages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,MAAM,MAAM,eAAe,CAAC;AACnC,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,gCAAgC,EAChC,yBAAyB,EACzB,4BAA4B,EAC7B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,MAAM,cAAc,GAAG;IACrB,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ;IACvE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa;IAC1E,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW;IACpE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK;CACnC,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;KACvD,GAAG,CAAC,GAAG,EAAE,KAAK,IAAI,EAAE;IACnB,OAAO,MAAM,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;QAChC,OAAO,EAAE;YACP,QAAQ,EAAE;gBACR,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;aAC1B;SACF;KACF,CAAC,CAAC;AACL,CAAC,CAAC;KACD,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE;IACxD,MAAM,eAAe,GAAG,MAAM,CAAE,KAAa,EAAE,eAAe,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;IAC/F,MAAM,IAAI,GAAI,KAAa,EAAE,IAAI,IAAI,IAAI,CAAC;IAC1C,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;QACxC,KAAK,EAAE,EAAE,IAAI,EAAE;QACf,OAAO,EAAE;YACP,QAAQ,EAAE;gBACR,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE;gBACvD,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;aAC1B;SACF;KACF,CAAC,CAAC;IACH,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;IACrC,CAAC;IAED,MAAM,iBAAiB,GAAG,MAAM,OAAO,CAAC,GAAG,CACzC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QAClC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAc,CAAC;QACvC,IAAI,CAAC,OAAO;YAAE,OAAO,OAAO,CAAC;QAE7B,MAAM,gBAAgB,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;QAExC,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAChC,IAAI,QAAQ,GAAU,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;YAEjC,MAAM,cAAc,GAAG;gBACrB,QAAQ,EAAE,IAAI;gBACd,KAAK,EAAE,IAAI;gBACX,YAAY,EAAE,IAAI;gBAClB,QAAQ,EAAE;oBACR,OAAO,EAAE;wBACP,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE;qBAC7C;oBACD,OAAO,EAAE,EAAE,KAAK,EAAE,KAAc,EAAE;iBACnC;gBACD,MAAM,EAAE;oBACN,OAAO,EAAE,EAAE,KAAK,EAAE,KAAc,EAAE;iBACnC;aACF,CAAC;YAEF,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBAChC,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;oBACvC,KAAK,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE;oBACxB,OAAO,EAAE,cAAc;oBACvB,IAAI,EAAE,KAAK;iBACZ,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBACvD,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;oBACvC,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;oBAC3C,OAAO,EAAE,cAAc;oBACvB,IAAI,EAAE,KAAK;iBACZ,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC1E,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC3C,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;oBACvC,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE;oBAC1B,OAAO,EAAE,cAAc;iBACxB,CAAC,CAAC;gBACH,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACjE,CAAC;YAED,gBAAgB,CAAC,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE;gBAClD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;oBACnB,MAAM,OAAO,GAAQ,EAAE,CAAC;oBACxB,MAAM,OAAO,GAAQ,EAAE,CAAC;oBACxB,MAAM,UAAU,GAAQ,EAAE,CAAC;oBAC3B,CAAC,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAM,EAAE,EAAE;wBAChC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;wBAC7B,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC;wBACpC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;oBACtC,CAAC,CAAC,CAAC;oBACH,OAAO,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;gBAC5E,CAAC;qBAAM,CAAC;oBACN,MAAM,KAAK,GAAG,CAAC,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC;oBACpJ,OAAO,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,CAAC,KAAK,EAAE,IAAI,IAAI,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;gBACtH,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,OAAO,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACzC,IAAI,UAAU,GAAU,EAAE,CAAC;YAC3B,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACzE,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC5C,UAAU,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;oBAC1C,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE;oBAC1B,OAAO,EAAE;wBACP,MAAM,EAAE;4BACN,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;yBAC3B;qBACF;iBACF,CAAC,CAAC;gBACH,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACnE,CAAC;iBAAM,CAAC;gBACN,UAAU,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;oBAC1C,OAAO,EAAE;wBACP,MAAM,EAAE;4BACN,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;yBAC3B;qBACF;iBACF,CAAC,CAAC;YACL,CAAC;YACD,gBAAgB,CAAC,UAAU,GAAG,UAAU,CAAC;QAC3C,CAAC;QAED,OAAO;YACL,GAAG,OAAO;YACV,OAAO,EAAE,gBAAgB;SAC1B,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;IAEF,OAAO;QACL,GAAG,IAAI;QACP,QAAQ,EAAE,iBAAiB;KAC5B,CAAC;AACJ,CAAC,CAAC;KACD,GAAG,CAAC,iBAAiB,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;IAC1D,IAAI,CAAC;QACH,OAAO,MAAM,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC;YACvC,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE;YAC/B,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;SAC1B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAC;IACpD,CAAC;AACH,CAAC,CAAC;KACD,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE;IAC3C,IAAI,CAAC;QACH,MAAM,EAAE,KAAK,EAAE,GAAG,IAAyB,CAAC;QAC5C,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACnC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;YACjB,OAAO,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC;QAC5C,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,MAAM,CAAC;YACrD,KAAK,EAAE,EAAE,KAAK,EAAE;YAChB,MAAM,EAAE,EAAE;YACV,MAAM,EAAE,EAAE,KAAK,EAAE;SAClB,CAAC,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IACtC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,GAAG,CAAC,CAAC;QAC1C,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,EAAE;KAClB,CAAC;CACH,CAAC;KACD,GAAG,CAAC,SAAS,CAAC;KACd,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE;IACjC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAW,CAAC;QACzB,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;QAEhE,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAC5E,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;YACjB,OAAO,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC;QACnD,CAAC;QAED,IAAI,gBAAgB,GAAG,IAAI,CAAC;QAC5B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,gBAAgB,GAAG,MAAM,KAAK,WAAW,CAAC;QAC5C,CAAC;aAAM,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YACrC,gBAAgB,GAAG,WAAW,CAAC;QACjC,CAAC;QAED,IAAI,EAAE,EAAE,CAAC;YACP,OAAO,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;gBAC9B,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE;gBACzB,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE;aACrD,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,OAAO,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;gBAC9B,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE;aACrD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;QACrC,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,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAC1B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;QACjB,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAC9B,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;KAC9B,CAAC;CACH,CAAC;KACD,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;IACpD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;YACvB,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE;SAC5B,CAAC,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC;IAC5C,CAAC;AACH,CAAC,CAAC;KACD,IAAI,CAAC,iBAAiB,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE;IACjE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC5B,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAW,CAAC;QAEvD,MAAM,eAAe,GAAG,sBAAsB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC9D,IAAI,eAAe,EAAE,CAAC;YACpB,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;YACjB,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;QACpC,CAAC;QAED,IAAI,UAAU,GAAG,KAAK,CAAC;QACvB,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC;gBACrD,KAAK,EAAE,EAAE,MAAM,EAAE;gBACjB,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;aAC3B,CAAC,CAAC;YACH,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvD,CAAC;QAED,OAAO,MAAM,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC;YACrC,IAAI,EAAE;gBACJ,MAAM;gBACN,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE;gBACxB,KAAK,EAAE,UAAU;gBACjB,OAAO;gBACP,QAAQ,EAAE,QAAQ,KAAK,KAAK;aAC7B;SACF,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;QAC5C,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC;IAC5C,CAAC;AACH,CAAC,EAAE;IACD,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACb,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAC7B,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE;QAChB,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;KAClC,CAAC;CACH,CAAC;KACD,GAAG,CAAC,sBAAsB,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE;IAC1E,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QAC7B,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAW,CAAC;QAEvD,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;YACpB,MAAM,eAAe,GAAG,sBAAsB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC9D,IAAI,eAAe,EAAE,CAAC;gBACpB,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;gBACjB,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;YACpC,CAAC;QACH,CAAC;QAED,OAAO,MAAM,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC;YACrC,KAAK,EAAE,EAAE,EAAE,EAAE;YACb,IAAI,EAAE;gBACJ,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS;gBAC3C,KAAK;gBACL,OAAO;gBACP,QAAQ;aACT;SACF,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,GAAG,CAAC,CAAC;QACnD,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC;IAC/C,CAAC;AACH,CAAC,EAAE;IACD,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACb,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAC5B,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAC7B,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;QAC5B,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;KAClC,CAAC;CACH,CAAC;KACD,GAAG,CAAC,yBAAyB,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE;IACxE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC5B,MAAM,EAAE,aAAa,EAAE,GAAG,IAA0D,CAAC;QAErF,MAAM,MAAM,CAAC,YAAY,CACvB,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACzB,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC;YACxB,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE;YAC9B,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE;SAC5B,CAAC,CACH,CACF,CAAC;QACF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,GAAG,CAAC,CAAC;QACpD,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAC;IACjD,CAAC;AACH,CAAC,EAAE;IACD,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACb,aAAa,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;YAC9B,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;YACd,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;SAClB,CAAC,CAAC;KACJ,CAAC;CACH,CAAC;KACD,MAAM,CAAC,sBAAsB,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;IACvE,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC;YAC9B,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE;SACjC,CAAC,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC;IAC/C,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,SAAS,sBAAsB,CAAC,IAAY,EAAE,OAAY;IACxD,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5C,OAAO,+BAA+B,CAAC;IACzC,CAAC;IAED,QAAQ,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QAC3B,KAAK,MAAM;YACT,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE;gBAAE,OAAO,oDAAoD,CAAC;YAC5H,IAAI,OAAO,OAAO,CAAC,WAAW,KAAK,QAAQ;gBAAE,OAAO,gDAAgD,CAAC;YACrG,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE;gBAAE,OAAO,uDAAuD,CAAC;YACrI,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ;gBAAE,OAAO,4CAA4C,CAAC;YAC7F,IAAI,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ;gBAAE,OAAO,+CAA+C,CAAC;YACnG,IAAI,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ;gBAAE,OAAO,+CAA+C,CAAC;YACnG,MAAM;QACR,KAAK,YAAY;YACf,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE;gBAAE,OAAO,0DAA0D,CAAC;YAClI,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ;gBAAE,OAAO,2BAA2B,CAAC;YAC/G,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC7E,OAAO,yCAAyC,CAAC;YACnD,CAAC;YACD,MAAM;QACR,KAAK,UAAU;YACb,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE;gBAAE,OAAO,wDAAwD,CAAC;YAChI,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ;gBAAE,OAAO,2BAA2B,CAAC;YAC/G,IAAI,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5D,OAAO,wDAAwD,CAAC;YAClE,CAAC;YACD,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;gBACpE,OAAO,mDAAmD,CAAC;YAC7D,CAAC;YACD,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBACpE,OAAO,yDAAyD,CAAC;YACnE,CAAC;YACD,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACrE,OAAO,wBAAwB,CAAC;YAClC,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnF,OAAO,qCAAqC,CAAC;YAC/C,CAAC;YACD,IAAI,OAAO,CAAC,eAAe,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;gBAC1F,OAAO,mCAAmC,CAAC;YAC7C,CAAC;YACD,MAAM;QACR,KAAK,QAAQ;YACX,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE;gBAAE,OAAO,yDAAyD,CAAC;YACvI,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ;gBAAE,OAAO,0BAA0B,CAAC;YAC5G,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ;gBAAE,OAAO,wBAAwB,CAAC;YACtG,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,WAAW,KAAK,QAAQ;gBAAE,OAAO,8BAA8B,CAAC;YACxH,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ;gBAAE,OAAO,6BAA6B,CAAC;YACrH,MAAM;QACR,KAAK,MAAM;YACT,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE;gBAAE,OAAO,sDAAsD,CAAC;YAClI,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ;gBAAE,OAAO,wBAAwB,CAAC;YACtG,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;gBAChG,OAAO,gDAAgD,CAAC;YAC1D,CAAC;YACD,MAAM;QACR,KAAK,kBAAkB;YACrB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,8CAA8C,CAAC;YACzH,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;gBAAE,OAAO,4BAA4B,CAAC;YAChG,MAAM;QACR,KAAK,YAAY;YACf,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE;gBAAE,OAAO,0DAA0D,CAAC;YAClI,IAAI,OAAO,OAAO,CAAC,WAAW,KAAK,QAAQ;gBAAE,OAAO,yCAAyC,CAAC;YAC9F,IAAI,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE;gBAAE,OAAO,kDAAkD,CAAC;YACpI,IAAI,OAAO,OAAO,CAAC,mBAAmB,KAAK,SAAS;gBAAE,OAAO,uCAAuC,CAAC;YACrG,MAAM;QACR,KAAK,gBAAgB;YACnB,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE;gBAAE,OAAO,8DAA8D,CAAC;YACtI,IAAI,OAAO,OAAO,CAAC,WAAW,KAAK,QAAQ;gBAAE,OAAO,6CAA6C,CAAC;YAClG,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE;gBAAE,OAAO,oDAAoD,CAAC;YAClI,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,aAAa,CAAC;gBAAE,OAAO,yCAAyC,CAAC;YACzG,MAAM;QACR,KAAK,oBAAoB;YACvB,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE;gBAAE,OAAO,kEAAkE,CAAC;YAC1I,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,gDAAgD,CAAC;YAC3H,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;gBACpD,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE;oBAAE,OAAO,UAAU,GAAG,GAAC,CAAC,iCAAiC,CAAC;gBACpH,IAAI,OAAO,KAAK,CAAC,WAAW,KAAK,QAAQ;oBAAE,OAAO,UAAU,GAAG,GAAC,CAAC,oCAAoC,CAAC;gBACtG,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE;oBAAE,OAAO,UAAU,GAAG,GAAC,CAAC,oCAAoC,CAAC;gBAC7H,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;oBAAE,OAAO,UAAU,GAAG,GAAC,CAAC,iCAAiC,CAAC;YAClG,CAAC;YACD,MAAM;QACR,KAAK,cAAc,CAAC,CAAC,CAAC;YACpB,MAAM,GAAG,GAAG,wBAAwB,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YACxD,IAAI,CAAC,GAAG,CAAC,OAAO;gBAAE,OAAO,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnG,MAAM;QACR,CAAC;QACD,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,GAAG,GAAG,qBAAqB,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YACrD,IAAI,CAAC,GAAG,CAAC,OAAO;gBAAE,OAAO,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnG,MAAM;QACR,CAAC;QACD,KAAK,uBAAuB,CAAC,CAAC,CAAC;YAC7B,MAAM,GAAG,GAAG,gCAAgC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAChE,IAAI,CAAC,GAAG,CAAC,OAAO;gBAAE,OAAO,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnG,MAAM;QACR,CAAC;QACD,KAAK,eAAe,CAAC,CAAC,CAAC;YACrB,MAAM,GAAG,GAAG,yBAAyB,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YACzD,IAAI,CAAC,GAAG,CAAC,OAAO;gBAAE,OAAO,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnG,MAAM;QACR,CAAC;QACD,KAAK,kBAAkB,CAAC,CAAC,CAAC;YACxB,MAAM,GAAG,GAAG,4BAA4B,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAC5D,IAAI,CAAC,GAAG,CAAC,OAAO;gBAAE,OAAO,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnG,MAAM;QACR,CAAC;QACD;YACE,OAAO,yBAAyB,IAAI,EAAE,CAAC;IAC3C,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"} \ No newline at end of file diff --git a/apps/backend/src/routes/payments.d.ts b/apps/backend/src/routes/payments.d.ts new file mode 100644 index 0000000..9f30023 --- /dev/null +++ b/apps/backend/src/routes/payments.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/payments.d.ts.map b/apps/backend/src/routes/payments.d.ts.map new file mode 100644 index 0000000..7bded86 --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/payments.js b/apps/backend/src/routes/payments.js new file mode 100644 index 0000000..6cc89ad --- /dev/null +++ b/apps/backend/src/routes/payments.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/payments.js.map b/apps/backend/src/routes/payments.js.map new file mode 100644 index 0000000..a587dab --- /dev/null +++ b/apps/backend/src/routes/payments.js.map @@ -0,0 +1 @@ +{"version":3,"file":"payments.js","sourceRoot":"","sources":["payments.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/products.d.ts b/apps/backend/src/routes/products.d.ts new file mode 100644 index 0000000..8c26082 --- /dev/null +++ b/apps/backend/src/routes/products.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/products.d.ts.map b/apps/backend/src/routes/products.d.ts.map new file mode 100644 index 0000000..a1b386c --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/products.js b/apps/backend/src/routes/products.js new file mode 100644 index 0000000..1fd8db6 --- /dev/null +++ b/apps/backend/src/routes/products.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/products.js.map b/apps/backend/src/routes/products.js.map new file mode 100644 index 0000000..6809c28 --- /dev/null +++ b/apps/backend/src/routes/products.js.map @@ -0,0 +1 @@ +{"version":3,"file":"products.js","sourceRoot":"","sources":["products.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/profile.d.ts b/apps/backend/src/routes/profile.d.ts new file mode 100644 index 0000000..e5cd4b5 --- /dev/null +++ b/apps/backend/src/routes/profile.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/profile.d.ts.map b/apps/backend/src/routes/profile.d.ts.map new file mode 100644 index 0000000..e09888a --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/profile.js b/apps/backend/src/routes/profile.js new file mode 100644 index 0000000..35c3aa4 --- /dev/null +++ b/apps/backend/src/routes/profile.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/profile.js.map b/apps/backend/src/routes/profile.js.map new file mode 100644 index 0000000..7e5124f --- /dev/null +++ b/apps/backend/src/routes/profile.js.map @@ -0,0 +1 @@ +{"version":3,"file":"profile.js","sourceRoot":"","sources":["profile.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/promotions.d.ts b/apps/backend/src/routes/promotions.d.ts new file mode 100644 index 0000000..a0b546b --- /dev/null +++ b/apps/backend/src/routes/promotions.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/promotions.d.ts.map b/apps/backend/src/routes/promotions.d.ts.map new file mode 100644 index 0000000..c9f2e78 --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/promotions.js b/apps/backend/src/routes/promotions.js new file mode 100644 index 0000000..d360bdc --- /dev/null +++ b/apps/backend/src/routes/promotions.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/promotions.js.map b/apps/backend/src/routes/promotions.js.map new file mode 100644 index 0000000..7b08b12 --- /dev/null +++ b/apps/backend/src/routes/promotions.js.map @@ -0,0 +1 @@ +{"version":3,"file":"promotions.js","sourceRoot":"","sources":["promotions.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/raw-materials.d.ts b/apps/backend/src/routes/raw-materials.d.ts new file mode 100644 index 0000000..f1e7837 --- /dev/null +++ b/apps/backend/src/routes/raw-materials.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/raw-materials.d.ts.map b/apps/backend/src/routes/raw-materials.d.ts.map new file mode 100644 index 0000000..3cebc82 --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/raw-materials.js b/apps/backend/src/routes/raw-materials.js new file mode 100644 index 0000000..64335a1 --- /dev/null +++ b/apps/backend/src/routes/raw-materials.js @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/raw-materials.js.map b/apps/backend/src/routes/raw-materials.js.map new file mode 100644 index 0000000..799f3bb --- /dev/null +++ b/apps/backend/src/routes/raw-materials.js.map @@ -0,0 +1 @@ +{"version":3,"file":"raw-materials.js","sourceRoot":"","sources":["raw-materials.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/reviews.d.ts b/apps/backend/src/routes/reviews.d.ts new file mode 100644 index 0000000..4eec656 --- /dev/null +++ b/apps/backend/src/routes/reviews.d.ts @@ -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 \ No newline at end of file diff --git a/apps/backend/src/routes/reviews.d.ts.map b/apps/backend/src/routes/reviews.d.ts.map new file mode 100644 index 0000000..10b6d0d --- /dev/null +++ b/apps/backend/src/routes/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"} \ No newline at end of file diff --git a/apps/backend/src/routes/reviews.js b/apps/backend/src/routes/reviews.js new file mode 100644 index 0000000..22f617b --- /dev/null +++ b/apps/backend/src/routes/reviews.js @@ -0,0 +1,50 @@ +import { Elysia, t } from "elysia"; +import prisma from "../lib/prisma"; +export const reviewsRoute = new Elysia({ prefix: "/reviews" }) + .get("/product/:productId", async ({ params: { productId } }) => { + return await prisma.review.findMany({ + where: { + productId: Number(productId), + isPublished: true + }, + orderBy: { createdAt: 'desc' }, + include: { + customer: { + select: { + displayName: true, + pictureUrl: true + } + } + } + }); +}) + .post("/", async ({ body, set }) => { + try { + const data = body; + const review = await prisma.review.create({ + data: { + rating: data.rating, + comment: data.comment, + productId: data.productId, + customerId: data.customerId || null, + customerName: data.customerName || "Anonymous", + isPublished: true + } + }); + return review; + } + catch (err) { + console.error("[Reviews] Error:", err); + set.status = 500; + return { error: "Failed to create review" }; + } +}, { + body: t.Object({ + rating: t.Number(), + comment: t.Optional(t.String()), + productId: t.Number(), + customerId: t.Optional(t.Nullable(t.Number())), + customerName: t.Optional(t.String()) + }) +}); +//# sourceMappingURL=reviews.js.map \ No newline at end of file diff --git a/apps/backend/src/routes/reviews.js.map b/apps/backend/src/routes/reviews.js.map new file mode 100644 index 0000000..c697abc --- /dev/null +++ b/apps/backend/src/routes/reviews.js.map @@ -0,0 +1 @@ +{"version":3,"file":"reviews.js","sourceRoot":"","sources":["reviews.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,MAAM,MAAM,eAAe,CAAC;AAEnC,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;KAC3D,GAAG,CAAC,qBAAqB,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,EAAE;IAC9D,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;QAClC,KAAK,EAAE;YACL,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC;YAC5B,WAAW,EAAE,IAAI;SAClB;QACD,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;QAC9B,OAAO,EAAE;YACP,QAAQ,EAAE;gBACR,MAAM,EAAE;oBACN,WAAW,EAAE,IAAI;oBACjB,UAAU,EAAE,IAAI;iBACjB;aACF;SACF;KACF,CAAC,CAAC;AACL,CAAC,CAAC;KACD,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE;IACjC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAW,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;YACxC,IAAI,EAAE;gBACJ,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;gBACnC,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,WAAW;gBAC9C,WAAW,EAAE,IAAI;aAClB;SACF,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;QACvC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC;IAC9C,CAAC;AACH,CAAC,EAAE;IACD,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACb,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;QAClB,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAC/B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;QACrB,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9C,YAAY,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;KACrC,CAAC;CACH,CAAC,CAAC"} \ No newline at end of file diff --git a/apps/backend/src/routes/settings.d.ts b/apps/backend/src/routes/settings.d.ts new file mode 100644 index 0000000..e869526 --- /dev/null +++ b/apps/backend/src/routes/settings.d.ts @@ -0,0 +1,34 @@ +import { Elysia } from "elysia"; +export declare const settingsRoute: Elysia<"/settings", { + decorator: any; + store: any; + derive: any; + resolve: any; +}, any, any, { + settings: { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + [x: string]: any; + [x: number]: any; + [x: symbol]: any; + }; + }; + }; +} & { + settings: any; +} & { + settings: { + put: any; + }; +}, { + derive: {}; + resolve: {}; + schema: {}; + standaloneSchema: {}; + response: {}; +}, any>; +//# sourceMappingURL=settings.d.ts.map \ No newline at end of file diff --git a/apps/backend/src/routes/settings.d.ts.map b/apps/backend/src/routes/settings.d.ts.map new file mode 100644 index 0000000..555b79a --- /dev/null +++ b/apps/backend/src/routes/settings.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAInC,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmDtB,CAAC"} \ No newline at end of file diff --git a/apps/backend/src/routes/settings.js b/apps/backend/src/routes/settings.js new file mode 100644 index 0000000..7a7557e --- /dev/null +++ b/apps/backend/src/routes/settings.js @@ -0,0 +1,59 @@ +import { Elysia, t } from "elysia"; +import prisma from "../lib/prisma"; +import { adminOnly } from "../middleware/auth"; +export const settingsRoute = new Elysia({ prefix: "/settings" }) + .get("/", async ({ set }) => { + try { + let settings = await prisma.storeSettings.findFirst(); + if (!settings) { + settings = await prisma.storeSettings.create({ + data: {} + }); + } + return settings; + } + catch (err) { + console.error("[Settings] Error:", err); + set.status = 500; + return { error: "Failed to fetch settings" }; + } +}) + .use(adminOnly) + .put("/", async ({ body, set }) => { + try { + const data = body; + let settings = await prisma.storeSettings.findFirst(); + if (!settings) { + settings = await prisma.storeSettings.create({ data }); + } + else { + settings = await prisma.storeSettings.update({ + where: { id: settings.id }, + data + }); + } + return settings; + } + catch (err) { + console.error("[Settings] Error:", err); + set.status = 500; + return { error: "Failed to update settings" }; + } +}, { + body: t.Object({ + shippingFee: t.Number(), + bankName: t.String(), + bankAccountName: t.String(), + bankAccountNumber: t.String(), + pointsEarnRate: t.Number(), + facebookUrl: t.Optional(t.String()), + instagramUrl: t.Optional(t.String()), + tiktokUrl: t.Optional(t.String()), + footerDescription: t.Optional(t.String()), + newsletterTitle: t.Optional(t.String()), + newsletterDescription: t.Optional(t.String()), + newsletterPlaceholder: t.Optional(t.String()), + newsletterButtonText: t.Optional(t.String()) + }) +}); +//# sourceMappingURL=settings.js.map \ No newline at end of file diff --git a/apps/backend/src/routes/settings.js.map b/apps/backend/src/routes/settings.js.map new file mode 100644 index 0000000..0e71fd8 --- /dev/null +++ b/apps/backend/src/routes/settings.js.map @@ -0,0 +1 @@ +{"version":3,"file":"settings.js","sourceRoot":"","sources":["settings.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/shopee.d.ts b/apps/backend/src/routes/shopee.d.ts new file mode 100644 index 0000000..a56119e --- /dev/null +++ b/apps/backend/src/routes/shopee.d.ts @@ -0,0 +1,228 @@ +import { Elysia } from "elysia"; +export declare const shopeeRoute: Elysia<"/shopee", { + decorator: {}; + store: {}; + derive: {}; + resolve: {}; +}, { + typebox: {}; + error: {}; +}, { + schema: {}; + standaloneSchema: {}; + macro: {}; + macroFn: {}; + parser: {}; + response: {}; +}, { + shopee: { + import: { + orders: { + 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; + }; + }; + }; + }; + }; + }; +} & { + shopee: { + orders: { + ":orderId": { + get: { + body: unknown; + params: { + orderId: string; + } & {}; + query: unknown; + headers: unknown; + response: { + [x: string]: any; + [x: number]: any; + [x: symbol]: any; + }; + }; + }; + }; + }; +} & { + shopee: { + orders: { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + items: any; + total: any; + page: number; + limit: number; + totalPages: number; + }; + }; + }; + }; + }; +} & { + shopee: { + analytics: { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + monthly: { + month: string; + revenue: number; + orders: number; + commission: number; + cancelled: number; + items: number; + }[]; + statusBreakdown: { + status: string; + count: number; + }[]; + paymentBreakdown: { + count: number; + revenue: number; + method: string; + }[]; + topProducts: { + name: string; + variant: string; + qty: number; + revenue: number; + orders: number; + }[]; + summary: { + totalRevenue: any; + totalCommission: any; + totalShipping: any; + netRevenue: number; + totalOrders: any; + completedOrders: any; + cancelledOrders: any; + cancelRate: number; + aov: number; + commissionRate: number; + }; + }; + }; + }; + }; + }; +} & { + shopee: { + "analytics-overview": { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + byShop: { + net: number; + cancelRate: number; + aov: number; + shop: string; + orders: number; + completed: number; + cancelled: number; + revenue: number; + commission: number; + shippingFee: number; + items: number; + }[]; + monthly: any[]; + topProducts: { + name: string; + variant: string; + qty: number; + revenue: number; + }[]; + shops: string[]; + totals: { + totalRevenue: any; + totalCommission: any; + totalShipping: any; + netRevenue: number; + totalOrders: any; + totalCancelled: any; + cancelRate: number; + aov: number; + commissionRate: number; + }; + }; + }; + }; + }; + }; +} & { + shopee: { + summary: { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + orderCount: any; + totalAmount: any; + buyerPaidAmount: any; + totalCommission: any; + totalShipping: any; + totalItems: any; + totalItemRevenue: any; + }; + }; + }; + }; + }; +}, { + derive: {}; + resolve: {}; + schema: {}; + standaloneSchema: {}; + response: {}; +}, { + derive: {}; + resolve: {}; + schema: {}; + standaloneSchema: {}; + response: {}; +}>; +//# sourceMappingURL=shopee.d.ts.map \ No newline at end of file diff --git a/apps/backend/src/routes/shopee.d.ts.map b/apps/backend/src/routes/shopee.d.ts.map new file mode 100644 index 0000000..5ff506b --- /dev/null +++ b/apps/backend/src/routes/shopee.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"shopee.d.ts","sourceRoot":"","sources":["shopee.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAUnC,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCA4HsB,MAAM;qCAAW,MAAM;oCAAU,MAAM;wCAAc,MAAM;uCAAa,MAAM;mCAAS,MAAM;;;;;;;mCA2B3F,MAAM;qCAAW,MAAM;;;;kCAoBxB,MAAM;qCAAW,MAAM;iCAAO,MAAM;qCAAW,MAAM;oCAAU,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAyDxG,MAAM;oCAAU,MAAM;uCAAa,MAAM;uCAAa,MAAM;qCACzD,MAAM;wCAAc,MAAM;yCAAe,MAAM;mCAAS,MAAM;;;;kCAuD9B,MAAM;qCAAW,MAAM;iCAAO,MAAM;qCAAW,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgDhG,CAAC"} \ No newline at end of file diff --git a/apps/backend/src/routes/shopee.js b/apps/backend/src/routes/shopee.js new file mode 100644 index 0000000..9ac811d --- /dev/null +++ b/apps/backend/src/routes/shopee.js @@ -0,0 +1,331 @@ +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; +}; +export const shopeeRoute = new Elysia({ prefix: "/shopee" }) + // ── Import Orders ───────────────────────────────────────────────────────── + .post("/import/orders", async ({ body, set }) => { + try { + const { rows, shop } = body; + const shopName = shop || "hom-aroma"; + console.log(`[Shopee] Received ${rows.length} raw rows for shop: ${shopName}`); + const orderMap = new Map(); + for (const row of rows) { + const orderId = String(row["ΰΈ«ΰΈ‘ΰΈ²ΰΈ’ΰΉ€ΰΈ₯ΰΈ‚ΰΈ„ΰΈ³ΰΈͺΰΈ±ΰΉˆΰΈ‡ΰΈ‹ΰΈ·ΰΉ‰ΰΈ­"] || "").trim(); + if (!orderId) + continue; + if (!orderMap.has(orderId)) + orderMap.set(orderId, { meta: row, items: [] }); + orderMap.get(orderId).items.push(row); + } + console.log(`[Shopee] Grouped into ${orderMap.size} unique orders`); + const CHUNK_SIZE = 30; + const entries = Array.from(orderMap.entries()); + let upserted = 0; + for (let i = 0; i < entries.length; i += CHUNK_SIZE) { + const chunk = entries.slice(i, i + CHUNK_SIZE); + await prisma.$transaction(async (tx) => { + for (const [orderId, { meta, items }] of chunk) { + await tx.shopeeOrderItem.deleteMany({ where: { orderId } }); + const itemsData = items.map((item) => ({ + parentSku: String(item["ΰΉ€ΰΈ₯ΰΈ‚ΰΈ­ΰΉ‰ΰΈ²ΰΈ‡ΰΈ­ΰΈ΄ΰΈ‡ Parent SKU"] || ""), + productName: String(item["ชื่อΰΈͺΰΈ΄ΰΈ™ΰΈ„ΰΉ‰ΰΈ²"] || ""), + skuRef: String(item["ΰΉ€ΰΈ₯ΰΈ‚ΰΈ­ΰΉ‰ΰΈ²ΰΈ‡ΰΈ­ΰΈ΄ΰΈ‡ SKU (SKU Reference No.)"] || ""), + variantName: String(item["ΰΈŠΰΈ·ΰΉˆΰΈ­ΰΈ•ΰΈ±ΰΈ§ΰΉ€ΰΈ₯ือก"] || ""), + originalPrice: toNum(item["ΰΈ£ΰΈ²ΰΈ„ΰΈ²ΰΈ•ΰΈ±ΰΉ‰ΰΈ‡ΰΈ•ΰΉ‰ΰΈ™"]), + dealPrice: toNum(item["ΰΈ£ΰΈ²ΰΈ„ΰΈ²ΰΈ‚ΰΈ²ΰΈ’"]), + quantity: Math.max(1, Math.round(toNum(item["ΰΈˆΰΈ³ΰΈ™ΰΈ§ΰΈ™"]))), + totalPayment: toNum(item["ΰΈ’ΰΈ­ΰΈ”ΰΈŠΰΈ³ΰΈ£ΰΈ°ΰΉ€ΰΈ‡ΰΈ΄ΰΈ™"]), + rawData: item, + })); + const orderData = { + shop: shopName, + status: String(meta["ΰΈͺถานะการΰΈͺΰΈ±ΰΉˆΰΈ‡ΰΈ‹ΰΈ·ΰΉ‰ΰΈ­"] || ""), + buyerUsername: String(meta["ΰΈŠΰΈ·ΰΉˆΰΈ­ΰΈœΰΈΉΰΉ‰ΰΉƒΰΈŠΰΉ‰ (ΰΈœΰΈΉΰΉ‰ΰΈ‹ΰΈ·ΰΉ‰ΰΈ­)"] || ""), + orderCreatedAt: String(meta["ΰΈ§ΰΈ±ΰΈ™ΰΈ—ΰΈ΅ΰΉˆΰΈ—ΰΈ³ΰΈΰΈ²ΰΈ£ΰΈͺΰΈ±ΰΉˆΰΈ‡ΰΈ‹ΰΈ·ΰΉ‰ΰΈ­"] || ""), + paymentTime: String(meta["ΰΉ€ΰΈ§ΰΈ₯าการชำระΰΈͺΰΈ΄ΰΈ™ΰΈ„ΰΉ‰ΰΈ²"] || ""), + paymentMethod: String(meta["ΰΈŠΰΉˆΰΈ­ΰΈ‡ΰΈ—ΰΈ²ΰΈ‡ΰΈΰΈ²ΰΈ£ΰΈŠΰΈ³ΰΈ£ΰΈ°ΰΉ€ΰΈ‡ΰΈ΄ΰΈ™"] || ""), + trackingNo: String(meta["*ΰΈ«ΰΈ‘ΰΈ²ΰΈ’ΰΉ€ΰΈ₯ΰΈ‚ΰΈ•ΰΈ΄ΰΈ”ΰΈ•ΰΈ²ΰΈ‘ΰΈžΰΈ±ΰΈͺΰΈ”ΰΈΈ"] || ""), + totalAmount: toNum(meta["ΰΈˆΰΈ³ΰΈ™ΰΈ§ΰΈ™ΰΉ€ΰΈ‡ΰΈ΄ΰΈ™ΰΈ—ΰΈ±ΰΉ‰ΰΈ‡ΰΈ«ΰΈ‘ΰΈ”"]), + buyerPaidAmount: toNum(meta["ΰΈ£ΰΈ²ΰΈ„ΰΈ²ΰΈͺΰΈ΄ΰΈ™ΰΈ„ΰΉ‰ΰΈ²ΰΈ—ΰΈ΅ΰΉˆΰΈŠΰΈ³ΰΈ£ΰΈ°ΰΉ‚ΰΈ”ΰΈ’ΰΈœΰΈΉΰΉ‰ΰΈ‹ΰΈ·ΰΉ‰ΰΈ­ (THB)"]), + shippingFee: toNum(meta["ΰΈ„ΰΉˆΰΈ²ΰΈˆΰΈ±ΰΈ”ΰΈͺΰΉˆΰΈ‡ΰΈ—ΰΈ΅ΰΉˆΰΈŠΰΈ³ΰΈ£ΰΈ°ΰΉ‚ΰΈ”ΰΈ’ΰΈœΰΈΉΰΉ‰ΰΈ‹ΰΈ·ΰΉ‰ΰΈ­"]), + commission: toNum(meta["ΰΈ„ΰΉˆΰΈ²ΰΈ„ΰΈ­ΰΈ‘ΰΈ‘ΰΈ΄ΰΈŠΰΈŠΰΈ±ΰΉˆΰΈ™"]), + completedAt: String(meta["ΰΉ€ΰΈ§ΰΈ₯ΰΈ²ΰΈ—ΰΈ΅ΰΉˆΰΈ—ΰΈ³ΰΈΰΈ²ΰΈ£ΰΈͺΰΈ±ΰΉˆΰΈ‡ΰΈ‹ΰΈ·ΰΉ‰ΰΈ­ΰΈͺΰΈ³ΰΉ€ΰΈ£ΰΉ‡ΰΈˆ"] || ""), + }; + await tx.shopeeOrder.upsert({ + where: { orderId }, + update: { ...orderData, items: { create: itemsData } }, + create: { orderId, ...orderData, items: { create: itemsData } }, + }); + upserted++; + } + }, { timeout: 30000 }); + console.log(`[Shopee] Progress: ${Math.min(i + CHUNK_SIZE, entries.length)}/${entries.length}`); + } + console.log(`[Shopee] βœ… Done β€” ${upserted} orders upserted`); + return { success: true, message: `ΰΈ™ΰΈ³ΰΉ€ΰΈ‚ΰΉ‰ΰΈ²ΰΈͺΰΈ³ΰΉ€ΰΈ£ΰΉ‡ΰΈˆ ${upserted} ΰΈ„ΰΈ³ΰΈͺΰΈ±ΰΉˆΰΈ‡ΰΈ‹ΰΈ·ΰΉ‰ΰΈ­ (จาก ${rows.length} แถว)`, ordersCount: upserted, rowsCount: rows.length }; + } + catch (error) { + console.error(`[Shopee] ❌ 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()) }) }) + // ── GET Single Order ────────────────────────────────────────────────────── + .get("/orders/:orderId", async ({ params, set }) => { + const { orderId } = params; + const order = await prisma.shopeeOrder.findUnique({ where: { orderId }, 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.shopeeOrder.findMany({ where, include: { items: true }, orderBy: { orderCreatedAt: "desc" }, skip, take: limit }), + prisma.shopeeOrder.count({ where }), + ]); + return { items, total, page, limit, totalPages: Math.ceil(total / limit) }; +}) + // ── GET Analytics (with date range filter) ─────────────────────────────── + .get("/analytics", async ({ query }) => { + const shop = query.shop || "hom-aroma"; + const dateFrom = query.dateFrom; // "YYYY-MM-DD" + const dateTo = query.dateTo; + // Build where β€” orderCreatedAt stored as "YYYY-MM-DD HH:mm" string + const where = { shop }; + if (dateFrom || dateTo) { + where.orderCreatedAt = {}; + if (dateFrom) + where.orderCreatedAt.gte = dateFrom; + if (dateTo) + where.orderCreatedAt.lte = dateTo + " 23:59"; + } + const allOrders = await prisma.shopeeOrder.findMany({ + where, + select: { + orderId: true, status: true, orderCreatedAt: true, + totalAmount: true, buyerPaidAmount: true, commission: true, + shippingFee: true, paymentMethod: true, + items: { select: { quantity: true, totalPayment: true } }, + }, + }); + // Monthly aggregation + const monthMap = new Map(); + for (const o of allOrders) { + const month = (o.orderCreatedAt ?? "").slice(0, 7) || "Unknown"; + if (!monthMap.has(month)) + monthMap.set(month, { month, revenue: 0, orders: 0, commission: 0, cancelled: 0, items: 0 }); + const m = monthMap.get(month); + m.orders += 1; + m.revenue += o.totalAmount; + m.commission += o.commission; + if (o.status.includes("ฒกเΰΈ₯ิก")) + m.cancelled += 1; + m.items += o.items.reduce((s, i) => s + i.quantity, 0); + } + 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) { + let status = o.status; + if (status.includes("ΰΈͺΰΈ³ΰΉ€ΰΈ£ΰΉ‡ΰΈˆΰΉΰΈ₯ΰΉ‰ΰΈ§")) + status = "ΰΈͺΰΈ³ΰΉ€ΰΈ£ΰΉ‡ΰΈˆΰΉΰΈ₯ΰΉ‰ΰΈ§"; + else if (status.includes("ΰΉ„ΰΈ”ΰΉ‰ΰΈ£ΰΈ±ΰΈšΰΈͺินค้าแΰΈ₯ΰΉ‰ΰΈ§")) + status = "ΰΉ„ΰΈ”ΰΉ‰ΰΈ£ΰΈ±ΰΈšΰΈͺินค้าแΰΈ₯ΰΉ‰ΰΈ§"; + else if (status.includes("ฒกเΰΈ₯ิก")) + status = "ฒกเΰΈ₯ิก"; + statusMap.set(status, (statusMap.get(status) ?? 0) + 1); + } + const statusBreakdown = Array.from(statusMap.entries()) + .map(([status, count]) => ({ status, count })) + .sort((a, b) => b.count - a.count); + // Payment method breakdown + const paymentMap = new Map(); + for (const o of allOrders) { + const key = o.paymentMethod || "Unknown"; + if (!paymentMap.has(key)) + paymentMap.set(key, { count: 0, revenue: 0 }); + const p = paymentMap.get(key); + p.count += 1; + p.revenue += o.buyerPaidAmount; + } + const paymentBreakdown = Array.from(paymentMap.entries()) + .map(([method, v]) => ({ method, ...v })) + .sort((a, b) => b.count - a.count); + // Top products (from filtered order IDs) + const filteredOrderIds = allOrders.map(o => o.orderId); + const allItems = filteredOrderIds.length > 0 + ? await prisma.shopeeOrderItem.findMany({ + where: { orderId: { in: filteredOrderIds } }, + select: { productName: true, variantName: true, quantity: true, totalPayment: true, dealPrice: true }, + }) + : []; + const productMap = new Map(); + for (const item of allItems) { + const key = `${item.productName}__${item.variantName ?? ""}`; + if (!productMap.has(key)) + productMap.set(key, { name: item.productName, variant: item.variantName ?? "", qty: 0, revenue: 0, orders: 0 }); + const p = productMap.get(key); + p.qty += item.quantity; + p.revenue += item.totalPayment; + p.orders += 1; + } + 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.totalAmount, 0); + const totalCommission = allOrders.reduce((s, o) => s + o.commission, 0); + const totalShipping = allOrders.reduce((s, o) => s + o.shippingFee, 0); + const totalOrders = allOrders.length; + const completedOrders = allOrders.filter(o => !o.status.includes("ฒกเΰΈ₯ิก")).length; + const cancelledOrders = allOrders.filter(o => o.status.includes("ฒกเΰΈ₯ิก")).length; + const cancelRate = totalOrders > 0 ? (cancelledOrders / totalOrders) * 100 : 0; + const netRevenue = totalRevenue - totalCommission - totalShipping; + const aov = completedOrders > 0 ? totalRevenue / completedOrders : 0; + return { + monthly, statusBreakdown, paymentBreakdown, topProducts, + summary: { + totalRevenue, totalCommission, totalShipping, netRevenue, + totalOrders, completedOrders, cancelledOrders, cancelRate, aov, + commissionRate: totalRevenue > 0 ? (totalCommission / totalRevenue) * 100 : 0, + }, + }; +}) + // ── 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.orderCreatedAt = {}; + if (dateFrom) + where.orderCreatedAt.gte = dateFrom; + if (dateTo) + where.orderCreatedAt.lte = dateTo + " 23:59"; + } + const allOrders = await prisma.shopeeOrder.findMany({ + where, + select: { + orderId: true, shop: true, status: true, orderCreatedAt: true, + totalAmount: true, buyerPaidAmount: true, commission: true, + shippingFee: true, + items: { select: { quantity: true, totalPayment: 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, commission: 0, shippingFee: 0, items: 0 }); + } + const s = shopMap.get(o.shop); + s.orders += 1; + s.revenue += o.totalAmount; + s.commission += o.commission; + s.shippingFee += o.shippingFee; + s.items += o.items.reduce((acc, i) => acc + i.quantity, 0); + if (o.status.includes("ฒกเΰΈ₯ิก")) + s.cancelled += 1; + else + s.completed += 1; + } + const byShop = Array.from(shopMap.values()).map(s => ({ + ...s, + net: s.revenue - s.commission - s.shippingFee, + cancelRate: s.orders > 0 ? (s.cancelled / s.orders) * 100 : 0, + aov: s.completed > 0 ? s.revenue / s.completed : 0, + })).sort((a, b) => b.revenue - a.revenue); + // Monthly stacked by shop + const monthShopMap = new Map(); + for (const o of allOrders) { + const month = (o.orderCreatedAt ?? "").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, commission: 0 }); + const ms = sm.get(o.shop); + ms.revenue += o.totalAmount; + ms.orders += 1; + ms.commission += o.commission; + } + const shops = Array.from(shopMap.keys()); + const monthly = Array.from(monthShopMap.entries()) + .map(([month, sm]) => { + const row = { month }; + for (const shop of shops) { + const d = sm.get(shop) ?? { revenue: 0, orders: 0, commission: 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); + return row; + }) + .sort((a, b) => a.month.localeCompare(b.month)); + // Top products across all shops (filtered by date range via orderId) + const filteredOrderIds = allOrders.map(o => o.orderId); + const allItems = filteredOrderIds.length > 0 ? await prisma.shopeeOrderItem.findMany({ + where: { orderId: { in: filteredOrderIds } }, + select: { productName: true, variantName: true, quantity: true, totalPayment: true, order: { select: { shop: true } } }, + }) : []; + const productMap = new Map(); + for (const item of allItems) { + const key = `${item.productName}__${item.variantName ?? ""}`; + if (!productMap.has(key)) + productMap.set(key, { name: item.productName, variant: item.variantName ?? "", qty: 0, revenue: 0 }); + const p = productMap.get(key); + p.qty += item.quantity; + p.revenue += item.totalPayment; + } + const topProducts = Array.from(productMap.values()).sort((a, b) => b.revenue - a.revenue).slice(0, 10); + // Grand totals + const totalRevenue = allOrders.reduce((s, o) => s + o.totalAmount, 0); + const totalCommission = allOrders.reduce((s, o) => s + o.commission, 0); + const totalShipping = allOrders.reduce((s, o) => s + o.shippingFee, 0); + const totalOrders = allOrders.length; + const totalCancelled = allOrders.filter(o => o.status.includes("ฒกเΰΈ₯ิก")).length; + return { + byShop, monthly, topProducts, shops, + totals: { + totalRevenue, totalCommission, totalShipping, + netRevenue: totalRevenue - totalCommission - totalShipping, + totalOrders, totalCancelled, + cancelRate: totalOrders > 0 ? (totalCancelled / totalOrders) * 100 : 0, + aov: (totalOrders - totalCancelled) > 0 ? totalRevenue / (totalOrders - totalCancelled) : 0, + commissionRate: totalRevenue > 0 ? (totalCommission / totalRevenue) * 100 : 0, + }, + }; +}) + // ── GET Summary ─────────────────────────────────────────────────────────── + .get("/summary", async ({ query }) => { + const shop = query.shop || "hom-aroma"; + const where = { shop }; + const [orderCount, agg, itemAgg] = await Promise.all([ + prisma.shopeeOrder.count({ where }), + prisma.shopeeOrder.aggregate({ where, _sum: { totalAmount: true, buyerPaidAmount: true, commission: true, shippingFee: true } }), + prisma.shopeeOrderItem.aggregate({ where: { order: where }, _sum: { quantity: true, totalPayment: true } }), + ]); + return { + orderCount, + totalAmount: agg._sum.totalAmount ?? 0, + buyerPaidAmount: agg._sum.buyerPaidAmount ?? 0, + totalCommission: agg._sum.commission ?? 0, + totalShipping: agg._sum.shippingFee ?? 0, + totalItems: itemAgg._sum.quantity ?? 0, + totalItemRevenue: itemAgg._sum.totalPayment ?? 0, + }; +}); +//# sourceMappingURL=shopee.js.map \ No newline at end of file diff --git a/apps/backend/src/routes/shopee.js.map b/apps/backend/src/routes/shopee.js.map new file mode 100644 index 0000000..ca24cbc --- /dev/null +++ b/apps/backend/src/routes/shopee.js.map @@ -0,0 +1 @@ +{"version":3,"file":"shopee.js","sourceRoot":"","sources":["shopee.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/tiktok.d.ts b/apps/backend/src/routes/tiktok.d.ts new file mode 100644 index 0000000..ad11fb5 --- /dev/null +++ b/apps/backend/src/routes/tiktok.d.ts @@ -0,0 +1,217 @@ +import { Elysia } from "elysia"; +export declare const tiktokRoute: Elysia<"/tiktok", { + decorator: {}; + store: {}; + derive: {}; + resolve: {}; +}, { + typebox: {}; + error: {}; +}, { + schema: {}; + standaloneSchema: {}; + macro: {}; + macroFn: {}; + parser: {}; + response: {}; +}, { + tiktok: { + import: { + income: { + post: { + body: { + data: any[]; + }; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + success: boolean; + message: any; + }; + 422: { + type: "validation"; + on: string; + summary?: string; + message?: string; + found?: unknown; + property?: string; + expected?: string; + }; + }; + }; + }; + }; + }; +} & { + tiktok: { + import: { + product: { + post: { + body: { + data: any[]; + }; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + success: boolean; + message: any; + }; + 422: { + type: "validation"; + on: string; + summary?: string; + message?: string; + found?: unknown; + property?: string; + expected?: string; + }; + }; + }; + }; + }; + }; +} & { + tiktok: { + import: { + "product-sku": { + post: { + body: { + data: any[]; + }; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + success: boolean; + message: any; + }; + 422: { + type: "validation"; + on: string; + summary?: string; + message?: string; + found?: unknown; + property?: string; + expected?: string; + }; + }; + }; + }; + }; + }; +} & { + tiktok: { + income: { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + items: any; + total: any; + page: number; + limit: number; + totalPages: number; + }; + }; + }; + }; + }; +} & { + tiktok: { + product: { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + items: any; + total: any; + page: number; + limit: number; + totalPages: number; + }; + }; + }; + }; + }; +} & { + tiktok: { + "product-sku": { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + items: any; + total: any; + page: number; + limit: number; + totalPages: number; + }; + }; + }; + }; + }; +} & { + tiktok: { + summary: { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + incomeCount: any; + productCount: any; + skuCount: any; + totalRevenue: any; + totalSettlement: any; + totalGmv: any; + totalItemsSold: any; + totalOrders: any; + }; + }; + }; + }; + }; +} & { + tiktok: { + analysis: { + sku: { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: {}; + }; + }; + }; + }; +}, { + derive: {}; + resolve: {}; + schema: {}; + standaloneSchema: {}; + response: {}; +}, { + derive: {}; + resolve: {}; + schema: {}; + standaloneSchema: {}; + response: {}; +}>; +//# sourceMappingURL=tiktok.d.ts.map \ No newline at end of file diff --git a/apps/backend/src/routes/tiktok.d.ts.map b/apps/backend/src/routes/tiktok.d.ts.map new file mode 100644 index 0000000..391dadc --- /dev/null +++ b/apps/backend/src/routes/tiktok.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"tiktok.d.ts","sourceRoot":"","sources":["tiktok.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAGnC,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4SpB,CAAC"} \ No newline at end of file diff --git a/apps/backend/src/routes/tiktok.js b/apps/backend/src/routes/tiktok.js new file mode 100644 index 0000000..1cda83d --- /dev/null +++ b/apps/backend/src/routes/tiktok.js @@ -0,0 +1,261 @@ +import { Elysia, t } from "elysia"; +import prisma from "../lib/prisma"; +export const tiktokRoute = new Elysia({ prefix: "/tiktok" }) + .post("/import/income", async ({ body, set }) => { + try { + const { data } = body; + // ── Debug logging ──────────────────────────────────────────────────── + console.log(`[TikTok/Income] Received ${data.length} rows`); + if (data.length > 0) { + console.log(`[TikTok/Income] First row keys:`, Object.keys(data[0])); + console.log(`[TikTok/Income] First row id:`, data[0].id); + console.log(`[TikTok/Income] First row sample:`, JSON.stringify(data[0]).slice(0, 300)); + } + // Check for duplicate/missing IDs + const ids = data.map((r) => r.id); + const uniqueIds = new Set(ids); + console.log(`[TikTok/Income] Total IDs: ${ids.length}, Unique IDs: ${uniqueIds.size}`); + if (uniqueIds.size < ids.length) { + console.warn(`[TikTok/Income] ⚠️ Duplicate IDs detected!`); + } + // ── Chunked upsert (avoid transaction timeout on large files) ──────── + const CHUNK_SIZE = 50; + let inserted = 0; + for (let i = 0; i < data.length; i += CHUNK_SIZE) { + const chunk = data.slice(i, i + CHUNK_SIZE); + await prisma.$transaction(chunk.map((row) => prisma.tiktokIncome.upsert({ + where: { id: row.id }, + update: { + type: row.type, + orderCreatedTime: row.orderCreatedTime, + orderSettledTime: row.orderSettledTime, + currency: row.currency, + totalSettlementAmount: row.totalSettlementAmount, + totalRevenue: row.totalRevenue, + totalFees: row.totalFees, + rawData: row.rawData, + }, + create: { + id: row.id, + type: row.type, + orderCreatedTime: row.orderCreatedTime, + orderSettledTime: row.orderSettledTime, + currency: row.currency, + totalSettlementAmount: row.totalSettlementAmount, + totalRevenue: row.totalRevenue, + totalFees: row.totalFees, + rawData: row.rawData, + }, + })), { timeout: 30000 }); + inserted += chunk.length; + console.log(`[TikTok/Income] Progress: ${inserted}/${data.length}`); + } + console.log(`[TikTok/Income] βœ… Done β€” ${inserted} records upserted`); + return { success: true, message: `Imported ${inserted} income records.` }; + } + catch (error) { + console.error(`[TikTok/Income] ❌ Error:`, error.message); + set.status = 500; + return { success: false, message: error.message }; + } +}, { + body: t.Object({ + data: t.Array(t.Any()), + }), +}) + .post("/import/product", async ({ body, set }) => { + try { + const { data } = body; + await prisma.$transaction(data.map((row) => prisma.tiktokProduct.upsert({ + where: { id: row.id }, + update: { + productName: row.productName, + status: row.status, + gmv: row.gmv, + itemsSold: row.itemsSold, + orders: row.orders, + rawData: row.rawData, + }, + create: { + id: row.id, + productName: row.productName, + status: row.status, + gmv: row.gmv, + itemsSold: row.itemsSold, + orders: row.orders, + rawData: row.rawData, + }, + }))); + return { success: true, message: `Imported ${data.length} product records.` }; + } + catch (error) { + set.status = 500; + return { success: false, message: error.message }; + } +}, { + body: t.Object({ + data: t.Array(t.Any()), + }), +}) + .post("/import/product-sku", async ({ body, set }) => { + try { + const { data } = body; + await prisma.$transaction(data.map((row) => prisma.tiktokProductSku.upsert({ + where: { id: row.id }, + update: { + productId: row.productId, + skuName: row.skuName, + status: row.status, + gmv: row.gmv, + skuOrders: row.skuOrders, + itemsSold: row.itemsSold, + rawData: row.rawData, + }, + create: { + id: row.id, + productId: row.productId, + skuName: row.skuName, + status: row.status, + gmv: row.gmv, + skuOrders: row.skuOrders, + itemsSold: row.itemsSold, + rawData: row.rawData, + }, + }))); + return { success: true, message: `Imported ${data.length} SKU records.` }; + } + catch (error) { + set.status = 500; + return { success: false, message: error.message }; + } +}, { + body: t.Object({ + data: t.Array(t.Any()), + }), +}) + // ── GET endpoints ──────────────────────────────────────────────────────────── + .get("/income", async ({ query }) => { + const page = Number(query.page ?? 1); + const limit = Number(query.limit ?? 50); + const skip = (page - 1) * limit; + const [items, total] = await Promise.all([ + prisma.tiktokIncome.findMany({ + orderBy: { orderCreatedTime: "desc" }, + skip, + take: limit, + }), + prisma.tiktokIncome.count(), + ]); + return { items, total, page, limit, totalPages: Math.ceil(total / limit) }; +}) + .get("/product", async ({ query }) => { + const page = Number(query.page ?? 1); + const limit = Number(query.limit ?? 50); + const skip = (page - 1) * limit; + const [items, total] = await Promise.all([ + prisma.tiktokProduct.findMany({ + orderBy: { gmv: "desc" }, + skip, + take: limit, + }), + prisma.tiktokProduct.count(), + ]); + return { items, total, page, limit, totalPages: Math.ceil(total / limit) }; +}) + .get("/product-sku", async ({ query }) => { + const page = Number(query.page ?? 1); + const limit = Number(query.limit ?? 50); + const skip = (page - 1) * limit; + const [items, total] = await Promise.all([ + prisma.tiktokProductSku.findMany({ + orderBy: { gmv: "desc" }, + skip, + take: limit, + }), + prisma.tiktokProductSku.count(), + ]); + return { items, total, page, limit, totalPages: Math.ceil(total / limit) }; +}) + .get("/summary", async () => { + const [incomeCount, productCount, skuCount, incomeAgg, productAgg] = await Promise.all([ + prisma.tiktokIncome.count(), + prisma.tiktokProduct.count(), + prisma.tiktokProductSku.count(), + prisma.tiktokIncome.aggregate({ _sum: { totalRevenue: true, totalSettlementAmount: true } }), + prisma.tiktokProduct.aggregate({ _sum: { gmv: true, itemsSold: true, orders: true } }), + ]); + return { + incomeCount, + productCount, + skuCount, + totalRevenue: incomeAgg._sum.totalRevenue ?? 0, + totalSettlement: incomeAgg._sum.totalSettlementAmount ?? 0, + totalGmv: productAgg._sum.gmv ?? 0, + totalItemsSold: productAgg._sum.itemsSold ?? 0, + totalOrders: productAgg._sum.orders ?? 0, + }; +}) + .get("/analysis/sku", async () => { + // 1. Top 10 by GMV + const topGmv = await prisma.$queryRaw ` + WITH SkuStats AS ( + SELECT "id", "productId", "skuName", "gmv", "skuOrders", "itemsSold", + CASE WHEN "itemsSold" > 0 THEN "gmv" / "itemsSold" ELSE 0 END AS "avgPrice", + CASE WHEN "skuOrders" > 0 THEN "gmv" / "skuOrders" ELSE 0 END AS "aov" + FROM "TiktokProductSku" + ) + SELECT * FROM SkuStats ORDER BY "gmv" DESC LIMIT 10 + `; + // 2. Top 10 by Volume (itemsSold) + const topVolume = await prisma.$queryRaw ` + WITH SkuStats AS ( + SELECT "id", "productId", "skuName", "gmv", "skuOrders", "itemsSold", + CASE WHEN "itemsSold" > 0 THEN "gmv" / "itemsSold" ELSE 0 END AS "avgPrice", + CASE WHEN "skuOrders" > 0 THEN "gmv" / "skuOrders" ELSE 0 END AS "aov" + FROM "TiktokProductSku" + ) + SELECT * FROM SkuStats ORDER BY "itemsSold" DESC LIMIT 10 + `; + // 3. Scent/Variant Grouping + const scentAnalysis = await prisma.$queryRaw ` + WITH SplitNames AS ( + SELECT + "id", + "gmv", + "itemsSold", + "skuOrders", + TRIM(SPLIT_PART("skuName", ':', 2)) AS "scentName" + FROM "TiktokProductSku" + WHERE "skuName" LIKE '%:%' + ) + SELECT + "scentName", + SUM("gmv") AS "totalGmv", + SUM("itemsSold") AS "totalItems", + SUM("skuOrders") AS "totalOrders", + COUNT("id") AS "skuCount" + FROM SplitNames + GROUP BY "scentName" + ORDER BY "totalGmv" DESC + LIMIT 15 + `; + // 4. Overall Summary (Average AOV, Total GMV) + const summaryList = await prisma.$queryRaw ` + SELECT + SUM("gmv") AS "totalGmv", + SUM("itemsSold") AS "totalItems", + SUM("skuOrders") AS "totalOrders", + CASE WHEN SUM("skuOrders") > 0 THEN SUM("gmv") / SUM("skuOrders") ELSE 0 END AS "avgAov", + CASE WHEN SUM("itemsSold") > 0 THEN SUM("gmv") / SUM("itemsSold") ELSE 0 END AS "avgPrice" + FROM "TiktokProductSku" + `; + // Handle BigInt conversion since Prisma $queryRaw returns BigInts for COUNT/SUM in Postgres sometimes. + const serialize = (data) => JSON.parse(JSON.stringify(data, (key, value) => (typeof value === "bigint" ? Number(value) : value))); + return serialize({ + summary: summaryList[0] || null, + topGmv, + topVolume, + scentAnalysis + }); +}); +//# sourceMappingURL=tiktok.js.map \ No newline at end of file diff --git a/apps/backend/src/routes/tiktok.js.map b/apps/backend/src/routes/tiktok.js.map new file mode 100644 index 0000000..a65e18d --- /dev/null +++ b/apps/backend/src/routes/tiktok.js.map @@ -0,0 +1 @@ +{"version":3,"file":"tiktok.js","sourceRoot":"","sources":["tiktok.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/tiktokOrder.d.ts b/apps/backend/src/routes/tiktokOrder.d.ts new file mode 100644 index 0000000..ade5af9 --- /dev/null +++ b/apps/backend/src/routes/tiktokOrder.d.ts @@ -0,0 +1,247 @@ +import { Elysia } from "elysia"; +export declare const tiktokOrderRoute: Elysia<"/tiktok-order", { + decorator: {}; + store: {}; + derive: {}; + resolve: {}; +}, { + typebox: {}; + error: {}; +}, { + schema: {}; + standaloneSchema: {}; + macro: {}; + macroFn: {}; + parser: {}; + response: {}; +}, { + "tiktok-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; + }; + }; + }; + }; + }; +} & { + "tiktok-order": { + clear: { + delete: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + success: boolean; + message: string; + count: any; + } | { + success: boolean; + message: any; + count?: never; + }; + }; + }; + }; + }; +} & { + "tiktok-order": { + orders: { + ":orderId": { + get: { + body: unknown; + params: { + orderId: string; + } & {}; + query: unknown; + headers: unknown; + response: { + [x: string]: any; + [x: number]: any; + [x: symbol]: any; + }; + }; + }; + }; + }; +} & { + "tiktok-order": { + orders: { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + items: any; + total: any; + page: number; + limit: number; + totalPages: number; + }; + }; + }; + }; + }; +} & { + "tiktok-order": { + summary: { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + orderCount: any; + totalAmount: any; + totalShipping: any; + totalRefund: any; + totalTaxes: any; + totalItems: any; + totalSkuRevenue: any; + }; + }; + }; + }; + }; +} & { + "tiktok-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; + sku: string; + variation: string; + qty: number; + revenue: number; + }[]; + summary: { + totalRevenue: any; + totalShipping: any; + totalRefund: any; + totalPlatformDisc: any; + totalSellerDisc: any; + netRevenue: number; + totalOrders: any; + completedOrders: number; + cancelledOrders: any; + cancelRate: number; + aov: number; + }; + }; + }; + }; + }; + }; +} & { + "tiktok-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=tiktokOrder.d.ts.map \ No newline at end of file diff --git a/apps/backend/src/routes/tiktokOrder.d.ts.map b/apps/backend/src/routes/tiktokOrder.d.ts.map new file mode 100644 index 0000000..75bf4dd --- /dev/null +++ b/apps/backend/src/routes/tiktokOrder.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"tiktokOrder.d.ts","sourceRoot":"","sources":["tiktokOrder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AA2BnC,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCAmLiB,MAAM;qCAAW,MAAM;oCAAU,MAAM;uCAAa,MAAM;mCAAS,MAAM;8CAAoB,MAAM;4CAAkB,MAAM;;;;;;;;;;;kCAsC1H,MAAM;iCAAO,MAAM;uCAAa,MAAM;iCAAO,MAAM;qCAAW,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAuDvG,MAAM;oCAAU,MAAM;uCAAa,MAAM;uCAAa,MAAM;qCACzD,MAAM;yCAAe,MAAM;8CAAoB,MAAM;4CAAkB,MAAM;mCAAS,MAAM;;;;kCAwD5D,MAAM;uCAAa,MAAM;iCAAO,MAAM;qCAAW,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoClG,CAAC"} \ No newline at end of file diff --git a/apps/backend/src/routes/tiktokOrder.js b/apps/backend/src/routes/tiktokOrder.js new file mode 100644 index 0000000..f117b7a --- /dev/null +++ b/apps/backend/src/routes/tiktokOrder.js @@ -0,0 +1,371 @@ +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; +}; +// TikTok CSV values can have trailing \t β€” trim everything +const str = (val) => String(val ?? "").replace(/\t/g, "").trim(); +// TikTok dates are "DD/MM/YYYY HH:mm:ss" -> convert to "YYYY-MM-DD HH:mm:ss" for sorting/filtering +const parseTiktokDate = (val) => { + const d = str(val); + if (!d) + return ""; + const [datePart, timePart] = d.split(" "); + if (!datePart) + return d; + const parts = datePart.split("/"); + if (parts.length === 3) { + const [day, month, year] = parts; + return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")} ${timePart || "00:00:00"}`; + } + return d; +}; +export const tiktokOrderRoute = new Elysia({ prefix: "/tiktok-order" }) + // ── Import (CSV rows as JSON) ────────────────────────────────────────────── + .post("/import", async ({ body, set }) => { + try { + const { rows, shop } = body; + const shopName = shop || "hom-aroma"; + console.log(`[TiktokOrder] Received ${rows.length} raw rows for shop: ${shopName}`); + // Group rows by Order ID (trim tabs) + const orderMap = new Map(); + for (const row of rows) { + const orderId = str(row["Order ID"]); + if (!orderId) + continue; + if (!orderMap.has(orderId)) + orderMap.set(orderId, { meta: row, items: [] }); + orderMap.get(orderId).items.push(row); + } + console.log(`[TiktokOrder] 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 [orderId, { meta, items }] of chunk) { + // Delete old items first (sync strategy) + await tx.tiktokOrderItem.deleteMany({ where: { orderId } }); + const itemsData = items.map((item) => ({ + skuId: str(item["SKU ID"]), + sellerSku: str(item["Seller SKU"]), + productName: str(item["Product Name"]), + variation: str(item["Variation"]), + quantity: Math.max(1, Math.round(toNum(item["Quantity"]))), + skuQtyReturn: Math.round(toNum(item["Sku Quantity of return"])), + unitOriginalPrice: toNum(item["SKU Unit Original Price"]), + subtotalBeforeDiscount: toNum(item["SKU Subtotal Before Discount"]), + platformDiscount: toNum(item["SKU Platform Discount"]), + sellerDiscount: toNum(item["SKU Seller Discount"]), + subtotalAfterDiscount: toNum(item["SKU Subtotal After Discount"]), + productCategory: str(item["Product Category"]), + weight: toNum(item["Weight(kg)"]), + })); + const orderData = { + shop: shopName, + status: str(meta["Order Status"]), + subStatus: str(meta["Order Substatus"]), + orderType: str(meta["Normal or Pre-order"]), + cancelBy: str(meta["Cancel By"]), + cancelReason: str(meta["Cancel Reason"]), + fulfillmentType: str(meta["Fulfillment Type"]), + warehouseName: str(meta["Warehouse Name"]), + trackingId: str(meta["Tracking ID"]), + deliveryOption: str(meta["Delivery Option"]), + shippingProvider: str(meta["Shipping Provider Name"]), + buyerUsername: str(meta["Buyer Username"]), + paymentMethod: str(meta["Payment Method"]), + orderAmount: toNum(meta["Order Amount"]), + refundAmount: toNum(meta["Order Refund Amount"]), + shippingFee: toNum(meta["Shipping Fee After Discount"]), + originalShipping: toNum(meta["Original Shipping Fee"]), + taxes: toNum(meta["Taxes"]), + province: str(meta["Province"]), + district: str(meta["District"]), + country: str(meta["Country"]), + createdTime: parseTiktokDate(meta["Created Time"]), + paidTime: parseTiktokDate(meta["Paid Time"]), + rtsTime: parseTiktokDate(meta["RTS Time"]), + shippedTime: parseTiktokDate(meta["Shipped Time"]), + deliveredTime: parseTiktokDate(meta["Delivered Time"]), + cancelledTime: parseTiktokDate(meta["Cancelled Time"]), + }; + await tx.tiktokOrder.upsert({ + where: { orderId }, + update: { ...orderData, items: { create: itemsData } }, + create: { orderId, ...orderData, items: { create: itemsData } }, + }); + upserted++; + } + }, { timeout: 30000 }); + console.log(`[TiktokOrder] Progress: ${Math.min(i + CHUNK, entries.length)}/${entries.length}`); + } + console.log(`[TiktokOrder] βœ… Done β€” ${upserted} orders upserted`); + return { success: true, message: `ΰΈ™ΰΈ³ΰΉ€ΰΈ‚ΰΉ‰ΰΈ²ΰΈͺΰΈ³ΰΉ€ΰΈ£ΰΉ‡ΰΈˆ ${upserted} ΰΈ„ΰΈ³ΰΈͺΰΈ±ΰΉˆΰΈ‡ΰΈ‹ΰΈ·ΰΉ‰ΰΈ­ (${rows.length} แถว)`, ordersCount: upserted, rowsCount: rows.length }; + } + catch (error) { + console.error(`[TiktokOrder] ❌ 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.tiktokOrder.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/:orderId", async ({ params, set }) => { + const order = await prisma.tiktokOrder.findUnique({ where: { orderId: params.orderId }, 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.tiktokOrder.findMany({ where, include: { items: true }, orderBy: { createdTime: "desc" }, skip, take: limit }), + prisma.tiktokOrder.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, itemAgg] = await Promise.all([ + prisma.tiktokOrder.count({ where }), + prisma.tiktokOrder.aggregate({ where, _sum: { orderAmount: true, shippingFee: true, refundAmount: true, taxes: true } }), + prisma.tiktokOrderItem.aggregate({ where: { order: where }, _sum: { quantity: true, subtotalAfterDiscount: true } }), + ]); + return { + orderCount, + totalAmount: agg._sum.orderAmount ?? 0, + totalShipping: agg._sum.shippingFee ?? 0, + totalRefund: agg._sum.refundAmount ?? 0, + totalTaxes: agg._sum.taxes ?? 0, + totalItems: itemAgg._sum.quantity ?? 0, + totalSkuRevenue: itemAgg._sum.subtotalAfterDiscount ?? 0, + }; +}) + // ── GET Analytics (date range filter) ──────────────────────────────────── + .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.createdTime = {}; + if (dateFrom) + where.createdTime.gte = dateFrom; + if (dateTo) + where.createdTime.lte = dateTo + " 23:59"; + } + const allOrders = await prisma.tiktokOrder.findMany({ + where, + select: { + orderId: true, status: true, subStatus: true, createdTime: true, + orderAmount: true, shippingFee: true, refundAmount: true, taxes: true, + paymentMethod: true, province: true, + items: { select: { quantity: true, subtotalAfterDiscount: true, platformDiscount: true, sellerDiscount: true } }, + }, + }); + // Monthly aggregation + const monthMap = new Map(); + for (const o of allOrders) { + const month = (o.createdTime ?? "").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 + i.platformDiscount, 0); + m.sellerDiscount += o.items.reduce((s, i) => s + i.sellerDiscount, 0); + if (o.status.includes("ฒกเΰΈ₯ิก") || o.status.includes("cancel")) + m.cancelled += 1; + m.items += o.items.reduce((s, i) => s + i.quantity, 0); + } + 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.paymentMethod || "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 filteredIds = allOrders.map(o => o.orderId); + const allItems = filteredIds.length > 0 + ? await prisma.tiktokOrderItem.findMany({ + where: { orderId: { in: filteredIds } }, + select: { productName: true, variation: true, quantity: true, subtotalAfterDiscount: true, sellerSku: true }, + }) + : []; + const productMap = new Map(); + for (const item of allItems) { + const key = `${item.productName}__${item.variation ?? ""}`; + if (!productMap.has(key)) + productMap.set(key, { name: item.productName, sku: item.sellerSku ?? "", variation: item.variation ?? "", qty: 0, revenue: 0 }); + const p = productMap.get(key); + p.qty += item.quantity; + p.revenue += item.subtotalAfterDiscount; + } + 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 totalRefund = allOrders.reduce((s, o) => s + o.refundAmount, 0); + const totalPlatformDisc = allOrders.flatMap(o => o.items).reduce((s, i) => s + i.platformDiscount, 0); + const totalSellerDisc = allOrders.flatMap(o => o.items).reduce((s, i) => s + i.sellerDiscount, 0); + const totalOrders = allOrders.length; + const cancelledOrders = allOrders.filter(o => o.status.includes("ฒกเΰΈ₯ิก")).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, totalRefund, totalPlatformDisc, totalSellerDisc, + netRevenue: totalRevenue - totalShipping, + 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.createdTime = {}; + if (dateFrom) + where.createdTime.gte = dateFrom; + if (dateTo) + where.createdTime.lte = dateTo + " 23:59"; + } + const allOrders = await prisma.tiktokOrder.findMany({ + where, + select: { + orderId: true, shop: true, status: true, createdTime: true, + orderAmount: true, shippingFee: true, refundAmount: true, + items: { select: { quantity: true, platformDiscount: true, sellerDiscount: true, subtotalAfterDiscount: 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 + i.platformDiscount, 0); + s.sellerDiscount += o.items.reduce((acc, i) => acc + i.sellerDiscount, 0); + s.items += o.items.reduce((acc, i) => acc + i.quantity, 0); + if (o.status.includes("ฒกเΰΈ₯ิก") || o.status.includes("cancel")) + 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.createdTime ?? "").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; // Fallback for global view + return row; + }) + .sort((a, b) => a.month.localeCompare(b.month)); + // Top Products Global + const filteredIds = allOrders.map(o => o.orderId); + const allItems = filteredIds.length > 0 + ? await prisma.tiktokOrderItem.findMany({ + where: { orderId: { in: filteredIds } }, + select: { productName: true, variation: true, quantity: true, subtotalAfterDiscount: true }, + }) + : []; + const productMap = new Map(); + for (const item of allItems) { + const key = `${item.productName}__${item.variation ?? ""}`; + if (!productMap.has(key)) + productMap.set(key, { name: item.productName, variation: item.variation ?? "", qty: 0, revenue: 0 }); + const p = productMap.get(key); + p.qty += item.quantity; + p.revenue += item.subtotalAfterDiscount; + } + 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=tiktokOrder.js.map \ No newline at end of file diff --git a/apps/backend/src/routes/tiktokOrder.js.map b/apps/backend/src/routes/tiktokOrder.js.map new file mode 100644 index 0000000..7921db6 --- /dev/null +++ b/apps/backend/src/routes/tiktokOrder.js.map @@ -0,0 +1 @@ +{"version":3,"file":"tiktokOrder.js","sourceRoot":"","sources":["tiktokOrder.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/apps/backend/src/routes/upload.d.ts b/apps/backend/src/routes/upload.d.ts new file mode 100644 index 0000000..78aa53e --- /dev/null +++ b/apps/backend/src/routes/upload.d.ts @@ -0,0 +1,63 @@ +import { Elysia } from "elysia"; +export declare const uploadRoute: Elysia<"/upload", { + decorator: {}; + store: {}; + derive: {}; + resolve: {}; +}, { + typebox: {}; + error: {}; +}, { + schema: {}; + standaloneSchema: {}; + macro: {}; + macroFn: {}; + parser: {}; + response: {}; +}, { + upload: { + post: { + body: { + file: File; + }; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: { + error: string; + success?: never; + url?: never; + filename?: never; + } | { + success: boolean; + url: string; + filename: string; + 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=upload.d.ts.map \ No newline at end of file diff --git a/apps/backend/src/routes/upload.d.ts.map b/apps/backend/src/routes/upload.d.ts.map new file mode 100644 index 0000000..a7a34c5 --- /dev/null +++ b/apps/backend/src/routes/upload.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["upload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAK,MAAM,QAAQ,CAAC;AAInC,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwDpB,CAAC"} \ No newline at end of file diff --git a/apps/backend/src/routes/upload.js b/apps/backend/src/routes/upload.js new file mode 100644 index 0000000..fbca18e --- /dev/null +++ b/apps/backend/src/routes/upload.js @@ -0,0 +1,53 @@ +import { Elysia, t } from "elysia"; +import { join } from "path"; +import { mkdir } from "fs/promises"; +export const uploadRoute = new Elysia({ prefix: "/upload" }) + .post("/", async ({ body, set }) => { + const { file } = body; + if (!file) { + set.status = 400; + return { error: "No file uploaded" }; + } + // Validate file type + if (!file.type.startsWith("image/")) { + set.status = 400; + return { error: "Only image files are allowed" }; + } + console.log(`[Upload] Processing: ${file.name} (${(file.size / 1024).toFixed(2)} KB, ${file.type})`); + // Generate unique filename + const ext = file.name.split(".").pop(); + const filename = `${crypto.randomUUID()}.${ext}`; + const uploadDir = join(process.cwd(), "public", "uploads"); + const filePath = join(uploadDir, filename); + try { + // Ensure directory exists + await mkdir(uploadDir, { recursive: true }); + // Write file using Bun.write + await Bun.write(filePath, await file.arrayBuffer()); + const APP_URL = process.env.APP_URL || "http://localhost:3000"; + const publicUrl = `${APP_URL}/uploads/${filename}`; + console.log(`[Upload] Success: ${filename} -> ${publicUrl}`); + return { + success: true, + url: publicUrl, + filename + }; + } + catch (err) { + console.error("[Upload] Error:", err); + set.status = 500; + return { error: "Failed to save file" }; + } +}, { + body: t.Object({ + file: t.File({ + maxSize: '5m', + type: ['image/jpeg', 'image/png', 'image/webp', 'image/gif'] + }) + }), + detail: { + summary: "Upload product image", + tags: ["upload"] + } +}); +//# sourceMappingURL=upload.js.map \ No newline at end of file diff --git a/apps/backend/src/routes/upload.js.map b/apps/backend/src/routes/upload.js.map new file mode 100644 index 0000000..bf80025 --- /dev/null +++ b/apps/backend/src/routes/upload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"upload.js","sourceRoot":"","sources":["upload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;KACzD,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE;IACjC,MAAM,EAAE,IAAI,EAAE,GAAG,IAAsB,CAAC;IAExC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC;IACvC,CAAC;IAED,qBAAqB;IACrB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC;IACnD,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;IAErG,2BAA2B;IAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;IACvC,MAAM,QAAQ,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,IAAI,GAAG,EAAE,CAAC;IACjD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAE3C,IAAI,CAAC;QACH,0BAA0B;QAC1B,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE5C,6BAA6B;QAC7B,MAAM,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAEpD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,uBAAuB,CAAC;QAC/D,MAAM,SAAS,GAAG,GAAG,OAAO,YAAY,QAAQ,EAAE,CAAC;QAEnD,OAAO,CAAC,GAAG,CAAC,qBAAqB,QAAQ,OAAO,SAAS,EAAE,CAAC,CAAC;QAE7D,OAAO;YACL,OAAO,EAAE,IAAI;YACb,GAAG,EAAE,SAAS;YACd,QAAQ;SACT,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;QACtC,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,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;YACX,OAAO,EAAE,IAAI;YACb,IAAI,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,CAAC;SAC7D,CAAC;KACH,CAAC;IACF,MAAM,EAAE;QACN,OAAO,EAAE,sBAAsB;QAC/B,IAAI,EAAE,CAAC,QAAQ,CAAC;KACjB;CACF,CAAC,CAAC"} \ No newline at end of file diff --git a/apps/backend/tsconfig.tsbuildinfo b/apps/backend/tsconfig.tsbuildinfo new file mode 100644 index 0000000..eac98cd --- /dev/null +++ b/apps/backend/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./prisma.config.ts","./prisma/seed-pages.ts","./scripts/migrate-i18n.ts","./src/index.ts","./src/lib/lineClient.ts","./src/lib/notifications.ts","./src/lib/prisma.ts","./src/middleware/auth.ts","./src/routes/articles.ts","./src/routes/auth.ts","./src/routes/categories.ts","./src/routes/coupons.ts","./src/routes/customers.ts","./src/routes/hero.ts","./src/routes/lazadaOrder.ts","./src/routes/line.ts","./src/routes/menus.ts","./src/routes/newsletter.ts","./src/routes/orders.ts","./src/routes/pages.ts","./src/routes/payments.ts","./src/routes/products.ts","./src/routes/profile.ts","./src/routes/promotions.ts","./src/routes/raw-materials.ts","./src/routes/reviews.ts","./src/routes/settings.ts","./src/routes/shopee.ts","./src/routes/tiktok.ts","./src/routes/tiktokOrder.ts","./src/routes/upload.ts"],"errors":true,"version":"6.0.3"} \ No newline at end of file