TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { and, eq, sql } from 'drizzle-orm';2import type { Database } from '@rareindex/database';3import { savedSearches, notifications } from '@rareindex/database';4import { logger, newId } from '@rareindex/shared';56const log = logger.child({ job: 'account.saved-searches' });78/**9 * Re-run saved searches that have notifications enabled and report growth in matches.10 * Uses a conservative server-side approximation (title trigram + category param) so it works11 * independently of the web search module; counts are informative, not authoritative.12 */13export async function runSavedSearches(db: Database, now = new Date()): Promise<number> {14 const rows = await db.select().from(savedSearches).where(eq(savedSearches.notify, true));15 let notified = 0;16 for (const s of rows) {17 const params = s.params as Record<string, string>;18 const q = (params.q ?? '').trim();19 const category = params.category ?? params.cat ?? null;20 if (!q && !category) continue;21 const res = (await db.execute(sql`22 select count(*)::int as n from assets a23 where (${q ? sql`(a.title ilike ${'%' + q + '%'} or a.search @@ plainto_tsquery('simple', ${q}))` : sql`true`})24 and (${category ? sql`(a.category_slug = ${category} or a.family_slug = ${category})` : sql`true`})25 `)) as unknown as Array<{ n: number }>;26 const n = res[0]?.n ?? 0;27 const prev = s.lastCount;28 await db.update(savedSearches).set({ lastRunAt: now, lastCount: n }).where(eq(savedSearches.id, s.id));29 if (prev !== null && n > prev) {30 await db.insert(notifications).values({ id: newId('event'), userId: s.userId, kind: 'system', title: `${n - prev} new match${n - prev === 1 ? '' : 'es'} for “${s.name}”`, body: `${n} assets now match your saved search.`, href: s.url, payload: { savedSearchId: s.id, previous: prev, current: n } });31 notified++;32 }33 }34 log.info({ searches: rows.length, notified }, 'saved searches re-run');35 return notified;36}3738export async function markSavedSearchRun(db: Database, id: string, count: number): Promise<void> {39 await db.update(savedSearches).set({ lastRunAt: new Date(), lastCount: count }).where(and(eq(savedSearches.id, id)));40}41