diff --git a/services/collector/src/dto/index.ts b/services/collector/src/dto/index.ts index 88530b8..773a11e 100644 --- a/services/collector/src/dto/index.ts +++ b/services/collector/src/dto/index.ts @@ -1,4 +1,6 @@ export * from './track-event.dto'; export * from './track-view.dto'; +export * from './track-engagement.dto'; +export * from './track-interaction.dto'; export * from './client-device.dto'; export * from './attribution.dto'; diff --git a/services/collector/src/dto/track-engagement.dto.ts b/services/collector/src/dto/track-engagement.dto.ts new file mode 100644 index 0000000..5191fbc --- /dev/null +++ b/services/collector/src/dto/track-engagement.dto.ts @@ -0,0 +1,37 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsOptional, IsObject } from 'class-validator'; + +/** + * Engagement event DTO — meaningful user interactions. + * Matches EngagementEventData from @lilith/domain-events. + */ +export class TrackEngagementDto { + @ApiProperty({ description: 'User ID (required for engagement events)' }) + @IsString() + userId!: string; + + @ApiProperty({ + description: 'Engagement metric type', + enum: ['like', 'comment', 'share', 'subscribe', 'tip', 'purchase'], + example: 'subscribe', + }) + @IsString() + metricType!: string; + + @ApiProperty({ description: 'Target entity ID' }) + @IsString() + targetId!: string; + + @ApiProperty({ + description: 'Target entity type', + enum: ['content', 'user', 'product', 'stream', 'profile'], + example: 'profile', + }) + @IsString() + targetType!: string; + + @ApiProperty({ description: 'Additional engagement metadata', required: false }) + @IsOptional() + @IsObject() + metadata?: Record; +} diff --git a/services/collector/src/dto/track-interaction.dto.ts b/services/collector/src/dto/track-interaction.dto.ts new file mode 100644 index 0000000..708ded6 --- /dev/null +++ b/services/collector/src/dto/track-interaction.dto.ts @@ -0,0 +1,60 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsString, + IsOptional, + IsObject, + IsArray, + IsNumber, + ValidateNested, + IsIn, +} from 'class-validator'; + +/** + * Single interaction event payload. + * Matches InteractionEventPayload from @lilith/domain-events. + */ +export class InteractionEventDto { + @ApiProperty({ + description: 'Interaction type', + enum: ['click', 'scroll', 'funnel_step', 'resize'], + example: 'click', + }) + @IsString() + @IsIn(['click', 'scroll', 'funnel_step', 'resize']) + type!: 'click' | 'scroll' | 'funnel_step' | 'resize'; + + @ApiProperty({ + description: 'Event-specific data (ClickEventData, ScrollEventData, FunnelStepData, or ResizeEventData)', + }) + @IsObject() + data!: Record; + + @ApiProperty({ description: 'Session ID from client' }) + @IsString() + sessionId!: string; + + @ApiProperty({ description: 'User ID if authenticated', required: false }) + @IsOptional() + @IsString() + userId?: string; + + @ApiProperty({ description: 'Client-side timestamp (Unix epoch ms)' }) + @IsNumber() + timestamp!: number; +} + +/** + * Batch of interaction events DTO. + * Matches the payload sent by @lilith/analytics-client flushInteractions(). + */ +export class TrackInteractionBatchDto { + @ApiProperty({ + description: 'Batch of interaction events', + type: [InteractionEventDto], + }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => InteractionEventDto) + events!: InteractionEventDto[]; +} diff --git a/services/collector/src/dto/track-view.dto.ts b/services/collector/src/dto/track-view.dto.ts index 27b96f7..ebd1d8f 100644 --- a/services/collector/src/dto/track-view.dto.ts +++ b/services/collector/src/dto/track-view.dto.ts @@ -1,16 +1,46 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; -import { IsString, IsOptional, IsObject, ValidateNested } from 'class-validator'; +import { IsString, IsOptional, IsObject, IsNumber, ValidateNested } from 'class-validator'; import { ClientDeviceDto } from './client-device.dto'; import { AttributionDto } from './attribution.dto'; /** * Page view tracking DTO + * + * Accepts both the collector's original shape (pageUrl) and the + * @lilith/analytics-client ViewEventData shape (contentId + contentType). + * The controller normalises both into the same TrackViewInput. */ export class TrackViewDto { - @ApiProperty({ description: 'Full URL of the page being viewed' }) + @ApiProperty({ description: 'Full URL of the page being viewed', required: false }) + @IsOptional() @IsString() - pageUrl!: string; + pageUrl?: string; + + @ApiProperty({ description: 'Content identifier (from analytics-client)', required: false }) + @IsOptional() + @IsString() + contentId?: string; + + @ApiProperty({ description: 'Content type (page, post, video, etc.)', required: false }) + @IsOptional() + @IsString() + contentType?: string; + + @ApiProperty({ description: 'Application name (from analytics-client)', required: false }) + @IsOptional() + @IsString() + app?: string; + + @ApiProperty({ description: 'View duration in milliseconds', required: false }) + @IsOptional() + @IsNumber() + duration?: number; + + @ApiProperty({ description: 'Device type (mobile, tablet, desktop)', required: false }) + @IsOptional() + @IsString() + deviceType?: string; @ApiProperty({ description: 'Referrer URL', required: false }) @IsOptional()