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,
}));
}
|