import { pgTable, serial, text, integer, boolean, timestamp, jsonb } from "drizzle-orm/pg-core"; import { createInsertSchema } from "drizzle-zod"; import { sql } from "drizzle-orm"; import { z } from "zod"; // ── Customers ────────────────────────────────────────────────────────────────── export const customers = pgTable("customers", { id: serial("id").primaryKey(), email: text("email").notNull().unique(), firstName: text("first_name"), lastName: text("last_name"), phone: text("phone"), defaultAddressId: integer("default_address_id"), notes: text("notes"), tags: text("tags").array(), authNetProfileId: text("auth_net_profile_id"), stripeCustomerId: text("stripe_customer_id"), storeCredit: text("store_credit").notNull().default("0.00"), magicLinkToken: text("magic_link_token"), magicLinkTokenExpiresAt: timestamp("magic_link_token_expires_at"), createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(), updatedAt: timestamp("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(), }); export const insertCustomerSchema = createInsertSchema(customers).omit({ id: true, createdAt: true, updatedAt: true }); export type Customer = typeof customers.$inferSelect; export type InsertCustomer = z.infer; // ── Addresses ───────────────────────────────────────────────────────────────── export const addresses = pgTable("addresses", { id: serial("id").primaryKey(), customerId: integer("customer_id").notNull().references(() => customers.id, { onDelete: "cascade" }), firstName: text("first_name"), lastName: text("last_name"), address1: text("address1").notNull(), address2: text("address2"), city: text("city").notNull(), state: text("state").notNull(), zip: text("zip").notNull(), country: text("country").notNull().default("US"), isDefault: boolean("is_default").default(false), createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(), }); export const insertAddressSchema = createInsertSchema(addresses).omit({ id: true, createdAt: true }); export type Address = typeof addresses.$inferSelect; export type InsertAddress = z.infer; // ── Brands ──────────────────────────────────────────────────────────────────── export const brands = pgTable("brands", { id: serial("id").primaryKey(), name: text("name").notNull().unique(), slug: text("slug").notNull().unique(), description: text("description"), logoUrl: text("logo_url"), websiteUrl: text("website_url"), createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(), }); export const insertBrandSchema = createInsertSchema(brands).omit({ id: true, createdAt: true }); export type Brand = typeof brands.$inferSelect; export type InsertBrand = z.infer; // ── Categories ──────────────────────────────────────────────────────────────── export const categories = pgTable("categories", { id: serial("id").primaryKey(), name: text("name").notNull().unique(), slug: text("slug").notNull().unique(), parentId: integer("parent_id"), description: text("description"), imageUrl: text("image_url"), position: integer("position").default(0), createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(), }); export const insertCategorySchema = createInsertSchema(categories).omit({ id: true, createdAt: true }); export type Category = typeof categories.$inferSelect; export type InsertCategory = z.infer; // ── Products ────────────────────────────────────────────────────────────────── export const products = pgTable("products", { id: serial("id").primaryKey(), handle: text("handle").notNull().unique(), title: text("title").notNull(), description: text("description"), descriptionHtml: text("description_html"), vendor: text("vendor"), productType: text("product_type"), brandId: integer("brand_id").references(() => brands.id), categoryId: integer("category_id").references(() => categories.id), tags: text("tags").array(), status: text("status").notNull().default("active"), price: text("price").notNull().default("0.00"), compareAtPrice: text("compare_at_price"), images: jsonb("images").$type().default([]), metaTitle: text("meta_title"), metaDescription: text("meta_description"), hsaFsa: boolean("hsa_fsa").default(false), ingredients: text("ingredients"), weight: text("weight"), weightUnit: text("weight_unit").default("lb"), createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(), updatedAt: timestamp("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(), }); export const insertProductSchema = createInsertSchema(products).omit({ id: true, createdAt: true, updatedAt: true }); export type Product = typeof products.$inferSelect; export type InsertProduct = z.infer; // ── Product Variants ────────────────────────────────────────────────────────── export const productVariants = pgTable("product_variants", { id: serial("id").primaryKey(), productId: integer("product_id").notNull().references(() => products.id, { onDelete: "cascade" }), title: text("title").notNull().default("Default Title"), sku: text("sku"), price: text("price").notNull().default("0.00"), compareAtPrice: text("compare_at_price"), inventoryQty: integer("inventory_qty").notNull().default(0), inventoryPolicy: text("inventory_policy").notNull().default("deny"), weight: text("weight"), weightUnit: text("weight_unit").default("lb"), imageUrl: text("image_url"), position: integer("position").default(0), createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(), }); export const insertProductVariantSchema = createInsertSchema(productVariants).omit({ id: true, createdAt: true }); export type ProductVariant = typeof productVariants.$inferSelect; export type InsertProductVariant = z.infer; // ── Discount Codes ──────────────────────────────────────────────────────────── export const discountCodes = pgTable("discount_codes", { id: serial("id").primaryKey(), code: text("code").notNull().unique(), type: text("type").notNull().default("percentage"), value: text("value").notNull(), minOrderAmount: text("min_order_amount").default("0.00"), maxUses: integer("max_uses"), usedCount: integer("used_count").notNull().default(0), active: boolean("active").notNull().default(true), expiresAt: timestamp("expires_at"), createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(), }); export const insertDiscountCodeSchema = createInsertSchema(discountCodes).omit({ id: true, usedCount: true, createdAt: true }); export type DiscountCode = typeof discountCodes.$inferSelect; export type InsertDiscountCode = z.infer; // ── Carts ───────────────────────────────────────────────────────────────────── export const carts = pgTable("carts", { id: serial("id").primaryKey(), sessionId: text("session_id").notNull(), customerId: integer("customer_id").references(() => customers.id), status: text("status").notNull().default("active"), discountCode: text("discount_code"), discountAmount: text("discount_amount"), expiresAt: timestamp("expires_at"), createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(), updatedAt: timestamp("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(), }); export const insertCartSchema = createInsertSchema(carts).omit({ id: true, createdAt: true, updatedAt: true }); export type Cart = typeof carts.$inferSelect; export type InsertCart = z.infer; // ── Cart Items ──────────────────────────────────────────────────────────────── export const cartItems = pgTable("cart_items", { id: serial("id").primaryKey(), cartId: integer("cart_id").notNull().references(() => carts.id, { onDelete: "cascade" }), productId: integer("product_id").notNull().references(() => products.id), variantId: integer("variant_id").references(() => productVariants.id), title: text("title").notNull(), variantTitle: text("variant_title"), price: text("price").notNull(), quantity: integer("quantity").notNull().default(1), imageUrl: text("image_url"), productHandle: text("product_handle"), createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(), }); export const insertCartItemSchema = createInsertSchema(cartItems).omit({ id: true, createdAt: true }); export type CartItem = typeof cartItems.$inferSelect; export type InsertCartItem = z.infer; // ── Orders ──────────────────────────────────────────────────────────────────── export const orders = pgTable("orders", { id: serial("id").primaryKey(), orderNumber: text("order_number").notNull().unique(), customerId: integer("customer_id").references(() => customers.id), email: text("email").notNull(), status: text("status").notNull().default("pending"), paymentStatus: text("payment_status").notNull().default("unpaid"), fulfillmentStatus: text("fulfillment_status").notNull().default("unfulfilled"), paymentProcessor: text("payment_processor").notNull().default("authorizenet"), subtotal: text("subtotal").notNull(), tax: text("tax").notNull().default("0.00"), shipping: text("shipping").notNull().default("0.00"), discountCode: text("discount_code"), discountAmount: text("discount_amount").default("0.00"), total: text("total").notNull(), currency: text("currency").notNull().default("USD"), authNetTransactionId: text("auth_net_transaction_id"), stripePaymentIntentId: text("stripe_payment_intent_id"), shippingAddress: jsonb("shipping_address"), billingAddress: jsonb("billing_address"), notes: text("notes"), trackingNumber: text("tracking_number"), trackingCarrier: text("tracking_carrier"), shippingMethod: text("shipping_method"), paymentVerified: boolean("payment_verified").default(false), archived: boolean("archived").default(false), createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(), updatedAt: timestamp("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(), }); export const insertOrderSchema = createInsertSchema(orders).omit({ id: true, createdAt: true, updatedAt: true }); export type Order = typeof orders.$inferSelect; export type InsertOrder = z.infer; // ── Order Items ─────────────────────────────────────────────────────────────── export const orderItems = pgTable("order_items", { id: serial("id").primaryKey(), orderId: integer("order_id").notNull().references(() => orders.id, { onDelete: "cascade" }), productHandle: text("product_handle"), variantId: integer("variant_id"), title: text("title").notNull(), variantTitle: text("variant_title"), quantity: integer("quantity").notNull(), price: text("price").notNull(), total: text("total").notNull(), sku: text("sku"), imageUrl: text("image_url"), createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(), }); export const insertOrderItemSchema = createInsertSchema(orderItems).omit({ id: true, createdAt: true }); export type OrderItem = typeof orderItems.$inferSelect; export type InsertOrderItem = z.infer; // ── Order Events (timeline) ─────────────────────────────────────────────────── export const orderEvents = pgTable("order_events", { id: serial("id").primaryKey(), orderId: integer("order_id").notNull().references(() => orders.id, { onDelete: "cascade" }), type: text("type").notNull(), body: text("body"), actor: text("actor"), createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(), }); export const insertOrderEventSchema = createInsertSchema(orderEvents).omit({ id: true, createdAt: true }); export type OrderEvent = typeof orderEvents.$inferSelect; export type InsertOrderEvent = z.infer; // ── Payment Methods (vaulted cards) ────────────────────────────────────────── export const paymentMethods = pgTable("payment_methods", { id: serial("id").primaryKey(), customerId: integer("customer_id").notNull().references(() => customers.id, { onDelete: "cascade" }), source: text("source").notNull().default("authorizenet"), authNetPaymentProfileId: text("auth_net_payment_profile_id"), stripePaymentMethodId: text("stripe_payment_method_id"), cardLast4: text("card_last4"), cardType: text("card_type"), expirationDate: text("expiration_date"), isDefault: boolean("is_default").default(false), createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(), }); export const insertPaymentMethodSchema = createInsertSchema(paymentMethods).omit({ id: true, createdAt: true }); export type PaymentMethod = typeof paymentMethods.$inferSelect; export type InsertPaymentMethod = z.infer; // ── Admin Users ─────────────────────────────────────────────────────────────── export const adminUsers = pgTable("admin_users", { id: serial("id").primaryKey(), email: text("email").notNull().unique(), name: text("name"), role: text("role").notNull().default("admin"), passwordHash: text("password_hash"), createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(), }); export const insertAdminUserSchema = createInsertSchema(adminUsers).omit({ id: true, createdAt: true }); export type AdminUser = typeof adminUsers.$inferSelect; export type InsertAdminUser = z.infer;