import { and, eq, inArray, sql } from "drizzle-orm"; import { db as prodDb } from "../../db/index.ts"; import { categories, items, setupItems, setups } from "../../db/schema.ts"; import type { CreateSetup, UpdateSetup } from "../../shared/types.ts"; type Db = typeof prodDb; export async function createSetup( db: Db, userId: number, data: CreateSetup, ) { const [row] = await db .insert(setups) .values({ name: data.name, userId }) .returning(); return row; } export async function getAllSetups(db: Db, userId: number) { return db .select({ id: setups.id, name: setups.name, createdAt: setups.createdAt, updatedAt: setups.updatedAt, itemCount: sql`COALESCE(( SELECT COUNT(*) FROM setup_items WHERE setup_items.setup_id = setups.id ), 0)`.as("item_count"), totalWeight: sql`COALESCE(( SELECT SUM(items.weight_grams * items.quantity) FROM setup_items JOIN items ON items.id = setup_items.item_id WHERE setup_items.setup_id = setups.id ), 0)`.as("total_weight"), totalCost: sql`COALESCE(( SELECT SUM(items.price_cents * items.quantity) FROM setup_items JOIN items ON items.id = setup_items.item_id WHERE setup_items.setup_id = setups.id ), 0)`.as("total_cost"), }) .from(setups) .where(eq(setups.userId, userId)); } export async function getSetupWithItems( db: Db, userId: number, setupId: number, ) { const [setup] = await db .select() .from(setups) .where(and(eq(setups.id, setupId), eq(setups.userId, userId))); if (!setup) return null; const itemList = await db .select({ id: items.id, name: items.name, weightGrams: items.weightGrams, priceCents: items.priceCents, quantity: items.quantity, categoryId: items.categoryId, notes: items.notes, productUrl: items.productUrl, imageFilename: items.imageFilename, createdAt: items.createdAt, updatedAt: items.updatedAt, categoryName: categories.name, categoryIcon: categories.icon, classification: setupItems.classification, }) .from(setupItems) .innerJoin(items, eq(setupItems.itemId, items.id)) .innerJoin(categories, eq(items.categoryId, categories.id)) .where(eq(setupItems.setupId, setupId)); return { ...setup, items: itemList }; } export async function updateSetup( db: Db, userId: number, setupId: number, data: UpdateSetup, ) { const [existing] = await db .select({ id: setups.id }) .from(setups) .where(and(eq(setups.id, setupId), eq(setups.userId, userId))); if (!existing) return null; const [row] = await db .update(setups) .set({ name: data.name, updatedAt: new Date() }) .where(and(eq(setups.id, setupId), eq(setups.userId, userId))) .returning(); return row; } export async function deleteSetup( db: Db, userId: number, setupId: number, ) { const [existing] = await db .select({ id: setups.id }) .from(setups) .where(and(eq(setups.id, setupId), eq(setups.userId, userId))); if (!existing) return false; await db .delete(setups) .where(and(eq(setups.id, setupId), eq(setups.userId, userId))); return true; } export async function syncSetupItems( db: Db, userId: number, setupId: number, itemIds: number[], ) { return await db.transaction(async (tx) => { // Verify the setup belongs to this user const [setup] = await tx .select({ id: setups.id }) .from(setups) .where(and(eq(setups.id, setupId), eq(setups.userId, userId))); if (!setup) return null; // Verify all itemIds belong to this user const validItems = itemIds.length > 0 ? await tx .select({ id: items.id }) .from(items) .where(and(eq(items.userId, userId), inArray(items.id, itemIds))) : []; const validItemIds = new Set(validItems.map((i) => i.id)); const filteredItemIds = itemIds.filter((id) => validItemIds.has(id)); // Save existing classifications before deleting const existing = await tx .select({ itemId: setupItems.itemId, classification: setupItems.classification, }) .from(setupItems) .where(eq(setupItems.setupId, setupId)); const classificationMap = new Map(); for (const row of existing) { classificationMap.set(row.itemId, row.classification); } // Delete all existing items for this setup await tx.delete(setupItems).where(eq(setupItems.setupId, setupId)); // Re-insert only user-owned items, preserving classifications for (const itemId of filteredItemIds) { await tx.insert(setupItems).values({ setupId, itemId, classification: classificationMap.get(itemId) ?? "base", }); } }); } export async function updateItemClassification( db: Db, userId: number, setupId: number, itemId: number, classification: string, ) { // Verify setup belongs to user const [setup] = await db .select({ id: setups.id }) .from(setups) .where(and(eq(setups.id, setupId), eq(setups.userId, userId))); if (!setup) return null; await db .update(setupItems) .set({ classification }) .where( and(eq(setupItems.setupId, setupId), eq(setupItems.itemId, itemId)), ); } export async function removeSetupItem( db: Db, userId: number, setupId: number, itemId: number, ) { // Verify setup belongs to user const [setup] = await db .select({ id: setups.id }) .from(setups) .where(and(eq(setups.id, setupId), eq(setups.userId, userId))); if (!setup) return null; await db .delete(setupItems) .where( and(eq(setupItems.setupId, setupId), eq(setupItems.itemId, itemId)), ); }