SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
11 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
2.1 KB · 41 lines typescript
Raw Blame History
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