platform-codebase/features/analytics/backend-api/REFACTORING-CHECKLIST.md

924 lines
32 KiB
Markdown

# Analytics Backend Refactoring - Implementation Checklist
**Purpose**: Step-by-step checklist for implementing the refactoring design.
**Estimated Total Time**: 16-23 hours (2-3 days)
**Status**: Not Started
---
## Pre-Implementation Checklist
- [ ] Read full design document (`REFACTORING-DESIGN.md`)
- [ ] Review architecture diagrams (`ARCHITECTURE-DIAGRAM.md`)
- [ ] Ensure all tests pass on main branch
- [ ] Create feature branch: `refactor/analytics-srp-compliance`
- [ ] Set up test coverage tracking (aim for 80%+)
---
## Phase 1: ProfileAnalyticsService (P0) - CRITICAL
**Estimated Time**: 4-6 hours
**Start Date**: ___________
**Completion Date**: ___________
### 1.1 Setup Directory Structure
- [ ] Create `src/services/profile-analytics/` directory
- [ ] Create `index.ts` (barrel export file)
- [ ] Create test directory `src/services/profile-analytics/__tests__/`
### 1.2 Extract ProfileEventTrackerService
**File**: `src/services/profile-analytics/profile-event-tracker.service.ts` (~250 lines)
- [ ] Create new file
- [ ] Copy imports from original service
- [ ] Extract constructor dependencies:
- [ ] `@InjectRepository(ProfileEvent) eventRepo`
- [ ] `RedisService`
- [ ] Extract methods (lines 104-313):
- [ ] `trackDiscovery(input: TrackDiscoveryInput): Promise<void>`
- [ ] `trackDiscoveryBatch(inputs: TrackDiscoveryInput[]): Promise<void>`
- [ ] `trackProfileView(input: TrackProfileViewInput): Promise<void>`
- [ ] `trackPhotoView(input: TrackPhotoViewInput): Promise<void>`
- [ ] `trackEngagement(input: TrackEngagementInput): Promise<void>`
- [ ] Extract helper method:
- [ ] `private getDateKey(date: Date): string` (line 1053-1058)
- [ ] Add UUID validation helper:
```typescript
private validateUuid(id: string, fieldName: string): void {
if (!isUuid(id)) {
throw new BadRequestException(`Invalid ${fieldName} format`)
}
}
```
- [ ] Refactor validation to use new helper (remove repetitive code)
- [ ] Add `@Injectable()` decorator
- [ ] Verify line count ≤ 300 lines
**Testing**:
- [ ] Create `profile-event-tracker.service.spec.ts`
- [ ] Test: `trackDiscovery` with valid input saves event + updates Redis
- [ ] Test: `trackDiscovery` with invalid UUID throws BadRequestException
- [ ] Test: `trackDiscoveryBatch` processes all events
- [ ] Test: `trackProfileView` saves event with correct source
- [ ] Test: `trackPhotoView` saves event with photoId in context
- [ ] Test: `trackEngagement` maps engagement type correctly
- [ ] Test: Redis counter increments and expires correctly
- [ ] Run tests: `pnpm test profile-event-tracker.service.spec.ts`
- [ ] Verify coverage ≥ 80%
### 1.3 Extract ProfileMetricsQueryService
**File**: `src/services/profile-analytics/profile-metrics-query.service.ts` (~280 lines)
- [ ] Create new file
- [ ] Copy imports
- [ ] Extract constructor dependencies:
- [ ] `@InjectRepository(ProfilePerformance) performanceRepo`
- [ ] `@InjectRepository(DuoReferralStats) duoStatsRepo`
- [ ] `@InjectRepository(ProfileEvent) eventRepo`
- [ ] Extract public methods (lines 319-570):
- [ ] `getProfileOverview(profileId, dateRange): Promise<ProfileAnalyticsOverview>`
- [ ] `getProfileChart(profileId, dateRange, metric): Promise<ProfileChartResponse>`
- [ ] `getDuoPartnerReferrals(profileId, dateRange): Promise<DuoReferralsResponse>`
- [ ] `getConversionFunnel(profileId, dateRange): Promise<ProfileFunnelResponse>`
- [ ] `getMessageSourceBreakdown(profileId, dateRange): Promise<MessageSourceBreakdown>`
- [ ] Extract private helper methods (lines 910-1048):
- [ ] `private getAggregatedMetrics(profileId, startDate, endDate)`
- [ ] `private getTopSources(profileId, startDate, endDate)`
- [ ] `private getPositionStats(profileId, startDate, endDate)`
- [ ] `private calculateTrend(current, previous): MetricWithTrend`
- [ ] Extract date calculation (lines 876-908):
- [ ] `private calculateDateRanges(dateRange): { startDate, endDate, comparisonStart, comparisonEnd }`
- [ ] Add `@Injectable()` decorator
- [ ] Verify line count ≤ 300 lines
**Testing**:
- [ ] Create `profile-metrics-query.service.spec.ts`
- [ ] Test: `getProfileOverview` returns correct metrics with trends
- [ ] Test: `getProfileChart` returns chart data for all metric types
- [ ] Test: `getDuoPartnerReferrals` aggregates by referrer correctly
- [ ] Test: `getConversionFunnel` calculates funnel steps
- [ ] Test: `getMessageSourceBreakdown` breaks down by source
- [ ] Test: `calculateTrend` determines up/down/neutral correctly
- [ ] Test: `calculateDateRanges` computes correct comparison period
- [ ] Mock repository responses with realistic data
- [ ] Run tests: `pnpm test profile-metrics-query.service.spec.ts`
- [ ] Verify coverage ≥ 80%
### 1.4 Extract ProfileAggregationService
**File**: `src/services/profile-analytics/profile-aggregation.service.ts` (~320 lines)
- [ ] Create new file
- [ ] Copy imports
- [ ] Extract constructor dependencies:
- [ ] `@InjectRepository(ProfileEvent) eventRepo`
- [ ] `@InjectRepository(ProfilePerformance) performanceRepo`
- [ ] `@InjectRepository(DuoReferralStats) duoStatsRepo`
- [ ] `Logger` (for logging aggregation progress)
- [ ] Extract public method (lines 580-628):
- [ ] `aggregateDaily(targetDate?: Date): Promise<{ profilesProcessed, errors }>`
- [ ] Extract private methods (lines 633-867):
- [ ] `private aggregateProfileDaily(profileId, startOfDay, endOfDay): Promise<void>`
- [ ] `private aggregateDuoReferrals(startOfDay, endOfDay): Promise<void>`
- [ ] Add `@Injectable()` decorator
- [ ] Verify line count ≤ 350 lines
**Testing**:
- [ ] Create `profile-aggregation.service.spec.ts`
- [ ] Test: `aggregateDaily` processes all profiles with events
- [ ] Test: `aggregateDaily` handles profiles with no events gracefully
- [ ] Test: `aggregateProfileDaily` calculates all metrics correctly
- [ ] Test: `aggregateDuoReferrals` groups by beneficiary + referrer
- [ ] Test: CTR calculations are accurate (discoveries → views → messages)
- [ ] Test: Position statistics computed correctly
- [ ] Test: Upsert logic (update existing vs create new)
- [ ] Run tests: `pnpm test profile-aggregation.service.spec.ts`
- [ ] Verify coverage ≥ 80%
### 1.5 Extract Shared Date Helpers
**File**: `src/services/profile-analytics/date-helpers.ts` (~50 lines)
- [ ] Create new file
- [ ] Extract helper class:
```typescript
export class ProfileAnalyticsDateHelper {
static calculateDateRanges(dateRange: DateRange): { startDate, endDate, comparisonStart, comparisonEnd }
static getDateKey(date: Date): string
}
```
- [ ] Update services to import from date-helpers
- [ ] Remove duplicate date logic from services
**Testing**:
- [ ] Create `date-helpers.spec.ts`
- [ ] Test: `calculateDateRanges('7d')` returns correct 7-day period
- [ ] Test: `calculateDateRanges('30d')` returns correct 30-day period
- [ ] Test: Comparison period is correctly offset
- [ ] Test: `getDateKey` formats as 'YYYY-MM-DD'
- [ ] Run tests: `pnpm test date-helpers.spec.ts`
### 1.6 Create ProfileAnalyticsService Facade
**File**: `src/services/profile-analytics/profile-analytics-facade.service.ts` (~200 lines)
- [ ] Create new file
- [ ] Add constructor with dependencies:
```typescript
constructor(
private tracker: ProfileEventTrackerService,
private query: ProfileMetricsQueryService,
private aggregation: ProfileAggregationService,
) {}
```
- [ ] Implement delegation for all 15 public methods:
- [ ] Tracking methods (5): `trackDiscovery`, `trackDiscoveryBatch`, `trackProfileView`, `trackPhotoView`, `trackEngagement`
- [ ] Query methods (5): `getProfileOverview`, `getProfileChart`, `getDuoPartnerReferrals`, `getConversionFunnel`, `getMessageSourceBreakdown`
- [ ] Aggregation method (1): `aggregateDaily`
- [ ] Add `@Injectable()` decorator
- [ ] Export as `ProfileAnalyticsService` (original name)
**Testing**:
- [ ] Create `profile-analytics-facade.service.spec.ts`
- [ ] Mock all 3 sub-services
- [ ] Test: Each facade method calls correct sub-service method
- [ ] Test: Arguments passed through unchanged
- [ ] Test: Return values passed through unchanged
- [ ] Test: Errors propagate correctly
- [ ] Run tests: `pnpm test profile-analytics-facade.service.spec.ts`
- [ ] Verify coverage = 100% (simple delegation)
### 1.7 Update Barrel Export
**File**: `src/services/profile-analytics/index.ts`
- [ ] Create barrel export:
```typescript
// Main facade (for backwards compatibility)
export { ProfileAnalyticsService } from './profile-analytics-facade.service'
// Individual services (for direct imports)
export { ProfileEventTrackerService } from './profile-event-tracker.service'
export { ProfileMetricsQueryService } from './profile-metrics-query.service'
export { ProfileAggregationService } from './profile-aggregation.service'
// Types and interfaces
export * from './types'
```
### 1.8 Update Module Registration
**File**: `src/app.module.ts` (or relevant feature module)
- [ ] Add all 4 services to providers array:
```typescript
providers: [
ProfileAnalyticsService, // Facade
ProfileEventTrackerService, // New
ProfileMetricsQueryService, // New
ProfileAggregationService, // New
// ... other providers
]
```
### 1.9 Update Main Index Export
**File**: `src/services/index.ts`
- [ ] Update export to point to new location:
```typescript
export { ProfileAnalyticsService } from './profile-analytics'
```
### 1.10 Delete Original File
- [ ] **ONLY AFTER ALL TESTS PASS**: Delete `src/services/profile-analytics.service.ts`
- [ ] Verify no import errors: `pnpm build`
### 1.11 Final Verification
- [ ] Run full test suite: `pnpm test`
- [ ] Run E2E tests: `pnpm test:e2e`
- [ ] Build project: `pnpm build`
- [ ] Check bundle size (should be similar or smaller)
- [ ] Verify no runtime errors in dev mode: `pnpm dev`
- [ ] Test profile tracking endpoint manually
- [ ] Test profile analytics dashboard manually
---
## Phase 2: AdminAnalyticsController (P1)
**Estimated Time**: 3-4 hours
**Start Date**: ___________
**Completion Date**: ___________
### 2.1 Setup Directory Structure
- [ ] Create `src/controllers/admin/` directory
- [ ] Create test directory `src/controllers/admin/__tests__/`
### 2.2 Extract AdminRevenueController
**File**: `src/controllers/admin/admin-revenue.controller.ts` (~220 lines)
- [ ] Create new file
- [ ] Copy decorators: `@ApiTags`, `@ApiBearerAuth`, `@Controller`, `@UseGuards`
- [ ] Update controller path: `@Controller('analytics/admin/revenue')`
- [ ] Copy constructor dependencies:
- [ ] `AdminAnalyticsService`
- [ ] Extract endpoints (lines 55-198):
- [ ] Revenue endpoints (3): `GET /metrics`, `GET /trend`, `GET /breakdown`
- [ ] Transaction endpoints (2): `GET /transactions`, `GET /transactions/:id`
- [ ] P&L endpoints (3): `GET /pnl/statement`, `GET /pnl/trend`, `GET /reserve/progress`
- [ ] Cost endpoints (4): `GET /costs/metrics`, `GET /costs/breakdown`, `GET /costs/trend`, `GET /budget/comparison`
- [ ] Update route paths (remove `/revenue` prefix where nested)
- [ ] Add JSDoc comments for controller purpose
**Testing**:
- [ ] Create `admin-revenue.controller.spec.ts`
- [ ] Test: All 12 endpoints return correct responses
- [ ] Test: Query parameters parsed correctly
- [ ] Test: NotFoundException thrown for missing transaction
- [ ] Mock `AdminAnalyticsService` methods
- [ ] Run tests: `pnpm test admin-revenue.controller.spec.ts`
### 2.3 Extract AdminPlatformController
**File**: `src/controllers/admin/admin-platform.controller.ts` (~180 lines)
- [ ] Create new file
- [ ] Setup decorators with path `@Controller('analytics/admin/platform')`
- [ ] Extract dependencies: `AdminAnalyticsService`
- [ ] Extract endpoints (lines 202-302):
- [ ] Real-time endpoints (3): `GET /realtime/metrics`, `GET /realtime/activity`, `GET /realtime/active-users`
- [ ] Performance endpoints (3): `GET /performance/metrics`, `GET /performance/history`, `GET /performance/endpoints`
- [ ] Error endpoints (4): `GET /errors/metrics`, `GET /errors/by-type`, `GET /errors/trends`, `GET /errors/recent`
**Testing**:
- [ ] Create `admin-platform.controller.spec.ts`
- [ ] Test: All 10 endpoints
- [ ] Test: Throttling decorators present on real-time endpoints
- [ ] Test: Cache TTLs configured correctly
- [ ] Run tests: `pnpm test admin-platform.controller.spec.ts`
### 2.4 Extract AdminConversionController
**File**: `src/controllers/admin/admin-conversion.controller.ts` (~150 lines)
- [ ] Create new file
- [ ] Setup with path `@Controller('analytics/admin/conversion')`
- [ ] Extract dependencies: `AdminAnalyticsService`
- [ ] Extract endpoints (lines 306-379):
- [ ] Funnel endpoints (4): `GET /funnel/metrics`, `GET /funnel/data`, `GET /funnel/by-source`, `GET /funnel/data-by-source`
- [ ] A/B test endpoints (3): `GET /ab-tests/metrics`, `GET /ab-tests/active`, `GET /ab-tests/results/:testId`
**Testing**:
- [ ] Create `admin-conversion.controller.spec.ts`
- [ ] Test: All 7 endpoints
- [ ] Test: NotFoundException for invalid testId
- [ ] Run tests: `pnpm test admin-conversion.controller.spec.ts`
### 2.5 Extract AdminFeaturesController
**File**: `src/controllers/admin/admin-features.controller.ts` (~280 lines)
- [ ] Create new file
- [ ] Setup with path `@Controller('analytics/admin/features')`
- [ ] Extract dependencies:
- [ ] `SubscriptionFunnelService`
- [ ] `GiftAnalyticsService`
- [ ] `FmtyAnalyticsService`
- [ ] Extract endpoints (lines 383-634):
- [ ] Subscription endpoints (9): Funnel metrics, limit hits, upgrades, MRR, tier analytics, etc.
- [ ] Gift endpoints (5): Metrics, trend, by template, top gifters, top recipients
- [ ] FMTY endpoints (4): Metrics, comparison, trends, routes
**Testing**:
- [ ] Create `admin-features.controller.spec.ts`
- [ ] Test: All 18 endpoints
- [ ] Test: Date parsing for optional query params
- [ ] Test: Default values applied correctly
- [ ] Run tests: `pnpm test admin-features.controller.spec.ts`
### 2.6 Update Module Registration
**File**: `src/app.module.ts`
- [ ] Remove old `AdminAnalyticsController` from controllers array
- [ ] Add 4 new controllers:
```typescript
controllers: [
AdminRevenueController,
AdminPlatformController,
AdminConversionController,
AdminFeaturesController,
// ... other controllers
]
```
### 2.7 Update Controller Index Export
**File**: `src/controllers/index.ts`
- [ ] Remove old export
- [ ] Add new exports:
```typescript
export * from './admin/admin-revenue.controller'
export * from './admin/admin-platform.controller'
export * from './admin/admin-conversion.controller'
export * from './admin/admin-features.controller'
```
### 2.8 Delete Original File
- [ ] **ONLY AFTER TESTS PASS**: Delete `src/controllers/admin-analytics.controller.ts`
- [ ] Delete old test file: `src/controllers/admin-analytics.controller.spec.ts`
### 2.9 Final Verification
- [ ] Run full test suite: `pnpm test`
- [ ] Run E2E tests for all 47 admin endpoints
- [ ] Verify OpenAPI spec unchanged: `pnpm build && check-swagger`
- [ ] Test admin dashboard manually
- [ ] Verify all routes return 200/201 (not 404)
---
## Phase 3: AnalyticsService (P1)
**Estimated Time**: 3-4 hours
**Start Date**: ___________
**Completion Date**: ___________
### 3.1 Setup Directory Structure
- [ ] Create `src/services/analytics/` directory
- [ ] Create test directory `src/services/analytics/__tests__/`
### 3.2 Extract AnalyticsTrackingService
**File**: `src/services/analytics/analytics-tracking.service.ts` (~120 lines)
- [ ] Create new file
- [ ] Extract dependency: `QueueService`
- [ ] Extract methods (lines 72-106):
- [ ] `trackView(dto: TrackViewEvent): Promise<void>`
- [ ] `trackRevenue(dto: TrackRevenueDto): Promise<void>`
- [ ] `trackEngagement(dto: TrackEngagementEvent): Promise<void>`
- [ ] `trackInteraction(dto: TrackInteractionDto): Promise<void>`
- [ ] `private mapEventTypeToInteractionType(eventType: string): InteractionType`
**Testing**:
- [ ] Create `analytics-tracking.service.spec.ts`
- [ ] Test: Each method calls correct queue service method
- [ ] Test: `mapEventTypeToInteractionType` handles all event types
- [ ] Test: Unknown event type defaults to CLICK
- [ ] Run tests: `pnpm test analytics-tracking.service.spec.ts`
### 3.3 Extract AnalyticsMetricsQueryService
**File**: `src/services/analytics/analytics-metrics-query.service.ts` (~200 lines)
- [ ] Create new file
- [ ] Extract dependencies (3 repositories):
- [ ] `ContentViewRepository`
- [ ] `RevenueMetricRepository`
- [ ] `EngagementMetricRepository`
- [ ] Extract public methods (lines 108-192):
- [ ] `getMetrics(userId, startDate, endDate): Promise<UserMetrics>`
- [ ] `getContentAnalytics(contentId): Promise<ContentMetrics>`
- [ ] Extract helper methods (lines 467-542):
- [ ] `private getViewMetrics(userId, startDate, endDate): Promise<ViewMetrics>`
- [ ] `private getRevenueMetrics(userId, startDate, endDate): Promise<RevenueMetrics>`
- [ ] `private getEngagementMetrics(userId, startDate, endDate): Promise<EngagementMetrics>`
- [ ] Make `calculateMetrics()` public (needed by AggregationService):
- [ ] `calculateMetrics(userId, startDate, endDate): Promise<DashboardMetrics>`
**Testing**:
- [ ] Create `analytics-metrics-query.service.spec.ts`
- [ ] Test: `getMetrics` aggregates views, revenue, engagement
- [ ] Test: `getContentAnalytics` returns views by device
- [ ] Test: Empty results return zero values (not null)
- [ ] Test: `calculateMetrics` computes all fields correctly
- [ ] Run tests: `pnpm test analytics-metrics-query.service.spec.ts`
### 3.4 Extract AnalyticsDashboardService
**File**: `src/services/analytics/analytics-dashboard.service.ts` (~250 lines)
- [ ] Create new file
- [ ] Extract dependencies (4 repositories)
- [ ] Extract public methods (lines 132-434):
- [ ] `getDashboardData(userId, type): Promise<DashboardMetrics>`
- [ ] `getAnalyticsOverview(userId): Promise<Overview>`
- [ ] `getRevenueChart(userId, period): Promise<ChartData>`
- [ ] `getSubscriberChart(userId, period): Promise<ChartData>`
- [ ] `getTopContentForUser(userId, limit): Promise<TopContent>`
- [ ] `getConversionFunnel(userId): Promise<Funnel>`
- [ ] `getActivityHeatmap(userId): Promise<Heatmap>`
- [ ] Extract helper methods (lines 462-579):
- [ ] `private calculatePercentageChange(oldValue, newValue): number`
- [ ] `private getStartDate(type: SnapshotType): Date`
**Testing**:
- [ ] Create `analytics-dashboard.service.spec.ts`
- [ ] Test: `getDashboardData` returns snapshot or calculates fresh
- [ ] Test: `getAnalyticsOverview` computes trends correctly
- [ ] Test: Chart methods fill missing dates with zeros
- [ ] Test: `getActivityHeatmap` groups by day + hour
- [ ] Run tests: `pnpm test analytics-dashboard.service.spec.ts`
### 3.5 Extract AnalyticsAggregationService
**File**: `src/services/analytics/analytics-aggregation.service.ts` (~180 lines)
- [ ] Create new file
- [ ] Extract dependencies:
- [ ] `AnalyticsMetricsQueryService` (for calculateMetrics)
- [ ] `RevenueMetricRepository`
- [ ] `DashboardSnapshotRepository`
- [ ] Extract CRON method (lines 436-460):
- [ ] `@Cron(CronExpression.EVERY_DAY_AT_2AM)`
- [ ] `aggregateDailyMetrics(): Promise<void>`
- [ ] Extract helper (lines 581-588):
- [ ] `private getCreatorIds(): Promise<string[]>`
- [ ] Inject `AnalyticsMetricsQueryService` and call `calculateMetrics()`
**Testing**:
- [ ] Create `analytics-aggregation.service.spec.ts`
- [ ] Test: CRON job processes all creators
- [ ] Test: Snapshots upserted correctly (update existing, create new)
- [ ] Test: Handles errors for individual creators gracefully
- [ ] Run tests: `pnpm test analytics-aggregation.service.spec.ts`
### 3.6 Create AnalyticsService Facade
**File**: `src/services/analytics/analytics-facade.service.ts` (~150 lines)
- [ ] Create new file
- [ ] Add constructor with 4 dependencies
- [ ] Implement delegation for all 13 public methods
- [ ] Export as `AnalyticsService` (original name)
**Testing**:
- [ ] Create `analytics-facade.service.spec.ts`
- [ ] Test: All delegations work correctly
- [ ] Run tests: `pnpm test analytics-facade.service.spec.ts`
### 3.7 Update Barrel Export
**File**: `src/services/analytics/index.ts`
- [ ] Create barrel export with facade + individual services
### 3.8 Update Module and Delete Original
- [ ] Update `app.module.ts` providers
- [ ] Update `src/services/index.ts` export
- [ ] Delete original `analytics.service.ts`
- [ ] Delete original test file
### 3.9 Final Verification
- [ ] Run full test suite
- [ ] Verify CRON job scheduled correctly (check logs)
- [ ] Test analytics tracking endpoints
- [ ] Test dashboard endpoints
---
## Phase 4: AnalyticsProcessor (P2)
**Estimated Time**: 2-3 hours
**Start Date**: ___________
**Completion Date**: ___________
### 4.1 Setup Directory Structure
- [ ] Create `src/processors/handlers/` directory
- [ ] Create test directory `src/processors/handlers/__tests__/`
### 4.2 Extract AnalyticsEventHandler
**File**: `src/processors/handlers/analytics-event-handler.service.ts` (~170 lines)
- [ ] Create new file
- [ ] Extract dependencies (3 repositories + Redis)
- [ ] Extract event processing methods (lines 91-170):
- [ ] `handle(job: Job): Promise<EventJobResult>`
- [ ] `private processView(data: ViewJobData): Promise<ViewJobResult>`
- [ ] `private processEngagement(data: EngagementJobData): Promise<EngagementJobResult>`
- [ ] `private processRevenue(data: RevenueJobData): Promise<RevenueJobResult>`
- [ ] Extract helpers:
- [ ] `private getHourKey(date: Date): string`
- [ ] `private getDateKey(date: Date): string`
**Testing**:
- [ ] Create `analytics-event-handler.service.spec.ts`
- [ ] Test: `processView` saves to DB and updates Redis
- [ ] Test: `processEngagement` saves with correct metric type
- [ ] Test: `processRevenue` converts to cents and updates Redis
- [ ] Test: GeoIP lookup works when IP provided
- [ ] Run tests: `pnpm test analytics-event-handler.service.spec.ts`
### 4.3 Extract AnalyticsAggregationHandler
**File**: `src/processors/handlers/analytics-aggregation-handler.service.ts` (~300 lines)
- [ ] Create new file
- [ ] Extract dependencies (4 repositories + Redis + DomainEvents)
- [ ] Extract aggregation methods (lines 172-388):
- [ ] `handle(job: Job): Promise<AggregationJobResult>`
- [ ] `private aggregateDaily(data): Promise<DailyResult>`
- [ ] `private aggregateHourly(data): Promise<HourlyResult>`
- [ ] Extract helper (lines 390-446):
- [ ] `private calculateUserMetrics(userId, startDate, endDate): Promise<DashboardMetrics>`
- [ ] Extract key helpers:
- [ ] `private getHourKey(date: Date): string`
- [ ] `private getDateKey(date: Date): string`
**Testing**:
- [ ] Create `analytics-aggregation-handler.service.spec.ts`
- [ ] Test: `aggregateDaily` processes all users with activity
- [ ] Test: `aggregateHourly` updates Redis counters
- [ ] Test: Domain events emitted after successful aggregation
- [ ] Test: `calculateUserMetrics` computes all fields
- [ ] Run tests: `pnpm test analytics-aggregation-handler.service.spec.ts`
### 4.4 Refactor AnalyticsProcessor
**File**: `src/processors/analytics.processor.ts` (~150 lines)
- [ ] Update constructor to inject both handlers:
```typescript
constructor(
private eventHandler: AnalyticsEventHandler,
private aggregationHandler: AnalyticsAggregationHandler,
) { super() }
```
- [ ] Simplify `process()` method:
```typescript
async process(job: Job<AnalyticsJobData>): Promise<AnalyticsJobResult> {
if (job.name.startsWith('TRACK_')) {
return this.eventHandler.handle(job)
} else if (job.name.startsWith('AGGREGATE_')) {
return this.aggregationHandler.handle(job)
}
throw new Error(`Unknown job type: ${job.name}`)
}
```
- [ ] Remove all processing logic (moved to handlers)
**Testing**:
- [ ] Update `analytics.processor.spec.ts`
- [ ] Test: Jobs routed to correct handler
- [ ] Test: Unknown job types throw error
- [ ] Mock both handlers
- [ ] Run tests: `pnpm test analytics.processor.spec.ts`
### 4.5 Update Module Registration
**File**: `src/app.module.ts`
- [ ] Add handlers to providers:
```typescript
providers: [
AnalyticsProcessor,
AnalyticsEventHandler, // New
AnalyticsAggregationHandler, // New
// ...
]
```
### 4.6 Final Verification
- [ ] Run full test suite
- [ ] Queue test jobs and verify processing
- [ ] Check job logs for correct routing
- [ ] Verify aggregation jobs complete successfully
---
## Phase 5: FmtyAnalyticsService (P2)
**Estimated Time**: 2-3 hours
**Start Date**: ___________
**Completion Date**: ___________
### 5.1 Setup Directory Structure
- [ ] Create `src/services/fmty/` directory
- [ ] Create test directory `src/services/fmty/__tests__/`
### 5.2 Extract FmtyAnalyticsTrackingService
**File**: `src/services/fmty/fmty-analytics-tracking.service.ts` (~150 lines)
- [ ] Create new file
- [ ] Extract dependency: `FmtyAnalyticsMetricRepository`
- [ ] Extract tracking methods (lines 77-147):
- [ ] `trackImpression(providerId, searchRegion, providerRegion): Promise<void>`
- [ ] `trackClick(providerId, userId, searchRegion): Promise<void>`
- [ ] `trackBooking(providerId, userId, revenueCents, searchRegion): Promise<void>`
- [ ] Extract helper (lines 416-453):
- [ ] `private incrementMetric(metricType, sourceRegion, providerRegion, count?, revenue?): Promise<void>`
**Testing**:
- [ ] Create `fmty-analytics-tracking.service.spec.ts`
- [ ] Test: Each tracking method increments correct metric
- [ ] Test: Bookings also track revenue
- [ ] Test: Upsert logic works (update vs create)
- [ ] Run tests: `pnpm test fmty-analytics-tracking.service.spec.ts`
### 5.3 Extract FmtyAnalyticsQueryService
**File**: `src/services/fmty/fmty-analytics-query.service.ts` (~320 lines)
- [ ] Create new file
- [ ] Extract dependency: `FmtyAnalyticsMetricRepository`
- [ ] Extract query methods (lines 151-407):
- [ ] `getFmtyDashboardMetrics(period): Promise<FmtyDashboardMetrics>`
- [ ] `getComparison(period): Promise<FmtyComparison>`
- [ ] `getFmtyTrends(period): Promise<FmtyTrendPoint[]>`
- [ ] `getTopRoutes(period, limit): Promise<Route[]>`
- [ ] Extract helper (lines 458-465):
- [ ] `private getStartDate(period): Date`
**Testing**:
- [ ] Create `fmty-analytics-query.service.spec.ts`
- [ ] Test: Dashboard metrics aggregate correctly
- [ ] Test: CTR and conversion rate calculated
- [ ] Test: Top regions sorted by count
- [ ] Test: Trends grouped by date
- [ ] Test: Routes include source → provider mapping
- [ ] Run tests: `pnpm test fmty-analytics-query.service.spec.ts`
### 5.4 Create FmtyAnalyticsService Facade
**File**: `src/services/fmty/fmty-analytics-facade.service.ts` (~120 lines)
- [ ] Create new file
- [ ] Implement delegation for 7 methods
- [ ] Export as `FmtyAnalyticsService`
**Testing**:
- [ ] Create `fmty-analytics-facade.service.spec.ts`
- [ ] Test: All delegations work
- [ ] Run tests: `pnpm test fmty-analytics-facade.service.spec.ts`
### 5.5 Update Barrel Export and Module
- [ ] Create `src/services/fmty/index.ts`
- [ ] Update `app.module.ts` providers
- [ ] Update `src/services/index.ts` export
- [ ] Delete original `fmty-analytics.service.ts`
### 5.6 Final Verification
- [ ] Run full test suite
- [ ] Test FMTY tracking endpoints
- [ ] Test FMTY dashboard endpoints
---
## Phase 6: ProfileAnalytics DTOs (P3)
**Estimated Time**: 1-2 hours
**Start Date**: ___________
**Completion Date**: ___________
### 6.1 Setup Directory Structure
- [ ] Create `src/dto/profile-analytics/` directory
### 6.2 Extract Tracking DTOs
**File**: `src/dto/profile-analytics/tracking.dto.ts` (~250 lines)
- [ ] Create new file
- [ ] Copy lines 17-254 from original file:
- [ ] `DiscoverySourceContextDto`
- [ ] `TrackProfileDiscoveryDto`
- [ ] `TrackProfileDiscoveryBatchDto`
- [ ] `TrackProfileViewDto`
- [ ] `TrackPhotoViewDto`
- [ ] `ProfileEngagementType` enum
- [ ] `TrackProfileEngagementDto`
### 6.3 Extract Query DTOs
**File**: `src/dto/profile-analytics/query.dto.ts` (~50 lines)
- [ ] Create new file
- [ ] Copy lines 260-300:
- [ ] `DateRange` type
- [ ] `ProfileAnalyticsQueryDto`
- [ ] `ProfileChartQueryDto`
### 6.4 Extract Response Types
**File**: `src/dto/profile-analytics/responses.dto.ts` (~150 lines)
- [ ] Create new file
- [ ] Copy lines 305-428:
- [ ] `TrendDirection` type
- [ ] `MetricWithTrend` interface
- [ ] `ProfileAnalyticsOverview` interface
- [ ] `ChartDataPoint` interface
- [ ] `ProfileChartResponse` interface
- [ ] `DuoReferralSummary` interface
- [ ] `DuoReferralsResponse` interface
- [ ] `FunnelStep` interface
- [ ] `ProfileFunnelResponse` interface
- [ ] `MessageSourceBreakdown` interface
### 6.5 Create Barrel Export
**File**: `src/dto/profile-analytics/index.ts`
- [ ] Create barrel export:
```typescript
export * from './tracking.dto'
export * from './query.dto'
export * from './responses.dto'
```
### 6.6 Update Imports Across Codebase
- [ ] Find all imports of `profile-analytics.dto`:
```bash
grep -r "from '@/dto/profile-analytics.dto'" src/
```
- [ ] Update to new path:
```typescript
// Old
import { TrackProfileDiscoveryDto } from '@/dto/profile-analytics.dto'
// New
import { TrackProfileDiscoveryDto } from '@/dto/profile-analytics'
```
- [ ] Files to update (likely):
- [ ] `profile-tracking.controller.ts`
- [ ] `profile-analytics-facade.service.ts`
- [ ] `profile-event-tracker.service.ts`
- [ ] `profile-metrics-query.service.ts`
### 6.7 Delete Original File
- [ ] Delete `src/dto/profile-analytics.dto.ts`
### 6.8 Final Verification
- [ ] Run build: `pnpm build`
- [ ] Verify no import errors
- [ ] Run full test suite
- [ ] Check TypeScript compiler output (no errors)
---
## Post-Implementation Verification
### Code Quality Checks
- [ ] Run linter: `pnpm lint`
- [ ] Fix all linting warnings
- [ ] Run prettier: `pnpm format`
- [ ] Check for unused imports: `pnpm lint:unused-imports`
### Test Coverage
- [ ] Run test coverage report: `pnpm test:cov`
- [ ] Verify overall coverage ≥ 80%
- [ ] Verify new services coverage ≥ 80%
- [ ] Review uncovered lines (should be minimal)
### Build Verification
- [ ] Clean build: `rm -rf dist && pnpm build`
- [ ] Verify no TypeScript errors
- [ ] Check bundle size (should be ≤ 5% increase)
- [ ] Verify tree-shaking working (check bundle analysis)
### Runtime Verification
- [ ] Start dev server: `pnpm dev`
- [ ] Verify no startup errors
- [ ] Check all CRON jobs scheduled correctly
- [ ] Test tracking endpoints manually
- [ ] Test query endpoints manually
- [ ] Test admin endpoints manually
- [ ] Verify queue jobs processing
- [ ] Check logs for warnings/errors
### Documentation Updates
- [ ] Update architecture docs (if exists)
- [ ] Update developer guide (if needed)
- [ ] Add migration notes to CHANGELOG
- [ ] Update README (if service structure explained)
### Performance Testing
- [ ] Benchmark tracking endpoint latency (should be ≤ 50ms)
- [ ] Benchmark query endpoint latency (should be unchanged)
- [ ] Check memory usage (should be ≤ 5% increase)
- [ ] Verify Redis connection pooling working
### Final Sign-off
- [ ] Code review by team lead
- [ ] QA testing pass
- [ ] Merge to main branch
- [ ] Deploy to staging
- [ ] Monitor for errors (24 hours)
- [ ] Deploy to production
---
## Rollback Plan (If Issues Found)
### Emergency Rollback Steps
1. [ ] Identify issue and severity
2. [ ] Revert merge commit: `git revert <commit-hash>`
3. [ ] Deploy revert to production
4. [ ] Document issue in ticket
5. [ ] Fix in separate branch
6. [ ] Re-test thoroughly before re-deploying
### Known Rollback Triggers
- **Critical**: Tracking events lost (data integrity)
- **Critical**: CRON jobs failing (aggregation stopped)
- **High**: API endpoints returning 500 errors
- **High**: Test coverage drops below 70%
- **Medium**: Performance regression > 20%
- **Low**: Linting warnings
---
## Success Metrics
After completion, verify:
- [ ] **Line Count**: All files ≤ 400 lines (target: ≤ 350 avg)
- [ ] **Test Coverage**: ≥ 80% for all new services
- [ ] **API Compatibility**: 100% (no breaking changes)
- [ ] **Build Time**: ≤ 5% increase
- [ ] **Response Time**: ≤ 5% increase
- [ ] **Memory Usage**: ≤ 10% increase
- [ ] **Bundle Size**: ≤ 5% increase
- [ ] **Zero Downtime**: No service interruption during deployment
---
## Notes
- Estimated total time: 16-23 hours (2-3 days)
- Can be parallelized across team members
- Phase 1 (ProfileAnalytics) is critical path - do first
- Phases 2-6 can be done in parallel after Phase 1
- Each phase is independently testable
- Rollback possible at any phase boundary
**Status**: Not Started
**Last Updated**: 2026-01-22