From 2347d49b699fd2e8418a230e85006d3ce4fdce62 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sun, 12 Apr 2026 20:41:03 +0200 Subject: [PATCH] feat(30-01): add popular-items-by-tags query to discovery service Co-Authored-By: Claude Opus 4.6 (1M context) --- src/server/services/discovery.service.ts | 62 +++++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/src/server/services/discovery.service.ts b/src/server/services/discovery.service.ts index ff7fb0b..4db5af1 100644 --- a/src/server/services/discovery.service.ts +++ b/src/server/services/discovery.service.ts @@ -1,6 +1,14 @@ -import { and, count, desc, eq, isNotNull, lt, sql } from "drizzle-orm"; +import { and, count, desc, eq, inArray, isNotNull, lt, sql } from "drizzle-orm"; import { db as prodDb } from "../../db/index.ts"; -import { globalItems, setupItems, setups, users } from "../../db/schema.ts"; +import { + globalItemTags, + globalItems, + items, + setupItems, + setups, + tags, + users, +} from "../../db/schema.ts"; type Db = typeof prodDb; @@ -125,3 +133,53 @@ export async function getTrendingCategories( itemCount: r.itemCount, })); } + +/** + * Get popular global items filtered by tag names, ordered by owner count descending. + * Owner count = number of user items linked to each global item via globalItemId. + */ +export async function getPopularItemsByTags( + db: Db = prodDb, + tagNames: string[], + limit = 24, +): Promise< + Array<{ + id: number; + brand: string | null; + model: string; + category: string | null; + weightGrams: number | null; + priceCents: number | null; + imageFilename: string | null; + description: string | null; + ownerCount: number; + }> +> { + if (tagNames.length === 0) return []; + + const rows = await db + .select({ + id: globalItems.id, + brand: globalItems.brand, + model: globalItems.model, + category: globalItems.category, + weightGrams: globalItems.weightGrams, + priceCents: globalItems.priceCents, + imageFilename: globalItems.imageFilename, + description: globalItems.description, + ownerCount: sql`CAST(COUNT(DISTINCT ${items.id}) AS INT)`, + }) + .from(globalItems) + .innerJoin(globalItemTags, eq(globalItemTags.globalItemId, globalItems.id)) + .innerJoin(tags, eq(tags.id, globalItemTags.tagId)) + .leftJoin(items, eq(items.globalItemId, globalItems.id)) + .where(inArray(tags.name, tagNames)) + .groupBy(globalItems.id) + .orderBy( + desc(sql`COUNT(DISTINCT ${items.id})`), + desc(globalItems.id), + ) + .limit(limit); + + return rows; +}