aboutsummaryrefslogtreecommitdiff
path: root/src/lib/server/services/groceries.ts
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-10-18 22:22:55 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-10-18 22:22:55 +0200
commit4efed80a2f8dc238b420eadf97d56c3eccaadb76 (patch)
tree7732a80745622c097a9e75dfb056603bceb10a23 /src/lib/server/services/groceries.ts
parent0685e9ece66fbcb09c9457027cf7bb2107cc0171 (diff)
refactor: inject the database into every servicemain
Groceries, projects, references and routines become classes holding the Drizzle handle, wired once in services/index.ts alongside categories. Tests build their own instance on an in-memory database. Drops the unused projects.loadAll and projects.rename.
Diffstat (limited to 'src/lib/server/services/groceries.ts')
-rw-r--r--src/lib/server/services/groceries.ts282
1 files changed, 145 insertions, 137 deletions
diff --git a/src/lib/server/services/groceries.ts b/src/lib/server/services/groceries.ts
index 092aba0..9f4722b 100644
--- a/src/lib/server/services/groceries.ts
+++ b/src/lib/server/services/groceries.ts
@@ -1,7 +1,7 @@
-import { db as defaultDb } from '$lib/server/db';
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 {
@@ -9,159 +9,167 @@ export interface EnrichedGrocery extends Grocery {
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), desc(groceries.id)),
- db
- .select()
- .from(categories)
- .orderBy(asc(categories.order), asc(categories.name)),
- db
- .select()
- .from(references)
- .orderBy(...referenceOrder),
- ]);
+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 {
- items: enrichItems(items, cats, refs),
- categories: cats,
- };
+ return rawItems.map((i) => ({
+ ...i,
+ categoryName: i.categoryId ? (catMap.get(i.categoryId) ?? null) : null,
+ bestRef: bestRef.get(i.id) ?? null,
+ }));
}
-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), desc(groceries.id)),
- db
- .select()
- .from(categories)
- .orderBy(asc(categories.order), asc(categories.name)),
- db
- .select()
- .from(references)
- .orderBy(...referenceOrder),
- ]);
-
- return {
- items: enrichItems(items, cats, refs),
- categories: cats,
- };
-}
+export class GroceriesService {
+ constructor(private readonly db: AppDatabase) {}
-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');
+ 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),
+ ]);
- await db
- .update(groceries)
- .set({ checked: !item.checked })
- .where(eq(groceries.id, id));
-}
+ return {
+ items: enrichItems(items, cats, refs),
+ categories: cats,
+ };
+ }
-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);
+ 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),
+ ]);
- 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,
+ items: enrichItems(items, cats, refs),
+ categories: cats,
};
}
- const [inserted] = await db
- .insert(groceries)
- .values({
- name,
- categoryId,
- createdAt: Temporal.Now.instant().toString({
- smallestUnit: 'millisecond',
- }),
- })
- .returning();
+ async toggle(id: number): Promise<void> {
+ const [item] = await this.db
+ .select()
+ .from(groceries)
+ .where(eq(groceries.id, id));
+ if (!item) throw new Error('Grocery not found');
- return { item: inserted, created: true };
-}
+ await this.db
+ .update(groceries)
+ .set({ checked: !item.checked })
+ .where(eq(groceries.id, id));
+ }
-export async function updateGrocery(
- id: number,
- name: string,
- categoryId: number | null,
- db = defaultDb,
-): Promise<void> {
- await db
- .update(groceries)
- .set({ name, categoryId })
- .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);
-export async function updateGroceryCategory(
- id: number,
- categoryId: number | null,
- db = defaultDb,
-): Promise<void> {
- await db.update(groceries).set({ categoryId }).where(eq(groceries.id, id));
-}
+ 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,
+ };
+ }
-export async function bulkUpdateGroceryCategory(
- ids: number[],
- categoryId: number | null,
- db = defaultDb,
-): Promise<void> {
- await db
- .update(groceries)
- .set({ categoryId })
- .where(inArray(groceries.id, ids));
-}
+ const [inserted] = await this.db
+ .insert(groceries)
+ .values({
+ name,
+ categoryId,
+ createdAt: Temporal.Now.instant().toString({
+ smallestUnit: 'millisecond',
+ }),
+ })
+ .returning();
-export async function deleteGrocery(id: number, db = defaultDb): Promise<void> {
- await db.delete(groceries).where(eq(groceries.id, id));
-}
+ return { item: inserted, created: true };
+ }
-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);
- }
+ async updateGrocery(
+ id: number,
+ name: string,
+ categoryId: number | null,
+ ): Promise<void> {
+ await this.db
+ .update(groceries)
+ .set({ name, categoryId })
+ .where(eq(groceries.id, id));
}
- return rawItems.map((i) => ({
- ...i,
- categoryName: i.categoryId ? (catMap.get(i.categoryId) ?? null) : null,
- bestRef: bestRef.get(i.id) ?? null,
- }));
+ async updateGroceryCategory(
+ id: number,
+ categoryId: number | null,
+ ): Promise<void> {
+ await this.db
+ .update(groceries)
+ .set({ categoryId })
+ .where(eq(groceries.id, id));
+ }
+
+ async bulkUpdateGroceryCategory(
+ ids: number[],
+ categoryId: number | null,
+ ): Promise<void> {
+ await this.db
+ .update(groceries)
+ .set({ categoryId })
+ .where(inArray(groceries.id, ids));
+ }
+
+ async deleteGrocery(id: number): Promise<void> {
+ await this.db.delete(groceries).where(eq(groceries.id, id));
+ }
}