Procházet zdrojové kódy

feat(stats): enforce required channelId and machine fields in event payload validation

Dave před 3 měsíci
rodič
revize
c2316036ae

+ 34 - 6
PHASE4_REFACTOR_SUMMARY.md

@@ -1,6 +1,7 @@
 # Phase 4: Enforce Required channelId and machine Fields
 # Phase 4: Enforce Required channelId and machine Fields
 
 
 ## Overview
 ## Overview
+
 This phase makes `channelId` and `machine` required (non-optional) throughout the entire stats pipeline, enforcing data integrity from the client through to the database layer.
 This phase makes `channelId` and `machine` required (non-optional) throughout the entire stats pipeline, enforcing data integrity from the client through to the database layer.
 
 
 ## Changes Made
 ## Changes Made
@@ -8,12 +9,14 @@ This phase makes `channelId` and `machine` required (non-optional) throughout th
 ### 1. Prisma Models (mongo-stats schema)
 ### 1. Prisma Models (mongo-stats schema)
 
 
 **Updated Files:**
 **Updated Files:**
+
 - `prisma/mongo-stats/schema/user.prisma`
 - `prisma/mongo-stats/schema/user.prisma`
 - `prisma/mongo-stats/schema/user-login-history.prisma`
 - `prisma/mongo-stats/schema/user-login-history.prisma`
 - `prisma/mongo-stats/schema/ads-click-history.prisma`
 - `prisma/mongo-stats/schema/ads-click-history.prisma`
 - `prisma/mongo-stats/schema/events.prisma`
 - `prisma/mongo-stats/schema/events.prisma`
 
 
 **Changes:**
 **Changes:**
+
 - Removed `?` (optional) marker from `channelId` field in:
 - Removed `?` (optional) marker from `channelId` field in:
   - `User` model
   - `User` model
   - `UserLoginHistory` model
   - `UserLoginHistory` model
@@ -31,6 +34,7 @@ This phase makes `channelId` and `machine` required (non-optional) throughout th
   - `AdImpressionEvents` model
   - `AdImpressionEvents` model
 
 
 **Indexes Added:**
 **Indexes Added:**
+
 - Added composite index `@@index([channelId, createAt])` to `User` for channel-based queries
 - Added composite index `@@index([channelId, createAt])` to `User` for channel-based queries
 - Added composite index `@@index([channelId, createAt])` to `UserLoginHistory` for channel-based reporting
 - Added composite index `@@index([channelId, createAt])` to `UserLoginHistory` for channel-based reporting
 - Added composite index `@@index([channelId, uid, clickAt])` to `AdsClickHistory` for channel+user analytics
 - Added composite index `@@index([channelId, uid, clickAt])` to `AdsClickHistory` for channel+user analytics
@@ -41,32 +45,36 @@ This phase makes `channelId` and `machine` required (non-optional) throughout th
 ### 2. Event Payload Interfaces
 ### 2. Event Payload Interfaces
 
 
 **Updated File:**
 **Updated File:**
+
 - `libs/common/src/events/user-login-event.dto.ts`
 - `libs/common/src/events/user-login-event.dto.ts`
 
 
 **Changes:**
 **Changes:**
+
 ```typescript
 ```typescript
 // Before
 // Before
 export interface UserLoginEventPayload {
 export interface UserLoginEventPayload {
-  channelId?: string;  // optional
-  machine?: string;    // optional
+  channelId?: string; // optional
+  machine?: string; // optional
 }
 }
 
 
 // After
 // After
 export interface UserLoginEventPayload {
 export interface UserLoginEventPayload {
-  channelId: string;  // required
-  machine: string;    // required
+  channelId: string; // required
+  machine: string; // required
 }
 }
 ```
 ```
 
 
 ### 3. DTOs (API Input Validation)
 ### 3. DTOs (API Input Validation)
 
 
 **Updated Files:**
 **Updated Files:**
+
 - `apps/box-app-api/src/feature/auth/login.dto.ts`
 - `apps/box-app-api/src/feature/auth/login.dto.ts`
 - `apps/box-app-api/src/feature/ads/dto/ad-click.dto.ts`
 - `apps/box-app-api/src/feature/ads/dto/ad-click.dto.ts`
 - `apps/box-app-api/src/feature/ads/dto/ad-impression.dto.ts`
 - `apps/box-app-api/src/feature/ads/dto/ad-impression.dto.ts`
 
 
 **Changes:**
 **Changes:**
