aboutsummaryrefslogtreecommitdiff
path: root/src/lib/server/services/groceries.ts
blob: fb422248f2bf93f7a4e95e89777ed1c300b99bc5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122

import { db as defaultDb } from '$lib/server/db';
import { groceries, categories, references } from '$lib/server/db/schema';
import { eq, desc } from 'drizzle-orm';
import type { LibSQLDatabase } from 'drizzle-orm/libsql';
import type * as schema from '$lib/server/db/schema';
import type { Category, Grocery, Reference } from './types';

export type AppDatabase = LibSQLDatabase<typeof schema>;

export interface EnrichedGrocery extends Grocery {
	categoryName: string | null;
	bestRef: Reference | null;
}

export async function loadUnchecked(
	db = defaultDb,
): Promise<{ items: EnrichedGrocery[]; categories: Category[] }> {
	const [items, cats, refs] = await Promise.all([
		db
			.select()
			.from(groceries)
			.where(eq(groceries.checked, false))
			.orderBy(desc(groceries.createdAt)),
		db.select().from(categories).orderBy(desc(categories.name)),
		db.select().from(references).orderBy(desc(references.qualityScore)),
	]);

	return {
		items: enrichItems(items, cats, refs),
		categories: cats,
	};
}

export async function loadAll(
	db = defaultDb,
): Promise<{ items: EnrichedGrocery[]; categories: Category[] }> {
	const [items, cats, refs] = await Promise.all([
		db.select().from(groceries).orderBy(desc(groceries.createdAt)),
		db.select().from(categories).orderBy(desc(categories.name)),
		db.select().from(references).orderBy(desc(references.qualityScore)),
	]);

	return {
		items: enrichItems(items, cats, refs),
		categories: cats,
	};
}

export async function toggle(id: number, db = defaultDb): Promise<void> {
	const [item] = await db.select().from(groceries).where(eq(groceries.id, id));
	if (!item) throw new Error('Grocery not found');

	await db
		.update(groceries)
		.set({ checked: !item.checked })
		.where(eq(groceries.id, id));
}

export async function addOrUncheck(
	name: string,
	categoryId: number | null,
	db = defaultDb,
): Promise<{ item: Grocery; created: boolean }> {
	const [existing] = await db
		.select()
		.from(groceries)
		.where(eq(groceries.name, name))
		.limit(1);

	if (existing) {
		await 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 db
		.insert(groceries)
		.values({
			name,
			categoryId,
			createdAt: new Date().toISOString(),
		})
		.returning();

	return { item: inserted, created: true };
}

export async function deleteGrocery(id: number, db = defaultDb): Promise<void> {
	await db.delete(groceries).where(eq(groceries.id, id));
}

function enrichItems(
	rawItems: Grocery[],
	rawCategories: Category[],
	rawReferences: Reference[],
): EnrichedGrocery[] {
	const catMap = new Map(rawCategories.map((c) => [c.id, c.name]));
	const bestRef = new Map<number, Reference>();
	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,
	}));
}