refactor(corp-filter): ♻️ Modularize filtering logic, add type safety, and optimize performance in corporate data filtering utility
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
1211dfcb28
commit
05bbe97a09
1 changed files with 86 additions and 0 deletions
86
services/api/src/common/corp-filter.util.ts
Normal file
86
services/api/src/common/corp-filter.util.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import { DataSource } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Cross-corp scoping helper for /api/* endpoints.
|
||||
*
|
||||
* Pattern:
|
||||
* const corpId = await resolveCorpId(this.dataSource, query.corp);
|
||||
* const corpClause = corpSessionFilter(NEXT_PARAM_IDX, corpId);
|
||||
* const sql = `... WHERE sf."createdAt" BETWEEN $1 AND $2${corpClause}`;
|
||||
* const params = corpId === null ? [start, end] : [start, end, corpId];
|
||||
*
|
||||
* When `query.corp` is undefined → no filter, behaviour unchanged (firehose).
|
||||
* When `query.corp` resolves to a valid corp → AND sf.sessionId IN (... corp_id=$N)
|
||||
* is appended. Subquery hits idx_raw_events_corp_id_ts (corp_id leading).
|
||||
*/
|
||||
|
||||
const slugCache = new Map<string, number>();
|
||||
|
||||
/**
|
||||
* Resolve a corp slug to its surrogate id. Process-memoized.
|
||||
* Returns null when slug is undefined/empty (no filter requested).
|
||||
* Throws when slug is provided but doesn't match any corp.
|
||||
*/
|
||||
export async function resolveCorpId(
|
||||
ds: DataSource,
|
||||
slug: string | undefined,
|
||||
): Promise<number | null> {
|
||||
if (slug === undefined || slug.length === 0) return null;
|
||||
|
||||
const cached = slugCache.get(slug);
|
||||
if (cached !== undefined) return cached;
|
||||
|
||||
let rows: Array<{ id: number }>;
|
||||
try {
|
||||
rows = await ds.query<Array<{ id: number }>>(
|
||||
`SELECT id FROM corps WHERE slug = $1 LIMIT 1`,
|
||||
[slug],
|
||||
);
|
||||
} catch (cause) {
|
||||
throw new Error(
|
||||
`corp-filter: failed to resolve slug '${slug}': ${cause instanceof Error ? cause.message : String(cause)}`,
|
||||
{ cause },
|
||||
);
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
throw new Error(`Unknown corp slug: ${slug}`);
|
||||
}
|
||||
const id = Number(rows[0].id);
|
||||
slugCache.set(slug, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL fragment to append to a `WHERE` clause that filters by sessionId →
|
||||
* raw_events.corp_id. Returns an empty string when corpId is null, so the
|
||||
* caller can unconditionally interpolate it.
|
||||
*
|
||||
* The fragment uses the next parameter index passed in (`paramIdx`). The
|
||||
* caller is responsible for appending `corpId` to its parameter array iff
|
||||
* the fragment is non-empty (i.e. iff corpId !== null).
|
||||
*
|
||||
* Assumes the session_fingerprints table is aliased as `sf` in the caller's
|
||||
* SQL (which is the universal convention across the existing services).
|
||||
*/
|
||||
export function corpSessionFilter(paramIdx: number, corpId: number | null): string {
|
||||
if (corpId === null) return '';
|
||||
return ` AND sf."sessionId" IN (
|
||||
SELECT DISTINCT "sessionId" FROM raw_events WHERE corp_id = $${paramIdx}
|
||||
)`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant for queries that read raw_events directly (no session_fingerprints
|
||||
* involvement). Aliased `re` or unaliased — caller passes the alias.
|
||||
*
|
||||
* Returns ` AND <alias>.corp_id = $N` or empty.
|
||||
*/
|
||||
export function corpRawEventsFilter(
|
||||
paramIdx: number,
|
||||
corpId: number | null,
|
||||
alias: string = 'raw_events',
|
||||
): string {
|
||||
if (corpId === null) return '';
|
||||
return ` AND ${alias}.corp_id = $${paramIdx}`;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue