Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
Shukazuby committed Feb 25, 2025
2 parents 923f783 + 6bcec61 commit 4115315
Show file tree
Hide file tree
Showing 28 changed files with 1,301 additions and 26 deletions.
320 changes: 320 additions & 0 deletions backend/package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/cache-manager": "^3.0.0",
"@nestjs/common": "^11.0.5",
"@nestjs/config": "^4.0.0",
"@nestjs/core": "^11.0.5",
Expand All @@ -33,8 +34,10 @@
"@nestjs/websockets": "^11.0.10",
"@types/bcrypt": "^5.0.2",
"bcrypt": "^5.1.1",
"cache-manager-redis-store": "^3.0.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"ioredis": "^5.5.0",
"joi": "^17.13.3",
"pg": "^8.13.1",
"reflect-metadata": "^0.2.2",
Expand Down
15 changes: 14 additions & 1 deletion backend/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Module } from '@nestjs/common';
import { Module, } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
Expand All @@ -24,7 +24,11 @@ import { TournamentModule } from './tournament/tournament.module';
import { GameGateway } from './websocket-game comms/providers/gamegateway';
import { GameModule } from './websocket-game comms/game.module';
import { AchievementModule } from './achievement/achievement.module';
import { SongGenreModule } from './song-genre/song-genre.module';
import { SocialModule } from './social/social.module';
// import { AchievementModule } from './achievement/achievement.module';
import { CacheModule } from '@nestjs/cache-manager';
import * as redisStore from 'cache-manager-redis-store';
import { Player } from './player/player.entity';
import { GameSession } from './game-session/game-session.entity';
import { ChatRoom } from './chat-room/chat-room.entity';
Expand All @@ -48,13 +52,22 @@ import { ChatRoom } from './chat-room/chat-room.entity';
autoLoadEntities: true,
synchronize: process.env.NODE_ENV === 'development',
}),
CacheModule.register({
store: redisStore,
socket: {
host: process.env.REDIS_HOST || 'localhost',
port: Number(process.env.REDIS_PORT) || 6379,
},
ttl: 3600,
}),
SongsModule,
ChatRoomModule,
ScoringModule,
PowerUpModule,
TournamentModule,
AchievementModule,
SocialModule,
SongGenreModule,
],
controllers: [AppController],
providers: [
Expand Down
9 changes: 9 additions & 0 deletions backend/src/auth/guard/jwt-auth.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';

@Injectable()
export class JwtAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// Static guard: Always allow access (replace with real authentication later)
return true;
}
}
16 changes: 16 additions & 0 deletions backend/src/leaderboard/leaderboard-entry.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity('leaderboard_entries')
export class LeaderboardEntry {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ unique: true })
playerId: string;

@Column()
playerName: string;

@Column({ type: 'int', default: 0 })
score: number;
}
2 changes: 1 addition & 1 deletion backend/src/leaderboard/leaderboard.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ export class LeaderboardController {
@ApiResponse({ status: 200, description: 'Player rank successfully retrieved' })
@ApiResponse({ status: 404, description: 'Player not found' })
getPlayerRank() {
return this.leaderboardService.getPlayerRank();
return this.leaderboardService.getPlayerRank('player123');
}
}
57 changes: 50 additions & 7 deletions backend/src/leaderboard/providers/leaderboard.service.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,58 @@
// import { Injectable } from '@nestjs/common';

// // Service responsible for leaderboard operations.
// @Injectable()
// export class LeaderboardService {
// // Retrieve the global leaderboard.
// getLeaderboard() {
// // Implement get leaderboard logic
// }

// // Retrieve the rank of a specific player.
// getPlayerRank() {
// // Implement get player rank logic
// }
// }


import { Injectable } from '@nestjs/common';
import { RedisService } from 'src/redis/redis.service';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { LeaderboardEntry } from '../leaderboard-entry.entity';

