import { and, eq, sql } from "drizzle-orm"; import type { db as prodDb } from "../../db/index.ts"; import { categories, globalItems, items } from "../../db/schema.ts"; import type { CreateItem } from "../../shared/types.ts"; type Db = typeof prodDb; export async function getAllItems(db: Db, userId: number) { return db .select({ id: items.id, name: sql`COALESCE( CASE WHEN ${items.globalItemId} IS NOT NULL THEN ${globalItems.brand} || ' ' || ${globalItems.model} ELSE ${items.name} END, ${items.name} )`.as("name"), weightGrams: sql`COALESCE( CASE WHEN ${items.globalItemId} IS NOT NULL THEN ${globalItems.weightGrams} ELSE NULL END, ${items.weightGrams} )`.as("weight_grams"), priceCents: sql`COALESCE( CASE WHEN ${items.globalItemId} IS NOT NULL THEN ${globalItems.priceCents} ELSE NULL END, ${items.priceCents} )`.as("price_cents"), purchasePriceCents: items.purchasePriceCents, quantity: items.quantity, categoryId: items.categoryId, notes: items.notes, productUrl: items.productUrl, imageFilename: sql`COALESCE( ${items.imageFilename}, CASE WHEN ${items.globalItemId} IS NOT NULL THEN ${globalItems.imageUrl} ELSE NULL END )`.as("image_filename"), imageSourceUrl: items.imageSourceUrl, globalItemId: items.globalItemId, brand: sql< string | null >`COALESCE(${globalItems.brand}, ${items.brand})`.as("brand"), createdAt: items.createdAt, updatedAt: items.updatedAt, categoryName: categories.name, categoryIcon: categories.icon, }) .from(items) .innerJoin(categories, eq(items.categoryId, categories.id)) .leftJoin(globalItems, eq(items.globalItemId, globalItems.id)) .where(eq(items.userId, userId)); } export async function getItemById(db: Db, userId: number, id: number) { const [row] = await db .select({ id: items.id, name: sql`COALESCE( CASE WHEN ${items.globalItemId} IS NOT NULL THEN ${globalItems.brand} || ' ' || ${globalItems.model} ELSE ${items.name} END, ${items.name} )`.as("name"), weightGrams: sql`COALESCE( CASE WHEN ${items.globalItemId} IS NOT NULL THEN ${globalItems.weightGrams} ELSE NULL END, ${items.weightGrams} )`.as("weight_grams"), priceCents: sql`COALESCE( CASE WHEN ${items.globalItemId} IS NOT NULL THEN ${globalItems.priceCents} ELSE NULL END, ${items.priceCents} )`.as("price_cents"), purchasePriceCents: items.purchasePriceCents, quantity: items.quantity, categoryId: items.categoryId, notes: items.notes, productUrl: items.productUrl, imageFilename: sql`COALESCE( ${items.imageFilename}, CASE WHEN ${items.globalItemId} IS NOT NULL THEN ${globalItems.imageUrl} ELSE NULL END )`.as("image_filename"), imageSourceUrl: items.imageSourceUrl, globalItemId: items.globalItemId, brand: sql< string | null >`COALESCE(${globalItems.brand}, ${items.brand})`.as("brand"), createdAt: items.createdAt, updatedAt: items.updatedAt, categoryName: categories.name, categoryIcon: categories.icon, }) .from(items) .innerJoin(categories, eq(items.categoryId, categories.id)) .leftJoin(globalItems, eq(items.globalItemId, globalItems.id)) .where(and(eq(items.id, id), eq(items.userId, userId))); return row ?? null; } export async function createItem( db: Db, userId: number, data: Partial & { name: string; categoryId: number; imageFilename?: string; }, ) { // For reference items, look up global item for fallback name (items.name is NOT NULL) let name = data.name; if (data.globalItemId) { const [gi] = await db .select({ brand: globalItems.brand, model: globalItems.model }) .from(globalItems) .where(eq(globalItems.id, data.globalItemId)); if (gi) { name = `${gi.brand} ${gi.model}`; } } const [row] = await db .insert(items) .values({ name, weightGrams: data.weightGrams ?? null, priceCents: data.priceCents ?? null, quantity: data.quantity ?? 1, categoryId: data.categoryId, userId, notes: data.notes ?? null, productUrl: data.productUrl ?? null, imageFilename: data.imageFilename ?? null, imageSourceUrl: data.imageSourceUrl ?? null, globalItemId: data.globalItemId ?? null, purchasePriceCents: data.purchasePriceCents ?? null, }) .returning(); return row; } export async function updateItem( db: Db, userId: number, id: number, data: Partial<{ name: string; weightGrams: number; priceCents: number; quantity: number; categoryId: number; notes: string; productUrl: string; imageFilename: string; imageSourceUrl: string; globalItemId: number; purchasePriceCents: number; brand: string; dominantColor: string | null; cropZoom: number | null; cropX: number | null; cropY: number | null; }>, ) { // Check if item exists and belongs to user const [existing] = await db .select({ id: items.id }) .from(items) .where(and(eq(items.id, id), eq(items.userId, userId))); if (!existing) return null; const [row] = await db .update(items) .set({ ...data, updatedAt: new Date() }) .where(and(eq(items.id, id), eq(items.userId, userId))) .returning(); return row; } export async function duplicateItem(db: Db, userId: number, id: number) { const [source] = await db .select() .from(items) .where(and(eq(items.id, id), eq(items.userId, userId))); if (!source) return null; const [row] = await db .insert(items) .values({ name: `${source.name} (copy)`, weightGrams: source.weightGrams, priceCents: source.priceCents, categoryId: source.categoryId, userId, notes: source.notes, productUrl: source.productUrl, imageFilename: source.imageFilename, imageSourceUrl: source.imageSourceUrl, quantity: source.quantity, globalItemId: source.globalItemId, purchasePriceCents: source.purchasePriceCents, }) .returning(); return row; } export async function deleteItem(db: Db, userId: number, id: number) { // Get item first (for image cleanup info) const [item] = await db .select() .from(items) .where(and(eq(items.id, id), eq(items.userId, userId))); if (!item) return null; await db.delete(items).where(and(eq(items.id, id), eq(items.userId, userId))); return item; }