diff --git a/src/server/services/profile.service.ts b/src/server/services/profile.service.ts new file mode 100644 index 0000000..779f40c --- /dev/null +++ b/src/server/services/profile.service.ts @@ -0,0 +1,108 @@ +import { and, eq, sql } from "drizzle-orm"; +import { db as prodDb } from "../../db/index.ts"; +import { + categories, + 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`COALESCE(( + SELECT COUNT(*) FROM setup_items + WHERE setup_items.setup_id = setups.id + ), 0)`.as("item_count"), + totalWeight: sql`COALESCE(( + SELECT SUM(items.weight_grams * items.quantity) FROM setup_items + JOIN items ON items.id = setup_items.item_id + WHERE setup_items.setup_id = setups.id + ), 0)`.as("total_weight"), + totalCost: sql`COALESCE(( + SELECT SUM(items.price_cents * items.quantity) FROM setup_items + JOIN items ON items.id = setup_items.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: items.name, + weightGrams: items.weightGrams, + priceCents: items.priceCents, + quantity: items.quantity, + categoryId: items.categoryId, + notes: items.notes, + productUrl: items.productUrl, + imageFilename: items.imageFilename, + 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)) + .where(eq(setupItems.setupId, setupId)); + + return { ...setup, items: itemList }; +} diff --git a/src/server/services/setup.service.ts b/src/server/services/setup.service.ts index da0456f..08c61dd 100644 --- a/src/server/services/setup.service.ts +++ b/src/server/services/setup.service.ts @@ -12,7 +12,7 @@ export async function createSetup( ) { const [row] = await db .insert(setups) - .values({ name: data.name, userId }) + .values({ name: data.name, userId, isPublic: data.isPublic ?? false }) .returning(); return row; @@ -23,6 +23,7 @@ export async function getAllSetups(db: Db, userId: number) { .select({ id: setups.id, name: setups.name, + isPublic: setups.isPublic, createdAt: setups.createdAt, updatedAt: setups.updatedAt, itemCount: sql`COALESCE(( @@ -92,9 +93,17 @@ export async function updateSetup( .where(and(eq(setups.id, setupId), eq(setups.userId, userId))); if (!existing) return null; + const updateData: Record = { + name: data.name, + updatedAt: new Date(), + }; + if (data.isPublic !== undefined) { + updateData.isPublic = data.isPublic; + } + const [row] = await db .update(setups) - .set({ name: data.name, updatedAt: new Date() }) + .set(updateData) .where(and(eq(setups.id, setupId), eq(setups.userId, userId))) .returning();