284 lines
15 KiB
TypeScript
284 lines
15 KiB
TypeScript
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<typeof insertCustomerSchema>;
|
|
|
|
// ── 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<typeof insertAddressSchema>;
|
|
|
|
// ── 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<typeof insertBrandSchema>;
|
|
|
|
// ── 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<typeof insertCategorySchema>;
|
|
|
|
// ── 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<string[]>().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<typeof insertProductSchema>;
|
|
|
|
// ── 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<typeof insertProductVariantSchema>;
|
|
|
|
// ── 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<typeof insertDiscountCodeSchema>;
|
|
|
|
// ── 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<typeof insertCartSchema>;
|
|
|
|
// ── 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<typeof insertCartItemSchema>;
|
|
|
|
// ── 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<typeof insertOrderSchema>;
|
|
|
|
// ── 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<typeof insertOrderItemSchema>;
|
|
|
|
// ── 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<typeof insertOrderEventSchema>;
|
|
|
|
// ── 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<typeof insertPaymentMethodSchema>;
|
|
|
|
// ── 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<typeof insertAdminUserSchema>;
|