-- `LoginDto`: 
+
+- `LoginDto`:
   - `channelId` changed from `@IsOptional()` to `@IsNotEmpty()` @IsString()`
   - `channelId` changed from `@IsOptional()` to `@IsNotEmpty()` @IsString()`
   - `machine` changed from `@IsOptional()` to `@IsNotEmpty() @IsString()`
   - `machine` changed from `@IsOptional()` to `@IsNotEmpty() @IsString()`
 
 
@@ -81,13 +89,14 @@ export interface UserLoginEventPayload {
 ### 4. Service Methods
 ### 4. Service Methods
 
 
 **Updated File:**
 **Updated File:**
+
 - `apps/box-stats-api/src/feature/user-login/user-login.service.ts`
 - `apps/box-stats-api/src/feature/user-login/user-login.service.ts`
 
 
 **Changes:**
 **Changes:**
+
 - `createUser()` method:
 - `createUser()` method:
   - Removed `?? null` coalescing for `channelId` and `machine`
   - Removed `?? null` coalescing for `channelId` and `machine`
   - Now directly assigns values without null fallback (since they're required)
   - Now directly assigns values without null fallback (since they're required)
-  
 - `recordLogin()` method:
 - `recordLogin()` method:
   - Removed `?? null` coalescing for `channelId` and `machine`
   - Removed `?? null` coalescing for `channelId` and `machine`
   - Now directly assigns values
   - Now directly assigns values
@@ -95,9 +104,11 @@ export interface UserLoginEventPayload {
 ### 5. Stats Event Consumer
 ### 5. Stats Event Consumer
 
 
 **Updated File:**
 **Updated File:**
+
 - `apps/box-stats-api/src/feature/stats-events/stats-events.consumer.ts`
 - `apps/box-stats-api/src/feature/stats-events/stats-events.consumer.ts`
 
 
 **Changes:**
 **Changes:**
+
 - `handleAdClick()`:
 - `handleAdClick()`:
   - Validation now checks: `!payload.channelId || !payload.machine`
   - Validation now checks: `!payload.channelId || !payload.machine`
   - Removed `?? null` coalescing, now directly assigns values
   - Removed `?? null` coalescing, now directly assigns values
@@ -114,11 +125,14 @@ export interface UserLoginEventPayload {
 ### 6. Channel Management Updates
 ### 6. Channel Management Updates
 
 
 **Updated Files:**
 **Updated Files:**
+
 - `apps/box-mgnt-api/src/mgnt-backend/feature/channel/channel.dto.ts`
 - `apps/box-mgnt-api/src/mgnt-backend/feature/channel/channel.dto.ts`
 - `apps/box-mgnt-api/src/mgnt-backend/feature/channel/channel.service.ts`
 - `apps/box-mgnt-api/src/mgnt-backend/feature/channel/channel.service.ts`
 
 
 **Changes:**
 **Changes:**
+
 - Added required `channelId` field to `CreateChannelDto`:
 - Added required `channelId` field to `CreateChannelDto`:
+
   ```typescript
   ```typescript
   @IsNotEmpty()
   @IsNotEmpty()
   @IsString()
   @IsString()
@@ -198,19 +212,23 @@ StatsEventsConsumer.handleAdClick()
 ## Validation Strategy
 ## Validation Strategy
 
 
 ### Client-Side (Frontend)
 ### Client-Side (Frontend)
+
 - `channelId` and `machine` must be provided in all API requests
 - `channelId` and `machine` must be provided in all API requests
 - Both fields are required and cannot be empty/null
 - Both fields are required and cannot be empty/null
 
 
 ### API Layer (NestJS)
 ### API Layer (NestJS)
+
 - DTOs enforce `@IsNotEmpty()` and `@IsString()` decorators
 - DTOs enforce `@IsNotEmpty()` and `@IsString()` decorators
 - ValidationPipe validates before reaching controllers
 - ValidationPipe validates before reaching controllers
 - Controllers reject requests with missing/invalid values (400 Bad Request)
 - Controllers reject requests with missing/invalid values (400 Bad Request)
 
 
 ### Message Queue (RabbitMQ)
 ### Message Queue (RabbitMQ)
+
 - Event payloads typed as required fields
 - Event payloads typed as required fields
 - Consumer validation checks for presence before persisting
 - Consumer validation checks for presence before persisting
 
 
 ### Database (MongoDB)
 ### Database (MongoDB)
+
 - Prisma schema enforces non-optional fields
 - Prisma schema enforces non-optional fields
 - Database will reject inserts/updates without `channelId` and `machine`
 - Database will reject inserts/updates without `channelId` and `machine`
 - Composite indexes optimize channel+user based analytics queries
 - Composite indexes optimize channel+user based analytics queries
@@ -218,6 +236,7 @@ StatsEventsConsumer.handleAdClick()
 ## Migration Path
 ## Migration Path
 
 
 For existing data with null `channelId` or `machine`:
 For existing data with null `channelId` or `machine`:
+
 1. No automatic migration - manual data cleanup required
 1. No automatic migration - manual data cleanup required
 2. All NEW requests MUST provide both fields
 2. All NEW requests MUST provide both fields
 3. Recommend setting default values for legacy data during migration window
 3. Recommend setting default values for legacy data during migration window
@@ -226,6 +245,7 @@ For existing data with null `channelId` or `machine`:
 ## Testing Recommendations
 ## Testing Recommendations
 
 
 ### Unit Tests
 ### Unit Tests
+
 - ✅ LoginDto validation with required fields
 - ✅ LoginDto validation with required fields
 - ✅ AdClickDto validation with required fields
 - ✅ AdClickDto validation with required fields
 - ✅ AdImpressionDto validation with required fields
 - ✅ AdImpressionDto validation with required fields
@@ -233,12 +253,14 @@ For existing data with null `channelId` or `machine`:
 - ✅ StatsEventsConsumer validation of required fields
 - ✅ StatsEventsConsumer validation of required fields
 
 
 ### Integration Tests
 ### Integration Tests
+
 - ✅ End-to-end login flow with channelId + machine
 - ✅ End-to-end login flow with channelId + machine
 - ✅ Ad click tracking with channelId + machine
 - ✅ Ad click tracking with channelId + machine
 - ✅ Ad impression tracking with channelId + machine
 - ✅ Ad impression tracking with channelId + machine
 - ✅ Data persists correctly to MongoDB
 - ✅ Data persists correctly to MongoDB
 
 
 ### Manual Testing
 ### Manual Testing
+
 ```bash
 ```bash
 # Test 1: Login with required fields
 # Test 1: Login with required fields
 curl -X POST http://localhost:3301/api/v1/auth/login \
 curl -X POST http://localhost:3301/api/v1/auth/login \
@@ -274,31 +296,37 @@ curl -X POST http://localhost:3301/api/v1/auth/login \
 ## Files Modified Summary
 ## Files Modified Summary
 
 
 ### Prisma Schemas (4 files)
 ### Prisma Schemas (4 files)
+
 - `prisma/mongo-stats/schema/user.prisma` ✅
 - `prisma/mongo-stats/schema/user.prisma` ✅
 - `prisma/mongo-stats/schema/user-login-history.prisma` ✅
 - `prisma/mongo-stats/schema/user-login-history.prisma` ✅
 - `prisma/mongo-stats/schema/ads-click-history.prisma` ✅
 - `prisma/mongo-stats/schema/ads-click-history.prisma` ✅
 - `prisma/mongo-stats/schema/events.prisma` ✅
 - `prisma/mongo-stats/schema/events.prisma` ✅
 
 
 ### Event Interfaces (1 file)
 ### Event Interfaces (1 file)
+
 - `libs/common/src/events/user-login-event.dto.ts` ✅
 - `libs/common/src/events/user-login-event.dto.ts` ✅
 
 
 ### DTOs (3 files)
 ### DTOs (3 files)
+
 - `apps/box-app-api/src/feature/auth/login.dto.ts` ✅
 - `apps/box-app-api/src/feature/auth/login.dto.ts` ✅
 - `apps/box-app-api/src/feature/ads/dto/ad-click.dto.ts` ✅
 - `apps/box-app-api/src/feature/ads/dto/ad-click.dto.ts` ✅
 - `apps/box-app-api/src/feature/ads/dto/ad-impression.dto.ts` ✅
 - `apps/box-app-api/src/feature/ads/dto/ad-impression.dto.ts` ✅
 
 
 ### Services (3 files)
 ### Services (3 files)
+
 - `apps/box-stats-api/src/feature/user-login/user-login.service.ts` ✅
 - `apps/box-stats-api/src/feature/user-login/user-login.service.ts` ✅
 - `apps/box-stats-api/src/feature/stats-events/stats-events.consumer.ts` ✅
 - `apps/box-stats-api/src/feature/stats-events/stats-events.consumer.ts` ✅
 - `apps/box-mgnt-api/src/mgnt-backend/feature/channel/channel.service.ts` ✅
 - `apps/box-mgnt-api/src/mgnt-backend/feature/channel/channel.service.ts` ✅
 
 
 ### Channel Management (2 files)
 ### Channel Management (2 files)
+
 - `apps/box-mgnt-api/src/mgnt-backend/feature/channel/channel.dto.ts` ✅
 - `apps/box-mgnt-api/src/mgnt-backend/feature/channel/channel.dto.ts` ✅
 
 
 ## Compilation Status
 ## Compilation Status
 
 
 ✅ **Prisma Generation**: All three schemas passed validation
 ✅ **Prisma Generation**: All three schemas passed validation
 ✅ **TypeScript**: Phase 4 critical files compile without errors
 ✅ **TypeScript**: Phase 4 critical files compile without errors
+
 - Note: Existing errors from Phase 3 (Ads-Channel split) remain in ad-related files but don't impact Phase 4 functionality
 - Note: Existing errors from Phase 3 (Ads-Channel split) remain in ad-related files but don't impact Phase 4 functionality
 
 
 ## Next Steps
 ## Next Steps

+ 23 - 3
apps/box-stats-api/src/feature/stats-events/stats-events.consumer.ts

@@ -236,7 +236,13 @@ export class StatsEventsConsumer implements OnModuleInit, OnModuleDestroy {
 
 
     // Validate required fields (use adsId from publisher, adId for backward compatibility)
     // Validate required fields (use adsId from publisher, adId for backward compatibility)
     const adId = payload?.adId || payload?.adsId;
     const adId = payload?.adId || payload?.adsId;
-    if (!payload || !payload.uid || !adId || !payload.channelId || !payload.machine) {
+    if (
+      !payload ||
+      !payload.uid ||
+      !adId ||
+      !payload.channelId ||
+      !payload.machine
+    ) {
       this.logger.warn(
       this.logger.warn(
         `Malformed ad.click message (missing uid, adId, channelId, or machine), dropping: ${msg.content.toString()}`,
         `Malformed ad.click message (missing uid, adId, channelId, or machine), dropping: ${msg.content.toString()}`,
       );
       );
@@ -293,7 +299,14 @@ export class StatsEventsConsumer implements OnModuleInit, OnModuleDestroy {
   private async handleVideoClick(msg: ConsumeMessage | null): Promise<void> {
   private async handleVideoClick(msg: ConsumeMessage | null): Promise<void> {
     if (!msg) return;
     if (!msg) return;
     const payload = this.parseJson<VideoClickMessage>(msg);
     const payload = this.parseJson<VideoClickMessage>(msg);
-    if (!payload || !payload.messageId || !payload.uid || !payload.videoId || !payload.channelId || !payload.machine) {
+    if (
+      !payload ||
+      !payload.messageId ||
+      !payload.uid ||
+      !payload.videoId ||
+      !payload.channelId ||
+      !payload.machine
+    ) {
       this.logger.warn(
       this.logger.warn(
         `Malformed video.click message (missing required fields), dropping: ${msg?.content.toString()}`,
         `Malformed video.click message (missing required fields), dropping: ${msg?.content.toString()}`,
       );
       );
@@ -348,7 +361,14 @@ export class StatsEventsConsumer implements OnModuleInit, OnModuleDestroy {
   private async handleAdImpression(msg: ConsumeMessage | null): Promise<void> {
   private async handleAdImpression(msg: ConsumeMessage | null): Promise<void> {
     if (!msg) return;
     if (!msg) return;
     const payload = this.parseJson<AdImpressionMessage>(msg);
     const payload = this.parseJson<AdImpressionMessage>(msg);
-    if (!payload || !payload.messageId || !payload.uid || !payload.adId || !payload.channelId || !payload.machine) {
+    if (
+      !payload ||
+      !payload.messageId ||
+      !payload.uid ||
+      !payload.adId ||
+      !payload.channelId ||
+      !payload.machine
+    ) {
       this.logger.warn(
       this.logger.warn(
         `Malformed ad.impression message (missing required fields), dropping: ${msg?.content.toString()}`,
         `Malformed ad.impression message (missing required fields), dropping: ${msg?.content.toString()}`,
       );
       );