From e206df39b45abcb7f094a0e0a1369c9a07013ca1 Mon Sep 17 00:00:00 2001 From: Musa Khalid <112591148+Mkalbani@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:32:09 +0000 Subject: [PATCH 1/2] Implemented Retrieval & Statistics Endpoints --- .../controllers/progress.controller.ts | 103 ++++++++++++++++++ .../src/progress/dtos/category-stats.dto.ts | 33 ++++++ .../src/progress/dtos/overall-stats.dto.ts | 33 ++++++ .../progress/dtos/paginated-progress.dto.ts | 44 ++++++++ .../src/progress/dtos/progress-history.dto.ts | 57 ++++++++++ backend/src/progress/progress.module.ts | 14 ++- .../providers/get-category-stats.provider.ts | 57 ++++++++++ .../providers/get-overall-stats.provider.ts | 49 +++++++++ .../get-progress-history.provider.ts | 28 +++++ package-lock.json | 45 +------- 10 files changed, 422 insertions(+), 41 deletions(-) create mode 100644 backend/src/progress/controllers/progress.controller.ts create mode 100644 backend/src/progress/dtos/category-stats.dto.ts create mode 100644 backend/src/progress/dtos/overall-stats.dto.ts create mode 100644 backend/src/progress/dtos/paginated-progress.dto.ts create mode 100644 backend/src/progress/dtos/progress-history.dto.ts create mode 100644 backend/src/progress/providers/get-category-stats.provider.ts create mode 100644 backend/src/progress/providers/get-overall-stats.provider.ts create mode 100644 backend/src/progress/providers/get-progress-history.provider.ts diff --git a/backend/src/progress/controllers/progress.controller.ts b/backend/src/progress/controllers/progress.controller.ts new file mode 100644 index 0000000..66ec87c --- /dev/null +++ b/backend/src/progress/controllers/progress.controller.ts @@ -0,0 +1,103 @@ +import { + Controller, + Get, + Param, + Query, + UseGuards, + BadRequestException, +} from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +import { AuthGuard } from '@nestjs/passport'; +import { paginationQueryDto } from '../../common/pagination/paginationQueryDto'; +import { GetProgressHistoryProvider } from '../providers/get-progress-history.provider'; +import { GetCategoryStatsProvider } from '../providers/get-category-stats.provider'; +import { GetOverallStatsProvider } from '../providers/get-overall-stats.provider'; +import { PaginatedProgressDto } from '../dtos/paginated-progress.dto'; +import { CategoryStatsDto } from '../dtos/category-stats.dto'; +import { OverallStatsDto } from '../dtos/overall-stats.dto'; +import { ActiveUser } from '../../auth/decorators/activeUser.decorator'; +import { ActiveUserData } from '../../auth/interfaces/activeInterface'; + +@Controller('progress') +@ApiTags('Progress') +@UseGuards(AuthGuard('jwt')) +@ApiBearerAuth() +export class ProgressController { + constructor( + private readonly getProgressHistoryProvider: GetProgressHistoryProvider, + private readonly getCategoryStatsProvider: GetCategoryStatsProvider, + private readonly getOverallStatsProvider: GetOverallStatsProvider, + ) {} + + @Get() + @ApiOperation({ + summary: 'Get paginated progress history', + description: + 'Retrieve user answer history ordered by most recent attempts first', + }) + @ApiResponse({ + status: 200, + description: 'Paginated progress history', + type: PaginatedProgressDto, + }) + async getProgressHistory( + @ActiveUser() user: ActiveUserData, + @Query() paginationDto: paginationQueryDto, + ) { + if (!user || !user.sub) { + throw new BadRequestException('User not found'); + } + + const { limit = 10, page = 1 } = paginationDto; + return this.getProgressHistoryProvider.getProgressHistory( + user.sub, + page, + limit, + ); + } + + @Get('stats') + @ApiOperation({ + summary: 'Get overall user statistics', + description: + 'Retrieve total attempts, correct answers, accuracy, points earned, and time spent', + }) + @ApiResponse({ + status: 200, + description: 'Overall user statistics', + type: OverallStatsDto, + }) + async getOverallStats(@ActiveUser() user: ActiveUserData) { + if (!user || !user.sub) { + throw new BadRequestException('User not found'); + } + + return this.getOverallStatsProvider.getOverallStats(user.sub); + } + + @Get('category/:id') + @ApiOperation({ + summary: 'Get category-specific statistics', + description: + 'Retrieve total attempts, correct answers, and accuracy for a specific category', + }) + @ApiResponse({ + status: 200, + description: 'Category statistics', + type: CategoryStatsDto, + }) + async getCategoryStats( + @ActiveUser() user: ActiveUserData, + @Param('id') categoryId: string, + ) { + if (!user || !user.sub) { + throw new BadRequestException('User not found'); + } + + if (!categoryId) { + throw new BadRequestException('Category ID is required'); + } + + return this.getCategoryStatsProvider.getCategoryStats(user.sub, categoryId); + } +} diff --git a/backend/src/progress/dtos/category-stats.dto.ts b/backend/src/progress/dtos/category-stats.dto.ts new file mode 100644 index 0000000..211371f --- /dev/null +++ b/backend/src/progress/dtos/category-stats.dto.ts @@ -0,0 +1,33 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CategoryStatsDto { + @ApiProperty({ + example: '123e4567-e89b-12d3-a456-426614174000', + description: 'Category ID', + }) + categoryId: string; + + @ApiProperty({ + example: 'Algorithms', + description: 'Category name', + }) + categoryName: string; + + @ApiProperty({ + example: 25, + description: 'Total number of attempts in this category', + }) + totalAttempts: number; + + @ApiProperty({ + example: 20, + description: 'Number of correct answers', + }) + correctAnswers: number; + + @ApiProperty({ + example: 80, + description: 'Accuracy percentage', + }) + accuracy: number; +} diff --git a/backend/src/progress/dtos/overall-stats.dto.ts b/backend/src/progress/dtos/overall-stats.dto.ts new file mode 100644 index 0000000..2b48b4c --- /dev/null +++ b/backend/src/progress/dtos/overall-stats.dto.ts @@ -0,0 +1,33 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class OverallStatsDto { + @ApiProperty({ + example: 150, + description: 'Total number of attempts across all categories', + }) + totalAttempts: number; + + @ApiProperty({ + example: 120, + description: 'Total number of correct answers', + }) + totalCorrect: number; + + @ApiProperty({ + example: 80, + description: 'Overall accuracy percentage', + }) + accuracy: number; + + @ApiProperty({ + example: 1500, + description: 'Total points earned across all puzzles', + }) + totalPointsEarned: number; + + @ApiProperty({ + example: 3600, + description: 'Total time spent on all puzzles in seconds', + }) + totalTimeSpent: number; +} diff --git a/backend/src/progress/dtos/paginated-progress.dto.ts b/backend/src/progress/dtos/paginated-progress.dto.ts new file mode 100644 index 0000000..d78833d --- /dev/null +++ b/backend/src/progress/dtos/paginated-progress.dto.ts @@ -0,0 +1,44 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ProgressHistoryDto } from './progress-history.dto'; + +export class PaginatedProgressDto { + @ApiProperty({ + type: [ProgressHistoryDto], + description: 'Array of progress history records', + }) + data: ProgressHistoryDto[]; + + @ApiProperty({ + example: { + itemsPerPage: 10, + totalItems: 150, + currentPage: 1, + totalPages: 15, + }, + description: 'Pagination metadata', + }) + meta: { + itemsPerPage: number; + totalItems: number; + currentPage: number; + totalPages: number; + }; + + @ApiProperty({ + example: { + first: 'http://localhost:3000/progress?limit=10&page=1', + last: 'http://localhost:3000/progress?limit=10&page=15', + current: 'http://localhost:3000/progress?limit=10&page=1', + previous: 'http://localhost:3000/progress?limit=10&page=1', + next: 'http://localhost:3000/progress?limit=10&page=2', + }, + description: 'Navigation links for pagination', + }) + links: { + first: string; + last: string; + current: string; + previous: string; + next: string; + }; +} diff --git a/backend/src/progress/dtos/progress-history.dto.ts b/backend/src/progress/dtos/progress-history.dto.ts new file mode 100644 index 0000000..fa52771 --- /dev/null +++ b/backend/src/progress/dtos/progress-history.dto.ts @@ -0,0 +1,57 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class ProgressHistoryDto { + @ApiProperty({ + example: '123e4567-e89b-12d3-a456-426614174000', + description: 'Progress record ID', + }) + id: string; + + @ApiProperty({ + example: '123e4567-e89b-12d3-a456-426614174001', + description: 'Puzzle ID', + }) + puzzleId: string; + + @ApiProperty({ + example: 'What is the time complexity of binary search?', + description: 'The puzzle question', + }) + question: string; + + @ApiProperty({ + example: 'O(log n)', + description: 'User answer', + }) + userAnswer: string; + + @ApiProperty({ + example: true, + description: 'Whether the answer was correct', + }) + isCorrect: boolean; + + @ApiProperty({ + example: 10, + description: 'Points earned for this attempt', + }) + pointsEarned: number; + + @ApiProperty({ + example: 45, + description: 'Time spent on this puzzle in seconds', + }) + timeSpent: number; + + @ApiProperty({ + example: '2024-01-28T10:30:00Z', + description: 'When the puzzle was attempted', + }) + attemptedAt: Date; + + @ApiProperty({ + example: '123e4567-e89b-12d3-a456-426614174002', + description: 'Category ID', + }) + categoryId: string; +} diff --git a/backend/src/progress/progress.module.ts b/backend/src/progress/progress.module.ts index 74202c2..5b8efa5 100644 --- a/backend/src/progress/progress.module.ts +++ b/backend/src/progress/progress.module.ts @@ -5,14 +5,26 @@ import { User } from '../users/user.entity'; import { Puzzle } from '../puzzles/entities/puzzle.entity'; import { Streak } from '../streak/entities/streak.entity'; import { DailyQuest } from '../quests/entities/daily-quest.entity'; +import { ProgressController } from './controllers/progress.controller'; import { ProgressService } from './progress.service'; +import { GetProgressHistoryProvider } from './providers/get-progress-history.provider'; +import { GetCategoryStatsProvider } from './providers/get-category-stats.provider'; +import { GetOverallStatsProvider } from './providers/get-overall-stats.provider'; import { ProgressCalculationProvider } from './providers/progress-calculation.provider'; + @Module({ imports: [ TypeOrmModule.forFeature([UserProgress, User, Puzzle, Streak, DailyQuest]), ], - providers: [ProgressService, ProgressCalculationProvider], + controllers: [ProgressController], + providers: [ + ProgressService, + GetProgressHistoryProvider, + GetCategoryStatsProvider, + GetOverallStatsProvider, + ProgressCalculationProvider, + ], exports: [ProgressService, ProgressCalculationProvider, TypeOrmModule], }) export class ProgressModule {} diff --git a/backend/src/progress/providers/get-category-stats.provider.ts b/backend/src/progress/providers/get-category-stats.provider.ts new file mode 100644 index 0000000..7d24990 --- /dev/null +++ b/backend/src/progress/providers/get-category-stats.provider.ts @@ -0,0 +1,57 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { UserProgress } from '../entities/progress.entity'; +import { CategoryStatsDto } from '../dtos/category-stats.dto'; + +@Injectable() +export class GetCategoryStatsProvider { + constructor( + @InjectRepository(UserProgress) + private readonly progressRepo: Repository, + ) {} + + async getCategoryStats(userId: string, categoryId: string) { + const result = await this.progressRepo + .createQueryBuilder('progress') + .select('progress.categoryId', 'categoryId') + .addSelect('COUNT(*)', 'totalAttempts') + .addSelect('SUM(CASE WHEN progress.isCorrect = true THEN 1 ELSE 0 END)', 'correctAnswers') + .where('progress.userId = :userId', { userId }) + .andWhere('progress.categoryId = :categoryId', { categoryId }) + .groupBy('progress.categoryId') + .getRawOne(); + + if (!result) { + return { + categoryId, + categoryName: '', + totalAttempts: 0, + correctAnswers: 0, + accuracy: 0, + }; + } + + const totalAttempts = parseInt(result.totalAttempts, 10) || 0; + const correctAnswers = parseInt(result.correctAnswers, 10) || 0; + const accuracy = + totalAttempts > 0 ? Math.round((correctAnswers / totalAttempts) * 100) : 0; + + // Get category name + const category = await this.progressRepo + .createQueryBuilder('progress') + .leftJoinAndSelect('progress.category', 'category') + .where('progress.categoryId = :categoryId', { categoryId }) + .select('category.name') + .limit(1) + .getRawOne(); + + return { + categoryId, + categoryName: category?.category_name || '', + totalAttempts, + correctAnswers, + accuracy, + }; + } +} diff --git a/backend/src/progress/providers/get-overall-stats.provider.ts b/backend/src/progress/providers/get-overall-stats.provider.ts new file mode 100644 index 0000000..447c726 --- /dev/null +++ b/backend/src/progress/providers/get-overall-stats.provider.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { UserProgress } from '../entities/progress.entity'; +import { OverallStatsDto } from '../dtos/overall-stats.dto'; + +@Injectable() +export class GetOverallStatsProvider { + constructor( + @InjectRepository(UserProgress) + private readonly progressRepo: Repository, + ) {} + + async getOverallStats(userId: string): Promise { + const result = await this.progressRepo + .createQueryBuilder('progress') + .select('COUNT(*)', 'totalAttempts') + .addSelect('SUM(CASE WHEN progress.isCorrect = true THEN 1 ELSE 0 END)', 'totalCorrect') + .addSelect('SUM(progress.pointsEarned)', 'totalPointsEarned') + .addSelect('SUM(progress.timeSpent)', 'totalTimeSpent') + .where('progress.userId = :userId', { userId }) + .getRawOne(); + + if (!result) { + return { + totalAttempts: 0, + totalCorrect: 0, + accuracy: 0, + totalPointsEarned: 0, + totalTimeSpent: 0, + }; + } + + const totalAttempts = parseInt(result.totalAttempts, 10) || 0; + const totalCorrect = parseInt(result.totalCorrect, 10) || 0; + const accuracy = + totalAttempts > 0 + ? Math.round((totalCorrect / totalAttempts) * 100) + : 0; + + return { + totalAttempts, + totalCorrect, + accuracy, + totalPointsEarned: parseInt(result.totalPointsEarned, 10) || 0, + totalTimeSpent: parseInt(result.totalTimeSpent, 10) || 0, + }; + } +} diff --git a/backend/src/progress/providers/get-progress-history.provider.ts b/backend/src/progress/providers/get-progress-history.provider.ts new file mode 100644 index 0000000..46b1043 --- /dev/null +++ b/backend/src/progress/providers/get-progress-history.provider.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { UserProgress } from '../entities/progress.entity'; +import { paginate } from '../../common/pagination/paginate'; +import { ProgressHistoryDto } from '../dtos/progress-history.dto'; + +@Injectable() +export class GetProgressHistoryProvider { + constructor( + @InjectRepository(UserProgress) + private readonly progressRepo: Repository, + ) {} + + async getProgressHistory( + userId: string, + page: number = 1, + limit: number = 10, + ) { + const qb = this.progressRepo + .createQueryBuilder('progress') + .leftJoinAndSelect('progress.puzzle', 'puzzle') + .where('progress.userId = :userId', { userId }) + .orderBy('progress.attemptedAt', 'DESC'); + + return paginate(qb, page, limit); + } +} diff --git a/package-lock.json b/package-lock.json index a04b709..3d7e3b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -155,7 +155,6 @@ "integrity": "sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@xhmikosr/bin-wrapper": "^13.0.5", @@ -202,7 +201,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.24" @@ -241,7 +239,6 @@ "integrity": "sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -350,7 +347,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -394,7 +390,6 @@ "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.26.tgz", "integrity": "sha512-o2RrBNn3lczx1qv4j+JliVMmtkPSqEGpG0UuZkt9tCfWkoXKu8MZnjvp2GjWPll1SehwemQw6xrbVRhmOglj8Q==", "license": "MIT", - "peer": true, "dependencies": { "@sqltools/formatter": "^1.2.5", "ansis": "^3.17.0", @@ -527,7 +522,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -842,7 +836,6 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -3580,7 +3573,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.6.tgz", "integrity": "sha512-krKwLLcFmeuKDqngG2N/RuZHCs2ycsKcxWIDgcm7i1lf3sQ0iG03ci+DsP/r3FcT/eJDFsIHnKtNta2LIi7PzQ==", "license": "MIT", - "peer": true, "dependencies": { "file-type": "21.0.0", "iterare": "1.2.1", @@ -3640,7 +3632,6 @@ "integrity": "sha512-siWX7UDgErisW18VTeJA+x+/tpNZrJewjTBsRPF3JVxuWRuAB1kRoiJcxHgln8Lb5UY9NdvklITR84DUEXD0Cg==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", @@ -3737,7 +3728,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.6.tgz", "integrity": "sha512-HErwPmKnk+loTq8qzu1up+k7FC6Kqa8x6lJ4cDw77KnTxLzsCaPt+jBvOq6UfICmfqcqCCf3dKXg+aObQp+kIQ==", "license": "MIT", - "peer": true, "dependencies": { "cors": "2.8.5", "express": "5.1.0", @@ -4932,7 +4922,6 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -5190,7 +5179,6 @@ "integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -5332,7 +5320,6 @@ "integrity": "sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/types": "8.42.0", @@ -6328,7 +6315,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "devOptional": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6414,7 +6400,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6863,7 +6848,6 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", - "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -7288,7 +7272,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", @@ -7767,15 +7750,13 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/class-validator": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz", "integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==", "license": "MIT", - "peer": true, "dependencies": { "@types/validator": "^13.11.8", "libphonenumber-js": "^1.11.1", @@ -8984,7 +8965,6 @@ "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -9074,7 +9054,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -9176,7 +9155,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -9651,7 +9629,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -11143,7 +11120,6 @@ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.7.0.tgz", "integrity": "sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==", "license": "MIT", - "peer": true, "dependencies": { "@ioredis/commands": "^1.3.0", "cluster-key-slot": "^1.1.0", @@ -11812,7 +11788,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -14690,7 +14665,6 @@ "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", "license": "MIT", - "peer": true, "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1", @@ -14866,7 +14840,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -15193,7 +15166,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -15466,7 +15438,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -15476,7 +15447,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -15496,7 +15466,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -15568,8 +15537,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -15584,8 +15552,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -15922,7 +15889,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -16040,7 +16006,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -17668,7 +17633,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18064,7 +18028,6 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18565,6 +18528,7 @@ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -18579,6 +18543,7 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=4.0" } From a9be904c9b7f60eac4c32031ac07d18467ba301e Mon Sep 17 00:00:00 2001 From: Musa Khalid <112591148+Mkalbani@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:59:07 +0000 Subject: [PATCH 2/2] updated the ProgressCalculationProvider --- backend/.env.example | 6 ++++++ backend/src/progress/progress.module.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/.env.example b/backend/.env.example index 2654878..3e53f67 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -8,6 +8,12 @@ DATABASE_NAME= mindBlock DATABASE_SYNC= true DATABASE_LOAD= true +# JWT Configuration +JWT_SECRET=your-super-secret-jwt-key-change-in-production-min-32-chars +JWT_TOKEN_AUDIENCE=localhost:3000 +JWT_TOKEN_ISSUER=localhost:3000 +JWT_ACCESS_TOKEN_TTL=3600 + # google config GOOGLE_CLIENT_ID= yourGoogleOauthClientId GOOGLE_CLIENT_SECRET= yourGoogleOauthClientSecrete diff --git a/backend/src/progress/progress.module.ts b/backend/src/progress/progress.module.ts index 5b8efa5..1badff3 100644 --- a/backend/src/progress/progress.module.ts +++ b/backend/src/progress/progress.module.ts @@ -2,7 +2,6 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UserProgress } from './entities/progress.entity'; import { User } from '../users/user.entity'; -import { Puzzle } from '../puzzles/entities/puzzle.entity'; import { Streak } from '../streak/entities/streak.entity'; import { DailyQuest } from '../quests/entities/daily-quest.entity'; import { ProgressController } from './controllers/progress.controller'; @@ -11,6 +10,7 @@ import { GetProgressHistoryProvider } from './providers/get-progress-history.pro import { GetCategoryStatsProvider } from './providers/get-category-stats.provider'; import { GetOverallStatsProvider } from './providers/get-overall-stats.provider'; import { ProgressCalculationProvider } from './providers/progress-calculation.provider'; +import { Puzzle } from '../puzzles/entities/puzzle.entity'; @Module({