feat(collector): Add structured DTOs for user interaction tracking (TrackViewDto, TrackEngagementDto, TrackInteractionDto) with re-exports in collector/index.ts

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-04-04 15:14:01 -07:00
parent ea3cddccde
commit 0e5e4dff58
4 changed files with 132 additions and 3 deletions

View file

@ -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';

View file

@ -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<string, unknown>;
}

View file

@ -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<string, unknown>;
@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[];
}

View file

@ -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()