Skip to content

Commit

Permalink
implemented redisdb
Browse files Browse the repository at this point in the history
  • Loading branch information
feyishola committed Feb 24, 2025
1 parent 4b59cbf commit 74cd121
Show file tree
Hide file tree
Showing 11 changed files with 536 additions and 21 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
14 changes: 12 additions & 2 deletions 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 @@ -25,7 +25,9 @@ import { GameGateway } from './websocket-game comms/providers/gamegateway';
import { GameModule } from './websocket-game comms/game.module';
import { AchievementModule } from './achievement/achievement.module';
import { SocialModule } from './social/social.module';
import { AchievementModule } from './achievement/achievement.module';
// import { AchievementModule } from './achievement/achievement.module';
import { CacheModule } from '@nestjs/cache-manager';
import * as redisStore from 'cache-manager-redis-store';

@Module({
imports: [
Expand All @@ -45,6 +47,14 @@ import { AchievementModule } from './achievement/achievement.module';
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,
Expand Down
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);
}
}

3 changes: 2 additions & 1 deletion backend/src/songs/songs.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { SongsService } from './songs.service';
import { SongsController } from './songs.controller';
import { Song } from './entities/song.entity';
import { RedisService } from 'src/redis/redis.service';

@Module({
imports: [TypeOrmModule.forFeature([Song])],
imports: [TypeOrmModule.forFeature([Song]), RedisService],
controllers: [SongsController],
providers: [SongsService],
exports: [SongsService],
Expand Down
84 changes: 74 additions & 10 deletions backend/src/songs/songs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,123 @@ import { Repository } from 'typeorm';
import { CreateSongDto } from './dto/create-song.dto';
import { UpdateSongDto } from './dto/update-song.dto';
import { Song } from './entities/song.entity';
import { RedisService } from 'src/redis/redis.service';

@Injectable()
export class SongsService {
constructor(
@InjectRepository(Song)
private songsRepository: Repository<Song>,
// injecting the redis sercvice
private redisService: RedisService
) {}

create(createSongDto: CreateSongDto) {
async create(createSongDto: CreateSongDto) {
const song = this.songsRepository.create(createSongDto);
return this.songsRepository.save(song);
const savedSong = await this.songsRepository.save(song);
await this.redisService.del('songs:all');
return savedSong;
}

findAll() {
return this.songsRepository.find();

async findAll() {
const cacheKey = 'songs:all';

const cachedSongs = await this.redisService.get(cacheKey);
if (cachedSongs) {
return JSON.parse(cachedSongs);
}

const songs = await this.songsRepository.find();

await this.redisService.set(cacheKey, JSON.stringify(songs), 3600);

return songs;
}

async findOne(id: string) {
const cacheKey = `song:${id}`;

const cachedSong = await this.redisService.get(cacheKey);
if (cachedSong) {
return JSON.parse(cachedSong);
}

const song = await this.songsRepository.findOne({ where: { id } });
if (!song) {
throw new NotFoundException(`Song with ID ${id} not found`);
}

await this.redisService.set(cacheKey, JSON.stringify(song), 3600);

return song;
}

async update(id: string, updateSongDto: UpdateSongDto) {
const song = await this.findOne(id);
Object.assign(song, updateSongDto);
return this.songsRepository.save(song);
const updatedSong = await this.songsRepository.save(song);

// Invalidate caches
await this.redisService.del(`song:${id}`);
await this.redisService.del('songs:all');

return updatedSong;
}

async remove(id: string) {
const song = await this.findOne(id);
return this.songsRepository.remove(song);
await this.songsRepository.remove(song);

await this.redisService.del(`song:${id}`);
await this.redisService.del('songs:all');

return { message: `Song with ID ${id} deleted successfully` };
}

async findByGenre(genre: string) {
return this.songsRepository.find({ where: { genre } });
const cacheKey = `songs:genre:${genre}`;

const cachedSongs = await this.redisService.get(cacheKey);
if (cachedSongs) {
return JSON.parse(cachedSongs);
}

const songs = await this.songsRepository.find({ where: { genre } });

await this.redisService.set(cacheKey, JSON.stringify(songs), 3600);

return songs;
}

async searchSongs(query: string) {
return this.songsRepository
const cacheKey = `songs:search:${query}`;

const cachedResults = await this.redisService.get(cacheKey);
if (cachedResults) {
return JSON.parse(cachedResults);
}

const songs = await this.songsRepository
.createQueryBuilder('song')
.where('song.title ILIKE :query OR song.artist ILIKE :query', {
query: `%${query}%`,
})
.getMany();

await this.redisService.set(cacheKey, JSON.stringify(songs), 1800);

return songs;
}

async updatePlayCount(id: string) {
const song = await this.findOne(id);
song.playCount += 1;
return this.songsRepository.save(song);
const updatedSong = await this.songsRepository.save(song);

await this.redisService.set(`song:${id}`, JSON.stringify(updatedSong), 3600);

return updatedSong;
}

async findByDifficulty(level: number) {
Expand All @@ -70,4 +134,4 @@ export class SongsService {
.take(1)
.getOne();
}
}
}

0 comments on commit 74cd121

Please sign in to comment.