import { boolean, doublePrecision, integer, pgTable, primaryKey, serial, text, timestamp, unique, } from "drizzle-orm/pg-core"; // ── Users ─────────────────────────────────────────────────────────── export const users = pgTable("users", { id: serial("id").primaryKey(), logtoSub: text("logto_sub").notNull().unique(), displayName: text("display_name"), avatarUrl: text("avatar_url"), bio: text("bio"), createdAt: timestamp("created_at").defaultNow().notNull(), }); // ── Categories ────────────────────────────────────────────────────── export const categories = pgTable( "categories", { id: serial("id").primaryKey(), name: text("name").notNull(), icon: text("icon").notNull().default("package"), userId: integer("user_id") .notNull() .references(() => users.id), createdAt: timestamp("created_at").defaultNow().notNull(), }, (table) => [unique().on(table.userId, table.name)], ); // ── Items ─────────────────────────────────────────────────────────── export const items = pgTable("items", { id: serial("id").primaryKey(), name: text("name").notNull(), weightGrams: doublePrecision("weight_grams"), priceCents: integer("price_cents"), categoryId: integer("category_id") .notNull() .references(() => categories.id), userId: integer("user_id") .notNull() .references(() => users.id), notes: text("notes"), productUrl: text("product_url"), imageFilename: text("image_filename"), imageSourceUrl: text("image_source_url"), quantity: integer("quantity").notNull().default(1), globalItemId: integer("global_item_id").references(() => globalItems.id), purchasePriceCents: integer("purchase_price_cents"), createdAt: timestamp("created_at").defaultNow().notNull(), updatedAt: timestamp("updated_at").defaultNow().notNull(), }); // ── Threads ───────────────────────────────────────────────────────── export const threads = pgTable("threads", { id: serial("id").primaryKey(), name: text("name").notNull(), status: text("status").notNull().default("active"), resolvedCandidateId: integer("resolved_candidate_id"), categoryId: integer("category_id") .notNull() .references(() => categories.id), userId: integer("user_id") .notNull() .references(() => users.id), createdAt: timestamp("created_at").defaultNow().notNull(), updatedAt: timestamp("updated_at").defaultNow().notNull(), }); // ── Thread Candidates ─────────────────────────────────────────────── export const threadCandidates = pgTable("thread_candidates", { id: serial("id").primaryKey(), threadId: integer("thread_id") .notNull() .references(() => threads.id, { onDelete: "cascade" }), name: text("name").notNull(), weightGrams: doublePrecision("weight_grams"), priceCents: integer("price_cents"), categoryId: integer("category_id") .notNull() .references(() => categories.id), notes: text("notes"), productUrl: text("product_url"), imageFilename: text("image_filename"), imageSourceUrl: text("image_source_url"), status: text("status").notNull().default("researching"), pros: text("pros"), cons: text("cons"), sortOrder: doublePrecision("sort_order").notNull().default(0), globalItemId: integer("global_item_id").references(() => globalItems.id), createdAt: timestamp("created_at").defaultNow().notNull(), updatedAt: timestamp("updated_at").defaultNow().notNull(), }); // ── Setups ────────────────────────────────────────────────────────── export const setups = pgTable("setups", { id: serial("id").primaryKey(), name: text("name").notNull(), userId: integer("user_id") .notNull() .references(() => users.id), isPublic: boolean("is_public").notNull().default(false), createdAt: timestamp("created_at").defaultNow().notNull(), updatedAt: timestamp("updated_at").defaultNow().notNull(), }); // ── Setup Items ───────────────────────────────────────────────────── export const setupItems = pgTable("setup_items", { id: serial("id").primaryKey(), setupId: integer("setup_id") .notNull() .references(() => setups.id, { onDelete: "cascade" }), itemId: integer("item_id") .notNull() .references(() => items.id, { onDelete: "cascade" }), classification: text("classification").notNull().default("base"), }); // ── Global Items ──────────────────────────────────────────────────── export const globalItems = pgTable("global_items", { id: serial("id").primaryKey(), brand: text("brand").notNull(), model: text("model").notNull(), category: text("category"), weightGrams: doublePrecision("weight_grams"), priceCents: integer("price_cents"), imageUrl: text("image_url"), description: text("description"), createdAt: timestamp("created_at").defaultNow().notNull(), }); // ── Tags ─────────────────────────────────────────────────────────── export const tags = pgTable("tags", { id: serial("id").primaryKey(), name: text("name").notNull().unique(), createdAt: timestamp("created_at").defaultNow().notNull(), }); // ── Global Item Tags ─────────────────────────────────────────────── export const globalItemTags = pgTable( "global_item_tags", { globalItemId: integer("global_item_id") .notNull() .references(() => globalItems.id, { onDelete: "cascade" }), tagId: integer("tag_id") .notNull() .references(() => tags.id, { onDelete: "cascade" }), }, (table) => [primaryKey({ columns: [table.globalItemId, table.tagId] })], ); // ── Settings ──────────────────────────────────────────────────────── export const settings = pgTable( "settings", { userId: integer("user_id") .notNull() .references(() => users.id), key: text("key").notNull(), value: text("value").notNull(), }, (table) => [primaryKey({ columns: [table.userId, table.key] })], ); // ── API Keys ──────────────────────────────────────────────────────── export const apiKeys = pgTable("api_keys", { id: serial("id").primaryKey(), name: text("name").notNull(), keyHash: text("key_hash").notNull(), keyPrefix: text("key_prefix").notNull(), userId: integer("user_id") .notNull() .references(() => users.id), createdAt: timestamp("created_at").defaultNow().notNull(), }); // ── OAuth Clients ─────────────────────────────────────────────────── export const oauthClients = pgTable("oauth_clients", { id: serial("id").primaryKey(), clientId: text("client_id").notNull().unique(), clientName: text("client_name"), redirectUris: text("redirect_uris").notNull(), // JSON array createdAt: timestamp("created_at").defaultNow().notNull(), }); // ── OAuth Authorization Codes ─────────────────────────────────────── export const oauthCodes = pgTable("oauth_codes", { id: serial("id").primaryKey(), code: text("code").notNull().unique(), clientId: text("client_id").notNull(), codeChallenge: text("code_challenge").notNull(), codeChallengeMethod: text("code_challenge_method").notNull().default("S256"), redirectUri: text("redirect_uri").notNull(), expiresAt: timestamp("expires_at").notNull(), used: integer("used").notNull().default(0), }); // ── OAuth Tokens ──────────────────────────────────────────────────── export const oauthTokens = pgTable("oauth_tokens", { id: serial("id").primaryKey(), accessTokenHash: text("access_token_hash").notNull().unique(), refreshTokenHash: text("refresh_token_hash").notNull().unique(), clientId: text("client_id").notNull(), userId: integer("user_id") .notNull() .references(() => users.id), expiresAt: timestamp("expires_at").notNull(), refreshExpiresAt: timestamp("refresh_expires_at").notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), });