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:
parent
c24e257ac8
commit
0781dd9628
2 changed files with 74 additions and 0 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue