feat(tracking): Add tracking controller methods and routes for new event tracking features

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-04-04 15:14:01 -07:00
parent 4068c597f0
commit 954a71489e

View file

@ -3,6 +3,8 @@ import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import type { Request } from 'express';
import { TrackViewDto } from '../dto/track-view.dto';
import { TrackEngagementDto } from '../dto/track-engagement.dto';
import { TrackInteractionBatchDto } from '../dto/track-interaction.dto';
import {
TrackEventDto,
TrackBatchDto,
@ -14,12 +16,14 @@ import { TrackingService, TrackingResult, BatchTrackingResult } from './tracking
/**
* Event Collection Controller
* Generic analytics event ingestion endpoints
* Analytics event ingestion endpoints
*
* Endpoints:
* - POST /track/view - Page view with device fingerprinting
* - POST /track/engagement - Meaningful user interactions (like, subscribe, purchase)
* - POST /track/interaction - Batched low-level interactions (click, scroll, funnel_step, resize)
* - POST /track/event - Single generic event
* - POST /track/batch - Batch of events
* - POST /track/batch - Batch of generic events
* - POST /track/conversion - Conversion event (higher priority)
* - POST /track/funnel - Funnel step event
* - POST /track/registration-funnel - Registration funnel event (Lilith-specific)
@ -32,18 +36,26 @@ export class TrackingController {
@Post('view')
@ApiOperation({
summary: 'Track page view',
description: 'Records a page view with device fingerprinting and first-touch attribution',
description: 'Records a page view with device fingerprinting and first-touch attribution. Accepts both collector shape (pageUrl) and analytics-client shape (contentId + contentType).',
})
@ApiResponse({ status: 201, description: 'View tracked successfully' })
@ApiResponse({ status: 400, description: 'Invalid request data' })
async trackView(@Body() dto: TrackViewDto, @Req() request: Request): Promise<TrackingResult> {
// Normalise: analytics-client sends contentId as the page URL
const pageUrl = dto.pageUrl ?? dto.contentId ?? '';
return this.trackingService.trackView(
{
pageUrl: dto.pageUrl,
pageUrl,
referrer: dto.referrer,
userId: dto.userId,
sessionId: dto.sessionId,
metadata: dto.metadata,
metadata: {
...dto.metadata,
...(dto.contentType ? { contentType: dto.contentType } : {}),
...(dto.app ? { app: dto.app } : {}),
...(dto.duration !== undefined ? { duration: dto.duration } : {}),
},
clientDevice: dto.clientDevice,
attribution: dto.attribution,
},
@ -51,6 +63,46 @@ export class TrackingController {
);
}
@Post('engagement')
@ApiOperation({
summary: 'Track engagement event',
description: 'Records a meaningful user interaction (like, comment, subscribe, purchase)',
})
@ApiResponse({ status: 201, description: 'Engagement tracked successfully' })
@ApiResponse({ status: 400, description: 'Invalid request data' })
async trackEngagement(@Body() dto: TrackEngagementDto): Promise<TrackingResult> {
return this.trackingService.trackEvent({
eventType: `engagement_${dto.metricType}`,
sessionId: dto.userId, // engagement events are user-scoped
userId: dto.userId,
metadata: {
metricType: dto.metricType,
targetId: dto.targetId,
targetType: dto.targetType,
...dto.metadata,
},
});
}
@Post('interaction')
@ApiOperation({
summary: 'Track interaction events (batched)',
description: 'Records a batch of low-level interactions (clicks, scrolls, funnel steps, resizes)',
})
@ApiResponse({ status: 201, description: 'Interactions tracked successfully' })
@ApiResponse({ status: 400, description: 'Invalid request data' })
async trackInteraction(@Body() dto: TrackInteractionBatchDto): Promise<BatchTrackingResult> {
return this.trackingService.trackBatch(
dto.events.map((event) => ({
eventType: event.type,
sessionId: event.sessionId,
userId: event.userId,
metadata: event.data,
timestamp: event.timestamp,
})),
);
}
@Post('event')
@ApiOperation({
summary: 'Track single event',