notshop-bundle/shared/schema.ts

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>;