157 lines
4.6 KiB
TypeScript
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
|
|
}
|
|
}
|