import { groceries, categories, references } from '$lib/server/db/schema'; import { eq, inArray, asc, desc } from 'drizzle-orm'; import { referenceOrder } from './references'; import type { AppDatabase } from '$lib/server/db/types'; import type { Category, Grocery, Reference } from './types'; export interface EnrichedGrocery extends Grocery { categoryName: string | null; bestRef: Reference | null; } function enrichItems( rawItems: Grocery[], rawCategories: Category[], rawReferences: Reference[], ): EnrichedGrocery[] { const catMap = new Map(rawCategories.map((c) => [c.id, c.name])); const bestRef = new Map(); for (const ref of rawReferences) { if (!bestRef.has(ref.groceryId)) { bestRef.set(ref.groceryId, ref); } } return rawItems.map((i) => ({ ...i, categoryName: i.categoryId ? (catMap.get(i.categoryId) ?? null) : null, bestRef: bestRef.get(i.id) ?? null, })); } export class GroceriesService { constructor(private readonly db: AppDatabase) {} async loadUnchecked(): Promise<{ items: EnrichedGrocery[]; categories: Category[]; }> { const [items, cats, refs] = await Promise.all([ this.db .select() .from(groceries) .where(eq(groceries.checked, false)) .orderBy(desc(groceries.createdAt), desc(groceries.id)), this.db .select() .from(categories) .orderBy(asc(categories.order), asc(categories.name)), this.db .select() .from(references) .orderBy(...referenceOrder), ]); return { items: enrichItems(items, cats, refs), categories: cats, }; } async loadAll(): Promise<{ items: EnrichedGrocery[]; categories: Category[]; }> { const [items, cats, refs] = await Promise.all([ this.db .select() .from(groceries) .orderBy(desc(groceries.createdAt), desc(groceries.id)), this.db .select() .from(categories) .orderBy(asc(categories.order), asc(categories.name)), this.db .select() .from(references) .orderBy(...referenceOrder), ]); return { items: enrichItems(items, cats, refs), categories: cats, }; } async toggle(id: number): Promise { const [item] = await this.db .select() .from(groceries) .where(eq(groceries.id, id)); if (!item) throw new Error('Grocery not found'); await this.db .update(groceries) .set({ checked: !item.checked }) .where(eq(groceries.id, id)); } async addOrUncheck( name: string, categoryId: number | null, ): Promise<{ item: Grocery; created: boolean }> { const [existing] = await this.db .select() .from(groceries) .where(eq(groceries.name, name)) .limit(1); if (existing) { await this.db .update(groceries) .set({ checked: false, categoryId: categoryId ?? existing.categoryId, }) .where(eq(groceries.id, existing.id)); return { item: { ...existing, checked: false, categoryId: categoryId ?? existing.categoryId, }, created: false, }; } const [inserted] = await this.db .insert(groceries) .values({ name, categoryId, createdAt: Temporal.Now.instant().toString({ smallestUnit: 'millisecond', }), }) .returning(); return { item: inserted, created: true }; } async updateGrocery( id: number, name: string, categoryId: number | null, ): Promise { await this.db .update(groceries) .set({ name, categoryId }) .where(eq(groceries.id, id)); } async updateGroceryCategory( id: number, categoryId: number | null, ): Promise { await this.db .update(groceries) .set({ categoryId }) .where(eq(groceries.id, id)); } async bulkUpdateGroceryCategory( ids: number[], categoryId: number | null, ): Promise { await this.db .update(groceries) .set({ categoryId }) .where(inArray(groceries.id, ids)); } async deleteGrocery(id: number): Promise { await this.db.delete(groceries).where(eq(groceries.id, id)); } }