diff --git a/services/collector/src/tracking/tracking.controller.ts b/services/collector/src/tracking/tracking.controller.ts index c8cf518..e1d531e 100644 --- a/services/collector/src/tracking/tracking.controller.ts +++ b/services/collector/src/tracking/tracking.controller.ts @@ -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 { + // 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 { + 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 { + 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',