From c8d902f858b99d12f4eeee8202778065480e0271 Mon Sep 17 00:00:00 2001 From: OluRemiFour Date: Tue, 28 Jul 2026 09:06:26 +0100 Subject: [PATCH 1/2] Stop publishing fabricated enrollment conversion metrics generated with Math.random --- src/courses/courses.controller.ts | 4 ++-- src/courses/courses.module.ts | 4 +++- src/courses/courses.service.ts | 4 +++- src/routing/interceptors/routing.interceptor.ts | 1 - src/utils/masking/kpi.service.ts | 12 ++++++------ 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/courses/courses.controller.ts b/src/courses/courses.controller.ts index cb381c04..e5ae6c5d 100644 --- a/src/courses/courses.controller.ts +++ b/src/courses/courses.controller.ts @@ -57,8 +57,8 @@ export class CoursesController { @ApiOperation({ summary: 'Get a specific course by ID' }) @ApiResponse({ status: 200, description: 'Returns course details' }) @ApiResponse({ status: 404, description: 'Course not found' }) - async findOne(@Param('id') id: string) { - return this.coursesService.findOne(id); + async findOne(@Param('id') id: string, @Request() req) { + return this.coursesService.findOne(id, req.user); } @Put(':id') diff --git a/src/courses/courses.module.ts b/src/courses/courses.module.ts index 5c1354f5..34df4fa7 100644 --- a/src/courses/courses.module.ts +++ b/src/courses/courses.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { CoursesService } from './courses.service'; import { EnrollmentsService } from './enrollments.service'; @@ -10,6 +10,7 @@ import { CourseReview } from './entities/course-review.entity'; import { CourseModule } from './entities/course-module.entity'; import { BulkOperation } from './entities/bulk-operation.entity'; import { CachingModule } from '../caching/caching.module'; +import { AnalyticsModule } from '../analytics/analytics.module'; import { PaginationService } from '../common/services/pagination.service'; @@ -17,6 +18,7 @@ import { PaginationService } from '../common/services/pagination.service'; imports: [ TypeOrmModule.forFeature([Course, Enrollment, CourseReview, CourseModule, BulkOperation]), CachingModule, + forwardRef(() => AnalyticsModule), ], providers: [CoursesService, EnrollmentsService, PaginationService], controllers: [CoursesController, EnrollmentsController], diff --git a/src/courses/courses.service.ts b/src/courses/courses.service.ts index 4afcfa63..cb2a99fd 100644 --- a/src/courses/courses.service.ts +++ b/src/courses/courses.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Optional } from '@nestjs/common'; +import { Injectable, Optional, Inject, forwardRef } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { InjectRepository } from '@nestjs/typeorm'; import { In, Repository } from 'typeorm'; @@ -31,6 +31,8 @@ import { PaginationQueryDto } from '../common/dto/pagination.dto'; import { OffsetPaginatedResponse } from '../common/interfaces/pagination.interface'; import { PaginationService } from '../common/services/pagination.service'; +import { AnalyticsService } from '../analytics/analytics.service'; +import { EventType } from '../analytics/entities/event.entity'; function checkUserRole(user?: User, ...roleNames: UserRole[]): boolean { if (!user) return false; diff --git a/src/routing/interceptors/routing.interceptor.ts b/src/routing/interceptors/routing.interceptor.ts index cce0e4a2..7a56fb46 100644 --- a/src/routing/interceptors/routing.interceptor.ts +++ b/src/routing/interceptors/routing.interceptor.ts @@ -233,7 +233,6 @@ export class RoutingInterceptor implements NestInterceptor { // Include experimental data if (transformed.analytics) { transformed.analytics.experimental = { - predictiveScores: Math.random(), behaviorInsights: 'beta-feature-data', }; } diff --git a/src/utils/masking/kpi.service.ts b/src/utils/masking/kpi.service.ts index 9abcbe53..7ee9b24c 100644 --- a/src/utils/masking/kpi.service.ts +++ b/src/utils/masking/kpi.service.ts @@ -8,7 +8,7 @@ import { MetricsService } from './metrics.service'; import { User } from '../../users/entities/user.entity'; import { Course } from '../../courses/entities/course.entity'; import { Enrollment } from '../../courses/entities/enrollment.entity'; -import { AnalyticsEvent } from '../../analytics/entities/event.entity'; +import { AnalyticsEvent, EventType } from '../../analytics/entities/event.entity'; import { Payment, PaymentStatus } from '../../payments/entities/payment.entity'; @Injectable() @@ -111,16 +111,16 @@ export class KpiService { } async calculateEnrollmentConversionRate(): Promise { - // This is a simplified version. A real-world scenario would track views vs enrollments. - // Here we'll simulate it by looking at enrollments vs total users. - // For a more accurate metric, you'd need an event tracking system for 'course_viewed'. const courses = await this.courseRepository.find(); this.metricsService.enrollmentConversionGauge.reset(); for (const course of courses) { const enrollments = await this.enrollmentRepository.count({ where: { courseId: course.id } }); - // Placeholder for views. In a real system, you'd query an analytics table. - const views = enrollments * 5 + Math.floor(Math.random() * 100); // Simulate views + const views = await this.eventRepository + .createQueryBuilder('event') + .where('event.eventType = :eventType', { eventType: EventType.COURSE_VIEW }) + .andWhere("event.properties->>'courseId' = :courseId", { courseId: course.id }) + .getCount(); const conversionRate = views > 0 ? (enrollments / views) * 100 : 0; this.metricsService.enrollmentConversionGauge.labels(course.id).set(conversionRate); From 7a08b97ce95c0ff09ee59f9e1838d1fa748cf86e Mon Sep 17 00:00:00 2001 From: OluRemiFour Date: Tue, 28 Jul 2026 09:09:37 +0100 Subject: [PATCH 2/2] quick fix [ci skip]