Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/ab-testing/ab-testing.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ABTestingService } from './ab-testing.service';
import { Experiment } from './entities/experiment.entity';
import { Experiment, ExperimentType } from './entities/experiment.entity';
import { IExperimentVariant } from './entities/experiment-variant.entity';
import { ExperimentType } from './entities/experiment.entity';

describe('ABTestingService', () => {
const makeDto = (variantCount = 2) => ({
Expand Down
13 changes: 9 additions & 4 deletions src/achievements/achievements.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
AchievementOverviewDto,
} from './dto/achievement-statistics.dto';
import { AchievementType } from './entities/achievement.entity';
import { OffsetPaginatedResponse } from '../common/interfaces/pagination.interface';

/**
* Achievements Controller
Expand Down Expand Up @@ -71,7 +72,7 @@ export class AchievementsController {
async getAllAchievements(
@Req() req: any,
@Query('includeHidden') includeHidden?: string,
): Promise<AchievementResponseDto[]> {
): Promise<OffsetPaginatedResponse<AchievementResponseDto>> {
const isAdmin = req.user?.role === 'admin';
const allowHidden = isAdmin && includeHidden === 'true';
return this.achievementsService.getAllAchievements(allowHidden);
Expand All @@ -84,7 +85,7 @@ export class AchievementsController {
@Get('type/:type')
async getAchievementsByType(
@Param('type') type: AchievementType,
): Promise<AchievementResponseDto[]> {
): Promise<OffsetPaginatedResponse<AchievementResponseDto>> {
return this.achievementsService.getAchievementsByType(type);
}

Expand Down Expand Up @@ -189,7 +190,9 @@ export class AchievementsController {
* GET /achievements/progress/:userId
*/
@Get('progress/:userId')
async getUserAllProgress(@Param('userId') userId: string): Promise<AchievementProgressDto[]> {
async getUserAllProgress(
@Param('userId') userId: string,
): Promise<OffsetPaginatedResponse<AchievementProgressDto>> {
return this.achievementsService.getUserAllProgress(userId);
}

Expand All @@ -216,7 +219,9 @@ export class AchievementsController {
* GET /achievements/user/:userId/unlocked
*/
@Get('user/:userId/unlocked')
async getUserAchievements(@Param('userId') userId: string): Promise<UserAchievementDto[]> {
async getUserAchievements(
@Param('userId') userId: string,
): Promise<OffsetPaginatedResponse<UserAchievementDto>> {
return this.achievementsService.getUserAchievements(userId);
}

Expand Down
14 changes: 7 additions & 7 deletions src/achievements/achievements.integration.example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class AchievementsIntegrationExample {
async awardAchievementManually(userId: string, achievementName: string): Promise<void> {
// 1. Find achievement by name
const achievements = await this.achievementsService.getAllAchievements();
const achievement = achievements.find((a) => a.name === achievementName);
const achievement = achievements.data.find((a) => a.name === achievementName);

if (!achievement) {
// console.error(`Achievement not found: ${achievementName}`);
Expand Down Expand Up @@ -118,12 +118,12 @@ export class AchievementsIntegrationExample {

return {
summary: overview,
progress: userProgress.map((p) => ({
progress: userProgress.data.map((p) => ({
achievement: p.achievement.name,
progress: `${p.currentProgress}/${p.targetProgress}`,
percentage: p.percentageComplete,
})),
unlocked: userUnlocked.map((a) => ({
unlocked: userUnlocked.data.map((a) => ({
achievement: a.achievement.name,
unlockedAt: a.unlockedAt,
pointsEarned: a.pointsEarned,
Expand All @@ -140,7 +140,7 @@ export class AchievementsIntegrationExample {
const leaderboard = await this.achievementsService.getAchievementsLeaderboard(10);

const statsByAchievement = await Promise.all(
achievements.map(async (achievement) => {
achievements.data.map(async (achievement) => {
const stats = await this.achievementsService.getAchievementStatistics(achievement.id);
return {
name: achievement.name,
Expand All @@ -155,7 +155,7 @@ export class AchievementsIntegrationExample {
return {
topAchievements: statsByAchievement.sort((a, b) => b.totalUnlocked - a.totalUnlocked),
topUsers: leaderboard,
totalAchievements: achievements.length,
totalAchievements: achievements.total,
};
}

Expand Down Expand Up @@ -195,7 +195,7 @@ export class AchievementsIntegrationExample {
achievementName: string,
): Promise<boolean> {
const achievements = await this.achievementsService.getAllAchievements();
const achievement = achievements.find((a) => a.name === achievementName);
const achievement = achievements.data.find((a) => a.name === achievementName);

if (!achievement) {
return false;
Expand All @@ -210,7 +210,7 @@ export class AchievementsIntegrationExample {
*/
async bulkUnlockAchievementsForUser(userId: string, count: number): Promise<void> {
const achievements = await this.achievementsService.getAllAchievements();
const achievementsToUnlock = achievements.slice(0, count).map((a) => a.id);
const achievementsToUnlock = achievements.data.slice(0, count).map((a) => a.id);

await this.achievementsService.batchUnlockAchievements(userId, achievementsToUnlock);

Expand Down
14 changes: 0 additions & 14 deletions src/achievements/achievements.seed.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AchievementType, AchievementDifficulty } from './entities/achievement.entity';
import { Logger } from '@nestjs/common';

Check warning on line 2 in src/achievements/achievements.seed.ts

View workflow job for this annotation

GitHub Actions / validate

'Logger' is defined but never used. Allowed unused vars must match /^_/u

/**
* Seed data for default achievements
Expand Down Expand Up @@ -319,17 +319,3 @@
console.error('❌ Error seeding achievements:', error);
}
}

export async function seedAchievements(): Promise<void> {
const logger = new Logger('AchievementsSeed');

logger.log('Starting achievements database seed process...');

try {
// Seed logic execution
logger.log('Successfully seeded default achievements.');
} catch (error) {
logger.error('Failed to seed achievements', error instanceof Error ? error.stack : error);
throw error;
}
}
15 changes: 6 additions & 9 deletions src/achievements/achievements.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { Injectable, Logger, NotFoundException, Inject } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, MoreThan } from 'typeorm';
import { Achievement, AchievementType } from './entities/achievement.entity';
Expand All @@ -21,12 +21,11 @@ import {
AchievementLeaderboardDto,
AchievementOverviewDto,
} from './dto/achievement-statistics.dto';
import { PaginationQueryDto } from '../../common/dto/pagination.dto';
import { OffsetPaginatedResponse } from '../../common/interfaces/pagination.interface';
import { buildOffsetResponse } from '../../common/utils/pagination.utils';
import { PaginationQueryDto } from '../common/dto/pagination.dto';
import { OffsetPaginatedResponse } from '../common/interfaces/pagination.interface';
import { buildOffsetResponse } from '../common/utils/pagination.utils';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { Inject } from '@nestjs/common';

const ACHIEVEMENTS_CACHE_KEY = 'achievements_definitions';

Expand Down Expand Up @@ -533,7 +532,7 @@ export class AchievementsService {
.addSelect('COALESCE(SUM(ua.experienceEarned), 0)', 'totalExperience')
.where('ua.userId = :userId', { userId })
.getRawOne();

const unlockedCount = parseInt(userStats?.unlockedCount || '0', 10);
const totalPoints = parseInt(userStats?.totalPoints || '0', 10);
const totalExperience = parseInt(userStats?.totalExperience || '0', 10);
Expand All @@ -547,9 +546,7 @@ export class AchievementsService {
})
.getRawOne();
const progressPercentage =
totalAchievements > 0
? Math.round((unlockedCount / totalAchievements) * 100)
: 0;
totalAchievements > 0 ? Math.round((unlockedCount / totalAchievements) * 100) : 0;

return {
totalAchievements,
Expand Down
4 changes: 3 additions & 1 deletion src/assessment/assessments.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@ describe('AssessmentsService', () => {
});

it('computes pagination metadata correctly', async () => {
const items = Array(25).fill(null).map((_, i) => makeAssessment({ id: `assess-${i}`, title: `A${i}` }));
const items = Array(25)
.fill(null)
.map((_, i) => makeAssessment({ id: `assess-${i}`, title: `A${i}` }));
assessmentRepo.findAndCount.mockResolvedValue([items.slice(0, 10), 25]);

const page1 = await service.findAll(1, 10);
Expand Down
6 changes: 1 addition & 5 deletions src/assessment/grading/rubrics.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,7 @@ export class RubricsService {
}

/** Lists rubrics (paginated), optionally filtering by owner. */
async findAll(
ownerId?: string,
page = 1,
limit = 10,
): Promise<OffsetPaginatedResponse<Rubric>> {
async findAll(ownerId?: string, page = 1, limit = 10): Promise<OffsetPaginatedResponse<Rubric>> {
const clampedLimit = clampLimit(limit);
const skip = (page - 1) * clampedLimit;
const [data, total] = await this.rubricRepo.findAndCount({
Expand Down
36 changes: 19 additions & 17 deletions src/assessment/questions/question-bank.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,24 @@ export class QuestionBankService {
): Promise<OffsetPaginatedResponse<Question>> {
const clampedLimit = clampLimit(limit);
const skip = (page - 1) * clampedLimit;
return this.questionRepo.findAndCount({
where: { assessment: { id: assessmentId } },
order: { createdAt: 'DESC' },
skip,
take: clampedLimit,
}).then(([data, total]) => {
const totalPages = Math.ceil(total / clampedLimit);
return {
data,
total,
page,
limit: clampedLimit,
totalPages,
hasNextPage: page < totalPages,
hasPrevPage: page > 1,
};
});
return this.questionRepo
.findAndCount({
where: { assessment: { id: assessmentId } },
order: { createdAt: 'DESC' },
skip,
take: clampedLimit,
})
.then(([data, total]) => {
const totalPages = Math.ceil(total / clampedLimit);
return {
data,
total,
page,
limit: clampedLimit,
totalPages,
hasNextPage: page < totalPages,
hasPrevPage: page > 1,
};
});
}
}
Loading
Loading