Files
GearBox/src/server/services/profile.service.ts
Jean-Luc Makiola 0a233c754d feat(19-03): add COALESCE merge for reference items in secondary services
- Setup service: LEFT JOIN globalItems in getAllSetups totals and getSetupWithItems
- Totals service: LEFT JOIN globalItems in getCategoryTotals and getGlobalTotals
- Profile service: LEFT JOIN globalItems in getPublicProfile totals and getPublicSetupWithItems
- CSV service: LEFT JOIN globalItems in exportItemsCsv for merged name/weight/price
2026-04-06 00:26:13 +02:00

136 lines
3.9 KiB
TypeScript

import { and, eq, sql } from "drizzle-orm";
import type { db as prodDb } from "../../db/index.ts";
import {
categories,
globalItems,
items,
setupItems,
setups,
users,
} from "../../db/schema.ts";
import type { UpdateProfile } from "../../shared/types.ts";
type Db = typeof prodDb;
export async function updateProfile(
db: Db,
userId: number,
data: UpdateProfile,
) {
const [existing] = await db.select().from(users).where(eq(users.id, userId));
if (!existing) return null;
// If no fields to update, return existing user
const hasUpdates = Object.values(data).some((v) => v !== undefined);
if (!hasUpdates) return existing;
const [updated] = await db
.update(users)
.set(data)
.where(eq(users.id, userId))
.returning();
return updated;
}
export async function getPublicProfile(db: Db, userId: number) {
const [user] = await db
.select({
id: users.id,
displayName: users.displayName,
avatarUrl: users.avatarUrl,
bio: users.bio,
})
.from(users)
.where(eq(users.id, userId));
if (!user) return null;
const publicSetups = await db
.select({
id: setups.id,
name: setups.name,
createdAt: setups.createdAt,
itemCount: sql<number>`COALESCE((
SELECT COUNT(*) FROM setup_items
WHERE setup_items.setup_id = setups.id
), 0)`.as("item_count"),
totalWeight: sql<number>`COALESCE((
SELECT SUM(
COALESCE(
CASE WHEN items.global_item_id IS NOT NULL THEN global_items.weight_grams ELSE NULL END,
items.weight_grams
) * items.quantity
) FROM setup_items
JOIN items ON items.id = setup_items.item_id
LEFT JOIN global_items ON global_items.id = items.global_item_id
WHERE setup_items.setup_id = setups.id
), 0)`.as("total_weight"),
totalCost: sql<number>`COALESCE((
SELECT SUM(
COALESCE(
CASE WHEN items.global_item_id IS NOT NULL THEN global_items.price_cents ELSE NULL END,
items.price_cents
) * items.quantity
) FROM setup_items
JOIN items ON items.id = setup_items.item_id
LEFT JOIN global_items ON global_items.id = items.global_item_id
WHERE setup_items.setup_id = setups.id
), 0)`.as("total_cost"),
})
.from(setups)
.where(and(eq(setups.userId, userId), eq(setups.isPublic, true)));
return { ...user, setups: publicSetups };
}
export async function getPublicSetupWithItems(db: Db, setupId: number) {
const [setup] = await db
.select()
.from(setups)
.where(and(eq(setups.id, setupId), eq(setups.isPublic, true)));
if (!setup) return null;
const itemList = await db
.select({
id: items.id,
name: sql<string>`COALESCE(
CASE WHEN ${items.globalItemId} IS NOT NULL
THEN ${globalItems.brand} || ' ' || ${globalItems.model}
ELSE ${items.name}
END,
${items.name}
)`.as("name"),
weightGrams: sql<number | null>`COALESCE(
CASE WHEN ${items.globalItemId} IS NOT NULL THEN ${globalItems.weightGrams} ELSE NULL END,
${items.weightGrams}
)`.as("weight_grams"),
priceCents: sql<number | null>`COALESCE(
CASE WHEN ${items.globalItemId} IS NOT NULL THEN ${globalItems.priceCents} ELSE NULL END,
${items.priceCents}
)`.as("price_cents"),
quantity: items.quantity,
categoryId: items.categoryId,
notes: items.notes,
productUrl: items.productUrl,
imageFilename: sql<string | null>`COALESCE(
${items.imageFilename},
CASE WHEN ${items.globalItemId} IS NOT NULL THEN ${globalItems.imageUrl} ELSE NULL END
)`.as("image_filename"),
globalItemId: items.globalItemId,
createdAt: items.createdAt,
updatedAt: items.updatedAt,
categoryName: categories.name,
categoryIcon: categories.icon,
classification: setupItems.classification,
})
.from(setupItems)
.innerJoin(items, eq(setupItems.itemId, items.id))
.innerJoin(categories, eq(items.categoryId, categories.id))
.leftJoin(globalItems, eq(items.globalItemId, globalItems.id))
.where(eq(setupItems.setupId, setupId));
return { ...setup, items: itemList };
}