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
55 changes: 37 additions & 18 deletions src/achievement/achievement.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,45 @@ import { Controller, Get, Param } from '@nestjs/common';
import { Repository } from 'typeorm';
import { UserAchievement } from './entities/user-achievement.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger';
import { Achievement } from './entities/achievement.entity';
import { AchievementService } from './providers/achievement.service';

@Controller('achievements')
export class AchievementController {
constructor(
@InjectRepository(UserAchievement)
private readonly userAchievementRepo: Repository<UserAchievement>
) {}
@Get('/users/:id/achievements')
public async getUserAchievements(@Param('id') userId: string) {
const unlocked = await this.userAchievementRepo.find({
where: { user: { id: userId } },
relations: ['achievement'],
});
constructor(
@InjectRepository(UserAchievement)
private readonly userAchievementRepo: Repository<UserAchievement>,

return unlocked.map((ua) => ({
id: ua.achievement.id,
title: ua.achievement.title,
description: ua.achievement.description,
iconUrl: ua.achievement.iconUrl,
unlockedAt: ua.unlockedAt,
}));
}
private readonly achievementsService: AchievementService
) {}
@Get('/users/:id/achievements')
public async getUserAchievements(@Param('id') userId: string) {
const unlocked = await this.userAchievementRepo.find({
where: { user: { id: userId } },
relations: ['achievement'],
});

return unlocked.map((ua) => ({
id: ua.achievement.id,
title: ua.achievement.title,
description: ua.achievement.description,
iconUrl: ua.achievement.iconUrl,
unlockedAt: ua.unlockedAt,
}));
}

@Get()
@ApiOperation({ summary: 'Get unlocked achievements for a user' })
@ApiParam({ name: 'id', description: 'User ID' })
@ApiResponse({
status: 200,
description: 'List of achievements',
type: [Achievement],
})
async getAchievements(
@Param('id') userId: string,
): Promise<Partial<Achievement>[]> {
return this.achievementsService.findByID(userId);
}
}
3 changes: 2 additions & 1 deletion src/achievement/achievement.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import { Achievement } from './entities/achievement.entity';
import { LeaderboardEntry } from 'src/leaderboard/entities/leaderboard.entity';
import { Badge } from 'src/badge/entities/badge.entity';
import { User } from 'src/users/user.entity';
import { FindByUserIdProvider } from './providers/find-by-user-id-provider';

@Module({
imports: [TypeOrmModule.forFeature([Achievement, UserAchievement, LeaderboardEntry, Badge, User])],
controllers: [AchievementController],
providers: [AchievementService, AchievementUnlockerProvider],
providers: [AchievementService, AchievementUnlockerProvider, FindByUserIdProvider],
exports: [AchievementService]
})
export class AchievementModule {}
14 changes: 12 additions & 2 deletions src/achievement/entities/achievement.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from "typeorm";
import { User } from 'src/users/user.entity';
import {
Column,
CreateDateColumn,
Entity,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';

@Entity('achievements')
export class Achievement {
Expand All @@ -8,6 +15,9 @@ export class Achievement {
@Column()
title: string;

@ManyToOne(() => User, { eager: true })
user: User;

@Column()
description: string;

Expand All @@ -16,4 +26,4 @@ export class Achievement {

@CreateDateColumn()
createdAt: Date;
}
}
10 changes: 8 additions & 2 deletions src/achievement/providers/achievement.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { Injectable } from '@nestjs/common';
import { AchievementUnlockerProvider } from './achievement-unlocker.service';
import { User } from 'src/users/user.entity';
import { FindByUserIdProvider } from './find-by-user-id-provider';

@Injectable()
export class AchievementService {
constructor(
private readonly achievementUnlockerProvider: AchievementUnlockerProvider
private readonly achievementUnlockerProvider: AchievementUnlockerProvider,
private readonly fndByUserIdProvider: FindByUserIdProvider,
) {}

public async achievementUnlocker(user: User) {
return this,this.achievementUnlockerProvider.unlockAchievementsForUser(user)
return this.achievementUnlockerProvider.unlockAchievementsForUser(user)
}

public async findByID(userId: string) {
return this.fndByUserIdProvider.findByUserId(userId)
}
}
21 changes: 21 additions & 0 deletions src/achievement/providers/find-by-user-id-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Achievement } from '../entities/achievement.entity';
import { Repository } from 'typeorm';

@Injectable()
export class FindByUserIdProvider {
constructor(
@InjectRepository(Achievement)
private readonly achievementRepo: Repository<Achievement>,
) {}

async findByUserId(userId: string) {
return this.achievementRepo.find({
where: { user: { id: userId } },
relations: ['user'],
select: ['id', 'title', 'iconUrl', 'createdAt'],
order: { createdAt: 'DESC' },
});
}
}
63 changes: 63 additions & 0 deletions src/achievement/providers/find-by-user-idn-provider.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Achievement } from '../entities/achievement.entity';
import { Repository } from 'typeorm';
import { FindByUserIdProvider } from './find-by-user-id-provider';

const mockAchievements = [
{
id: '1',
title: 'First Achievement',
iconUrl: 'http://example.com/icon1.svg',
createdAt: new Date('2024-01-01T00:00:00.000Z'),
user: { id: 'user123' },
},
{
id: '2',
title: 'Second Achievement',
iconUrl: 'http://example.com/icon2.svg',
createdAt: new Date('2024-02-01T00:00:00.000Z'),
user: { id: 'user123' },
},
];

describe('FindByUserIdProvider', () => {
let provider: FindByUserIdProvider;
let repo: Repository<Achievement>;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
FindByUserIdProvider,
{
provide: getRepositoryToken(Achievement),
useValue: {
find: jest.fn().mockResolvedValue(mockAchievements),
},
},
],
}).compile();

provider = module.get<FindByUserIdProvider>(FindByUserIdProvider);
repo = module.get<Repository<Achievement>>(getRepositoryToken(Achievement));
});

it('should be defined', () => {
expect(provider).toBeDefined();
});

describe('findByUserId', () => {
it('should return a list of achievements for the given user ID', async () => {
const result = await provider.findByUserId('user123');

expect(repo.find).toHaveBeenCalledWith({
where: { user: { id: 'user123' } },
relations: ['user'],
select: ['id', 'title', 'iconUrl', 'createdAt'],
order: { createdAt: 'DESC' },
});

expect(result).toEqual(mockAchievements);
});
});
});
Loading