// Service responsible for leaderboard operations.
@Injectable()
export class LeaderboardService {
// Retrieve the global leaderboard.
getLeaderboard() {
// Implement get leaderboard logic
constructor(
@InjectRepository(LeaderboardEntry)
private leaderboardRepository: Repository<LeaderboardEntry>,
private redisService: RedisService
) {}

// Helper method for caching
private async getCachedData<T>(key: string, fetchFunction: () => Promise<T>, ttl = 3600): Promise<T> {
const cachedData = await this.redisService.get(key);
if (cachedData) return JSON.parse(cachedData);

const freshData = await fetchFunction();
await this.redisService.set(key, JSON.stringify(freshData), ttl);

return freshData;
}

// Retrieve the global leaderboard
async getLeaderboard() {
return this.getCachedData('leaderboard:global', () =>
this.leaderboardRepository.find({ order: { score: 'DESC' }, take: 100 })
);
}

// Retrieve the rank of a specific player.
getPlayerRank() {
// Implement get player rank logic
// Retrieve the rank of a specific player
async getPlayerRank(playerId: string) {
return this.getCachedData(`leaderboard:rank:${playerId}`, async () => {
const leaderboard = await this.getLeaderboard(); // Fetch cached leaderboard
const rank = leaderboard.findIndex(entry => entry.playerId === playerId) + 1;
return rank > 0 ? { playerId, rank } : null;
});
}
}
9 changes: 9 additions & 0 deletions backend/src/redis/redis.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Module, Global } from '@nestjs/common';
import { RedisService } from './redis.service';

@Global()
@Module({
providers: [RedisService],
exports: [RedisService],
})
export class RedisModule {}
18 changes: 18 additions & 0 deletions backend/src/redis/redis.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { RedisService } from './redis.service';

describe('RedisService', () => {
let service: RedisService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [RedisService],
}).compile();

service = module.get<RedisService>(RedisService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
31 changes: 31 additions & 0 deletions backend/src/redis/redis.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Injectable } from '@nestjs/common';
import { Redis } from 'ioredis';

@Injectable()
export class RedisService {
private client: Redis;

constructor() {
this.client = new Redis({
host: '127.0.0.1',
port: 6379,
});
}

async get(key: string): Promise<string | null> {
return this.client.get(key);
}

async set(key: string, value: string, ttl?: number): Promise<void> {
if (ttl) {
await this.client.set(key, value, 'EX', ttl);
} else {
await this.client.set(key, value);
}
}

async del(key: string): Promise<void> {
await this.client.del(key);
}
}

30 changes: 30 additions & 0 deletions backend/src/song-genre/controllers/genre.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Controller, Get, Post, Body, Param, UseGuards } from '@nestjs/common';
import { GenreService } from '../services/genre.service';
import { JwtAuthGuard } from '../../auth/guard/jwt-auth.guard';
import { CurrentUser } from '../../auth/decorators/current-user.decorator';

@Controller('genres')
export class GenreController {
constructor(private readonly genreService: GenreService) {}

@Get()
findAll() {
return this.genreService.findAll();
}

@Post(':genreId/preferences')
@UseGuards(JwtAuthGuard)
updatePreference(
@CurrentUser() userId: string,
@Param('genreId') genreId: string,
@Body() performanceData: any,
) {
return this.genreService.updateUserPreference(userId, genreId, performanceData);
}

@Get('preferences')
@UseGuards(JwtAuthGuard)
getUserPreferences(@CurrentUser() userId: string) {
return this.genreService.getUserGenrePreferences(userId);
}
}
20 changes: 20 additions & 0 deletions backend/src/song-genre/controllers/song.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SongGenreController } from './song.controller';
import { SongGenreService } from '../services/song.service';

describe('SongGenreController', () => {
let controller: SongGenreController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [SongGenreController],
providers: [SongGenreService],
}).compile();

controller = module.get<SongGenreController>(SongGenreController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
26 changes: 26 additions & 0 deletions backend/src/song-genre/controllers/song.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Controller, Post, Body, Get, Param, Patch, UseGuards } from '@nestjs/common';
import { SongService } from '../services/song.service';
import { CreateSongDto } from '../dtos/create-song.dto';
import { JwtAuthGuard } from '../../auth/guard/jwt-auth.guard';

@Controller('songs')
export class SongController {
constructor(private readonly songService: SongService) {}

@Post()
@UseGuards(JwtAuthGuard)
create(@Body() createSongDto: CreateSongDto) {
return this.songService.create(createSongDto);
}

@Get('genre/:genreId')
findByGenre(@Param('genreId') genreId: string) {
return this.songService.findByGenre(genreId);
}

@Patch(':id/difficulty')
@UseGuards(JwtAuthGuard)
updateDifficulty(@Param('id') id: string, @Body() performanceData: any[]) {
return this.songService.updateDifficulty(id, performanceData);
}
}
26 changes: 26 additions & 0 deletions backend/src/song-genre/dtos/create-song.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { IsString, IsNumber, IsObject, IsUUID, IsArray, IsOptional } from 'class-validator';

export class CreateSongDto {
@IsString()
title: string;

@IsString()
artist: string;

@IsNumber()
durationSeconds: number;

@IsNumber()
baseDifficulty: number;

@IsObject()
difficultyFactors: Record<string, number>;

@IsUUID()
genreId: string;

@IsOptional()
@IsArray()
@IsString({ each: true })
tagIds?: string[];
}
4 changes: 4 additions & 0 deletions backend/src/song-genre/dtos/update-song-genre.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { CreateSongGenreDto } from './create-song.dto';

export class UpdateSongGenreDto extends PartialType(CreateSongGenreDto) {}
29 changes: 29 additions & 0 deletions backend/src/song-genre/entities/genre-challenge.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, CreateDateColumn } from 'typeorm';
import { Genre } from './genre.entity';

@Entity()
export class GenreChallenge {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
title: string;

@ManyToOne(() => Genre)
genre: Genre;

@Column('text')
description: string;

@Column('json')
requirements: Record<string, any>;

@Column('int')
experienceReward: number;

@Column()
expiresAt: Date;

@CreateDateColumn()
createdAt: Date;
}
29 changes: 29 additions & 0 deletions backend/src/song-genre/entities/genre.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Entity, Column, PrimaryGeneratedColumn, OneToMany, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { Song } from './song.entity';

@Entity()
export class Genre {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ unique: true })
name: string;

@Column('text')
description: string;

@Column()
icon: string;

@Column('float', { default: 1.0 })
difficultyMultiplier: number;

@OneToMany(() => Song, song => song.genre)
songs: Song[];

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;
}
Loading

0 comments on commit 4115315

Please sign in to comment.