From 25146df4b8d259dfd83eec17f90272898d37cbbd Mon Sep 17 00:00:00 2001 From: Lilith Date: Sun, 18 Jan 2026 09:20:26 -0800 Subject: [PATCH] =?UTF-8?q?feat(analytics):=20=E2=9C=A8=20Add=20comprehens?= =?UTF-8?q?ive=20analytics=20tracking=20with=20new=20DTOs,=20controllers,?= =?UTF-8?q?=20and=20services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/analytics.controller.spec.ts | 31 ++++++++++++++----- .../src/controllers/analytics.controller.ts | 15 ++++++++- .../backend-api/src/controllers/index.ts | 0 .../dashboard/dashboard.controller.spec.ts | 0 .../src/dashboard/dashboard.controller.ts | 0 .../src/dashboard/dashboard.module.ts | 0 .../src/dashboard/dashboard.service.ts | 0 .../backend-api/src/dto/client-device.dto.ts | 0 .../backend-api/src/dto/cross-domain.dto.ts | 0 .../analytics/backend-api/src/dto/index.ts | 26 ++++++++++++++++ .../src/dto/track-engagement.dto.ts | 0 .../backend-api/src/dto/track-revenue.dto.ts | 0 .../src/dto/track-view-attribution.dto.ts | 0 .../backend-api/src/dto/track-view.dto.ts | 0 .../src/entities/ab-test.entity.ts | 0 15 files changed, 64 insertions(+), 8 deletions(-) mode change 100644 => 100755 features/analytics/backend-api/src/controllers/analytics.controller.spec.ts mode change 100644 => 100755 features/analytics/backend-api/src/controllers/analytics.controller.ts mode change 100644 => 100755 features/analytics/backend-api/src/controllers/index.ts mode change 100644 => 100755 features/analytics/backend-api/src/dashboard/dashboard.controller.spec.ts mode change 100644 => 100755 features/analytics/backend-api/src/dashboard/dashboard.controller.ts mode change 100644 => 100755 features/analytics/backend-api/src/dashboard/dashboard.module.ts mode change 100644 => 100755 features/analytics/backend-api/src/dashboard/dashboard.service.ts mode change 100644 => 100755 features/analytics/backend-api/src/dto/client-device.dto.ts mode change 100644 => 100755 features/analytics/backend-api/src/dto/cross-domain.dto.ts mode change 100644 => 100755 features/analytics/backend-api/src/dto/index.ts mode change 100644 => 100755 features/analytics/backend-api/src/dto/track-engagement.dto.ts mode change 100644 => 100755 features/analytics/backend-api/src/dto/track-revenue.dto.ts mode change 100644 => 100755 features/analytics/backend-api/src/dto/track-view-attribution.dto.ts mode change 100644 => 100755 features/analytics/backend-api/src/dto/track-view.dto.ts mode change 100644 => 100755 features/analytics/backend-api/src/entities/ab-test.entity.ts diff --git a/features/analytics/backend-api/src/controllers/analytics.controller.spec.ts b/features/analytics/backend-api/src/controllers/analytics.controller.spec.ts old mode 100644 new mode 100755 index 50afc5fe2..44fb7ea65 --- a/features/analytics/backend-api/src/controllers/analytics.controller.spec.ts +++ b/features/analytics/backend-api/src/controllers/analytics.controller.spec.ts @@ -28,6 +28,7 @@ import { AdminAnalyticsService, TimePeriod, } from '@/services' +import { SubscriptionFunnelService } from '@/services/subscription-funnel.service' import { ContentType, DeviceType, @@ -395,7 +396,7 @@ describe('AnalyticsController', () => { it('should generate report without date range', async () => { const result = await controller.generateReport( mockUser, - ReportType.REVENUE, + ReportType.DAILY, undefined, undefined, ) @@ -403,7 +404,7 @@ describe('AnalyticsController', () => { expect(result).toEqual({ data: 'report' }) expect(mockReportsService.generateReport).toHaveBeenCalledWith( mockUser.id, - ReportType.REVENUE, + ReportType.DAILY, undefined, undefined, ) @@ -413,12 +414,12 @@ describe('AnalyticsController', () => { const startDate = '2024-01-01' const endDate = '2024-01-31' - await controller.generateReport(mockUser, ReportType.ENGAGEMENT, startDate, endDate) + await controller.generateReport(mockUser, ReportType.WEEKLY, startDate, endDate) expect(mockReportsService.generateReport).toHaveBeenCalled() const [userId, type, start, end] = (mockReportsService.generateReport as any).mock.calls[0] expect(userId).toBe(mockUser.id) - expect(type).toBe(ReportType.ENGAGEMENT) + expect(type).toBe(ReportType.WEEKLY) expect(start).toBeInstanceOf(Date) expect(end).toBeInstanceOf(Date) }) @@ -434,21 +435,21 @@ describe('AnalyticsController', () => { await controller.exportReportCsv( mockResponse, mockUser, - ReportType.REVENUE, + ReportType.DAILY, undefined, undefined, ) expect(mockReportsService.exportToCsv).toHaveBeenCalledWith( mockUser.id, - ReportType.REVENUE, + ReportType.DAILY, undefined, undefined, ) expect(mockResponse.setHeader).toHaveBeenCalledWith('Content-Type', 'text/csv') expect(mockResponse.setHeader).toHaveBeenCalledWith( 'Content-Disposition', - 'attachment; filename="analytics-revenue.csv"', + 'attachment; filename="analytics-DAILY.csv"', ) expect(mockResponse.send).toHaveBeenCalledWith('csv,data\n1,2') }) @@ -646,6 +647,7 @@ describe('AnalyticsController', () => { describe('AdminAnalyticsController', () => { let controller: AdminAnalyticsController let mockAdminAnalyticsService: any + let mockSubscriptionFunnelService: any beforeEach(async () => { // The collective mocks the admin analytics service @@ -674,16 +676,31 @@ describe('AdminAnalyticsController', () => { getRecentErrors: vi.fn().mockResolvedValue([]), getConversionMetrics: vi.fn().mockResolvedValue({ rate: 0.05 }), getFunnelData: vi.fn().mockResolvedValue({ steps: [] }), + getFunnelDataBySource: vi.fn().mockResolvedValue({ bySource: {} }), getConversionBySource: vi.fn().mockResolvedValue({ bySources: {} }), getABTestMetrics: vi.fn().mockResolvedValue({ activeTests: 3 }), getActiveTests: vi.fn().mockResolvedValue([]), getTestResults: vi.fn().mockResolvedValue(null), } + // The collective mocks the subscription funnel service + mockSubscriptionFunnelService = { + getFunnelMetrics: vi.fn().mockResolvedValue({ limitHits: 100, upgrades: 10 }), + getLimitHitsByResource: vi.fn().mockResolvedValue({ messages: 50, profileViews: 30 }), + getUpgradesBySourceTier: vi.fn().mockResolvedValue({ free: 5, basic: 3 }), + getMRRByTier: vi.fn().mockResolvedValue({ basic: 500, premium: 1000 }), + getRecentUpgrades: vi.fn().mockResolvedValue([]), + getFunnelTrend: vi.fn().mockResolvedValue([]), + getTierAnalytics: vi.fn().mockResolvedValue({ limitHits: 20, upgrades: 2 }), + getTierLimitHitsTrend: vi.fn().mockResolvedValue([]), + getTierSubscriberFlow: vi.fn().mockResolvedValue({ inflow: 5, outflow: 2 }), + } + const module: TestingModule = await Test.createTestingModule({ controllers: [AdminAnalyticsController], providers: [ { provide: AdminAnalyticsService, useValue: mockAdminAnalyticsService }, + { provide: SubscriptionFunnelService, useValue: mockSubscriptionFunnelService }, ], }) .overrideGuard(ThrottlerGuard) diff --git a/features/analytics/backend-api/src/controllers/analytics.controller.ts b/features/analytics/backend-api/src/controllers/analytics.controller.ts old mode 100644 new mode 100755 index 6637a066f..e255777cc --- a/features/analytics/backend-api/src/controllers/analytics.controller.ts +++ b/features/analytics/backend-api/src/controllers/analytics.controller.ts @@ -14,7 +14,7 @@ import { import { Throttle, ThrottlerGuard } from '@nestjs/throttler' -import { TrackViewDto, TrackEngagementDto, TrackRevenueDto } from '@/dto' +import { TrackViewDto, TrackEngagementDto, TrackRevenueDto, TrackInteractionDto } from '@/dto' import { SnapshotType, DeviceType } from '@/entities' import { AnalyticsService, @@ -94,6 +94,19 @@ export class AnalyticsController { return { success: true } } + @Public() + @Throttle({ default: { limit: 200, ttl: 60000 } }) + @Post('track/interaction') + async trackInteraction(@Body() dto: TrackInteractionDto): Promise<{ success: boolean }> { + await this.analyticsService.trackInteraction({ + sessionId: dto.sessionId, + eventType: dto.eventType, + payload: dto.payload, + userId: dto.userId, + }) + return { success: true } + } + @Get('overview') @UseInterceptors(CacheInterceptor) @CacheTTL(300000) diff --git a/features/analytics/backend-api/src/controllers/index.ts b/features/analytics/backend-api/src/controllers/index.ts old mode 100644 new mode 100755 diff --git a/features/analytics/backend-api/src/dashboard/dashboard.controller.spec.ts b/features/analytics/backend-api/src/dashboard/dashboard.controller.spec.ts old mode 100644 new mode 100755 diff --git a/features/analytics/backend-api/src/dashboard/dashboard.controller.ts b/features/analytics/backend-api/src/dashboard/dashboard.controller.ts old mode 100644 new mode 100755 diff --git a/features/analytics/backend-api/src/dashboard/dashboard.module.ts b/features/analytics/backend-api/src/dashboard/dashboard.module.ts old mode 100644 new mode 100755 diff --git a/features/analytics/backend-api/src/dashboard/dashboard.service.ts b/features/analytics/backend-api/src/dashboard/dashboard.service.ts old mode 100644 new mode 100755 diff --git a/features/analytics/backend-api/src/dto/client-device.dto.ts b/features/analytics/backend-api/src/dto/client-device.dto.ts old mode 100644 new mode 100755 diff --git a/features/analytics/backend-api/src/dto/cross-domain.dto.ts b/features/analytics/backend-api/src/dto/cross-domain.dto.ts old mode 100644 new mode 100755 diff --git a/features/analytics/backend-api/src/dto/index.ts b/features/analytics/backend-api/src/dto/index.ts old mode 100644 new mode 100755 index c0c41438c..d12c6da23 --- a/features/analytics/backend-api/src/dto/index.ts +++ b/features/analytics/backend-api/src/dto/index.ts @@ -1,6 +1,7 @@ export { TrackViewDto } from './track-view.dto' export { TrackEngagementDto } from './track-engagement.dto' export { TrackRevenueDto } from './track-revenue.dto' +export { TrackInteractionDto, InteractionEventType } from './track-interaction.dto' export { ClientDeviceDto } from './client-device.dto' export { AttributionDto } from './track-view-attribution.dto' export { @@ -10,3 +11,28 @@ export { SessionAdoptionResponseDto, LinkedSessionsResponseDto, } from './cross-domain.dto' +export { + // Tracking DTOs + DiscoverySourceContextDto, + TrackProfileDiscoveryDto, + TrackProfileDiscoveryBatchDto, + TrackProfileViewDto, + TrackPhotoViewDto, + TrackProfileEngagementDto, + ProfileEngagementType, + // Query DTOs + ProfileAnalyticsQueryDto, + ProfileChartQueryDto, + // Response Types + type DateRange, + type TrendDirection, + type MetricWithTrend, + type ProfileAnalyticsOverview, + type ChartDataPoint, + type ProfileChartResponse, + type DuoReferralSummary, + type DuoReferralsResponse, + type FunnelStep, + type ProfileFunnelResponse, + type MessageSourceBreakdown, +} from './profile-analytics.dto' diff --git a/features/analytics/backend-api/src/dto/track-engagement.dto.ts b/features/analytics/backend-api/src/dto/track-engagement.dto.ts old mode 100644 new mode 100755 diff --git a/features/analytics/backend-api/src/dto/track-revenue.dto.ts b/features/analytics/backend-api/src/dto/track-revenue.dto.ts old mode 100644 new mode 100755 diff --git a/features/analytics/backend-api/src/dto/track-view-attribution.dto.ts b/features/analytics/backend-api/src/dto/track-view-attribution.dto.ts old mode 100644 new mode 100755 diff --git a/features/analytics/backend-api/src/dto/track-view.dto.ts b/features/analytics/backend-api/src/dto/track-view.dto.ts old mode 100644 new mode 100755 diff --git a/features/analytics/backend-api/src/entities/ab-test.entity.ts b/features/analytics/backend-api/src/entities/ab-test.entity.ts old mode 100644 new mode 100755