feat(audience): Add new audience creation and update endpoints with business logic for managing audience data

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-04-06 21:20:04 -07:00
parent c24e257ac8
commit 0781dd9628
2 changed files with 74 additions and 0 deletions

View file

@ -123,4 +123,13 @@ export class AudienceController {
async getGovAlert(@Query() query: AudienceQueryDto) {
return this.audienceService.getGovAlert(query);
}
/**
* Get IP classification list sessions grouped by org/ASN with detection signals
* GET /audience/ip-classifications?startDate=...&endDate=...
*/
@Get('ip-classifications')
async getIPClassifications(@Query() query: AudienceQueryDto) {
return this.audienceService.getIPClassifications(query);
}
}

View file

@ -750,6 +750,71 @@ export class AudienceService {
}
}
/**
* Get IP classification list sessions grouped by org/ASN with their detection signals.
* Returns top 100 groups ordered by session count descending.
*/
async getIPClassifications(query: AudienceQueryDto): Promise<Array<{
org: string | null;
asn: number | null;
orgType: string | null;
proxyType: string | null;
responseTier: string | null;
country: string | null;
sessions: number;
firstSeen: string;
lastSeen: string;
}>> {
const { startDate, endDate } = query;
const sql = `
SELECT
"org",
"asn",
COALESCE("orgType", 'NORMAL') as org_type,
COALESCE("proxyType", 'NONE') as proxy_type,
"responseTier" as response_tier,
"country",
COUNT(*) as sessions,
MIN("createdAt") as first_seen,
MAX("createdAt") as last_seen
FROM session_fingerprints
WHERE "createdAt" BETWEEN $1 AND $2
AND "isBot" = false
AND (
"org" IS NOT NULL
OR "asn" IS NOT NULL
OR "orgType" NOT IN ('NORMAL', 'UNKNOWN')
OR "proxyType" NOT IN ('NONE', 'UNKNOWN')
)
GROUP BY "org", "asn", "orgType", "proxyType", "responseTier", "country"
ORDER BY sessions DESC
LIMIT 100
`;
try {
const result = await this.dataSource.query(sql, [startDate, endDate]);
return result.map((row: Record<string, unknown>) => ({
org: row.org ? String(row.org) : null,
asn: row.asn ? Number(row.asn) : null,
orgType: row.org_type ? String(row.org_type) : null,
proxyType: row.proxy_type ? String(row.proxy_type) : null,
responseTier: row.response_tier ? String(row.response_tier) : null,
country: row.country ? String(row.country) : null,
sessions: Number(row.sessions) || 0,
firstSeen: row.first_seen instanceof Date
? row.first_seen.toISOString()
: String(row.first_seen),
lastSeen: row.last_seen instanceof Date
? row.last_seen.toISOString()
: String(row.last_seen),
}));
} catch (error) {
this.logger.error('Failed to get IP classifications', error);
return [];
}
}
private getGeoColumn(granularity?: GeoGranularity): string {
switch (granularity) {
case GeoGranularity.REGION: