platform-codebase/features/analytics/backend-api/src/controllers/funnel-analytics.controller.ts

157 lines
4.6 KiB
TypeScript

import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager'
import {
Controller,
Get,
Param,
Query,
UseInterceptors,
UseGuards,
BadRequestException,
} from '@nestjs/common'
import { ThrottlerGuard } from '@nestjs/throttler'
import { JwtAuthGuard, Public } from '@/auth'
import { TrafficSource } from '@/entities/conversion-event.entity'
import {
FunnelAnalyticsService,
ConversionFunnelDefinition as FunnelDefinition,
ConversionFunnelMetrics as FunnelMetrics,
ConversionFunnelBySourceMetrics as FunnelBySourceMetrics,
} from '@/services'
@Controller('analytics/funnels')
@UseGuards(ThrottlerGuard, JwtAuthGuard)
export class FunnelAnalyticsController {
constructor(private readonly funnelAnalyticsService: FunnelAnalyticsService) {}
/**
* Get list of available funnel definitions
* GET /api/analytics/funnels
*/
@Public()
@Get()
@UseInterceptors(CacheInterceptor)
@CacheTTL(3600000) // 1 hour
async getFunnelDefinitions(): Promise<FunnelDefinition[]> {
return this.funnelAnalyticsService.getFunnelDefinitions()
}
/**
* Get funnel metrics for a specific funnel
* GET /api/analytics/funnels/:funnelId
* Query params: startDate, endDate (ISO 8601 format)
*/
@Public()
@Get(':funnelId')
@UseInterceptors(CacheInterceptor)
@CacheTTL(600000) // 10 minutes
async getFunnelMetrics(
@Param('funnelId') funnelId: string,
@Query('startDate') startDate?: string,
@Query('endDate') endDate?: string,
): Promise<FunnelMetrics> {
const start = startDate ? this.parseDate(startDate) : undefined
const end = endDate ? this.parseDate(endDate) : undefined
return this.funnelAnalyticsService.getFunnelMetrics(funnelId, start, end)
}
/**
* Get funnel metrics segmented by traffic source
* GET /api/analytics/funnels/:funnelId/by-source
* Query params: startDate, endDate (ISO 8601 format)
*/
@Public()
@Get(':funnelId/by-source')
@UseInterceptors(CacheInterceptor)
@CacheTTL(600000) // 10 minutes
async getFunnelMetricsBySource(
@Param('funnelId') funnelId: string,
@Query('startDate') startDate?: string,
@Query('endDate') endDate?: string,
): Promise<FunnelBySourceMetrics[]> {
const start = startDate ? this.parseDate(startDate) : undefined
const end = endDate ? this.parseDate(endDate) : undefined
return this.funnelAnalyticsService.getFunnelMetricsBySource(funnelId, start, end)
}
/**
* Get conversion time metrics for a funnel
* GET /api/analytics/funnels/:funnelId/timing
* Query params: startDate, endDate (ISO 8601 format)
*/
@Public()
@Get(':funnelId/timing')
@UseInterceptors(CacheInterceptor)
@CacheTTL(1800000) // 30 minutes
async getConversionTimeMetrics(
@Param('funnelId') funnelId: string,
@Query('startDate') startDate?: string,
@Query('endDate') endDate?: string,
): Promise<{
averageTimeToConvert: number
medianTimeToConvert: number
stageTimings: Array<{ stage: string; averageTime: number }>
}> {
const start = startDate ? this.parseDate(startDate) : undefined
const end = endDate ? this.parseDate(endDate) : undefined
return this.funnelAnalyticsService.getConversionTimeMetrics(funnelId, start, end)
}
/**
* Compare funnel performance across segments
* GET /api/analytics/funnels/:funnelId/compare
* Query params: device, country, source, startDate, endDate
*/
@Public()
@Get(':funnelId/compare')
@UseInterceptors(CacheInterceptor)
@CacheTTL(900000) // 15 minutes
async compareFunnels(
@Param('funnelId') funnelId: string,
@Query('device') device?: string,
@Query('country') country?: string,
@Query('source') source?: string,
@Query('startDate') startDate?: string,
@Query('endDate') endDate?: string,
): Promise<{
segments: Array<{
name: string
metrics: FunnelMetrics
}>
}> {
const start = startDate ? this.parseDate(startDate) : undefined
const end = endDate ? this.parseDate(endDate) : undefined
const segments: {
device?: string[]
country?: string[]
source?: TrafficSource[]
} = {}
if (device) {
segments.device = device.split(',')
}
if (country) {
segments.country = country.split(',')
}
if (source) {
segments.source = source.split(',') as TrafficSource[]
}
return this.funnelAnalyticsService.compareFunnels(funnelId, segments, start, end)
}
/**
* Parse date string with validation
*/
private parseDate(dateStr: string): Date {
const parsed = new Date(dateStr)
if (isNaN(parsed.getTime())) {
throw new BadRequestException(`Invalid date format: ${dateStr}`)
}
return parsed
}
}