feat(16-02): add userId scoping to item, category, totals, and CSV services

- All functions accept userId as second parameter, no more prodDb defaults
- All queries filter by eq(table.userId, userId) for data isolation
- Get-by-id, update, delete use and() for composite id+userId conditions
- deleteCategory uses dynamic getOrCreateUncategorized(db, userId) not hardcoded ID
- CSV import scopes category lookup/creation and item creation to userId
- CSV export filters items by userId
- Category service converted from sync SQLite to async Postgres patterns
This commit is contained in:
2026-04-05 10:41:59 +02:00
parent ad309510af
commit 8d85d2839e
4 changed files with 111 additions and 66 deletions

View File

@@ -1,10 +1,10 @@
import { eq, sql } from "drizzle-orm";
import { and, eq, sql } from "drizzle-orm";
import { db as prodDb } from "../../db/index.ts";
import { categories, items } from "../../db/schema.ts";
type Db = typeof prodDb;
export async function getCategoryTotals(db: Db = prodDb) {
export async function getCategoryTotals(db: Db, userId: number) {
return db
.select({
categoryId: items.categoryId,
@@ -16,17 +16,19 @@ export async function getCategoryTotals(db: Db = prodDb) {
})
.from(items)
.innerJoin(categories, eq(items.categoryId, categories.id))
.where(eq(items.userId, userId))
.groupBy(items.categoryId, categories.name, categories.icon);
}
export async function getGlobalTotals(db: Db = prodDb) {
export async function getGlobalTotals(db: Db, userId: number) {
const [row] = await db
.select({
totalWeight: sql<number>`COALESCE(SUM(${items.weightGrams} * ${items.quantity}), 0)`,
totalCost: sql<number>`COALESCE(SUM(${items.priceCents} * ${items.quantity}), 0)`,
itemCount: sql<number>`COUNT(*)`,
})
.from(items);
.from(items)
.where(eq(items.userId, userId));
return row;
}