-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #285 from LaGodxy/Create-Music-Genre-Management-Sy…
…stem-#252 Created Music Genre Management System Close #252
- Loading branch information
Showing
18 changed files
with
765 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
20
backend/src/song-genre/controllers/song.controller.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, ManyToMany, JoinTable, CreateDateColumn, UpdateDateColumn } from 'typeorm'; | ||
import { Genre } from './genre.entity'; | ||
import { Tag } from './tag.entity'; | ||
|
||
@Entity() | ||
export class Song { | ||
@PrimaryGeneratedColumn('uuid') | ||
id: string; | ||
|
||
@Column() | ||
title: string; | ||
|
||
@Column() | ||
artist: string; | ||
|
||
@Column('int') | ||
durationSeconds: number; | ||
|
||
@Column('float') | ||
baseDifficulty: number; | ||
|
||
@Column('float', { nullable: true }) | ||
calculatedDifficulty: number; | ||
|
||
@Column('json') | ||
difficultyFactors: Record<string, number>; | ||
|
||
@ManyToOne(() => Genre, genre => genre.songs) | ||
genre: Genre; | ||
|
||
@ManyToMany(() => Tag) | ||
@JoinTable() | ||
tags: Tag[]; | ||
|
||
@Column('int', { default: 0 }) | ||
playCount: number; | ||
|
||
@Column('float', { default: 0 }) | ||
averageScore: number; | ||
|
||
@Column('float', { default: 0 }) | ||
completionRate: number; | ||
|
||
@CreateDateColumn() | ||
createdAt: Date; | ||
|
||
@UpdateDateColumn() | ||
updatedAt: Date; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; | ||
|
||
@Entity() | ||
export class Tag { | ||
@PrimaryGeneratedColumn('uuid') | ||
id: string; | ||
|
||
@Column({ unique: true }) | ||
name: string; | ||
|
||
@Column() | ||
category: string; | ||
} |
29 changes: 29 additions & 0 deletions
29
backend/src/song-genre/entities/user-genre-preference.entity.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, CreateDateColumn, UpdateDateColumn } from 'typeorm'; | ||
import { Genre } from './genre.entity'; | ||
|
||
@Entity() | ||
export class UserGenrePreference { | ||
@PrimaryGeneratedColumn('uuid') | ||
id: string; | ||
|
||
@Column() | ||
userId: string; | ||
|
||
@ManyToOne(() => Genre) | ||
genre: Genre; | ||
|
||
@Column('float', { default: 0 }) | ||
preferenceScore: number; | ||
|
||
@Column('int', { default: 0 }) | ||
playCount: number; | ||
|
||
@Column('float', { default: 0 }) | ||
averagePerformance: number; | ||
|
||
@CreateDateColumn() | ||
createdAt: Date; | ||
|
||
@UpdateDateColumn() | ||
updatedAt: Date; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { InjectRepository } from '@nestjs/typeorm'; | ||
import { Repository } from 'typeorm'; | ||
import { Genre } from '../entities/genre.entity'; | ||
import { UserGenrePreference } from '../entities/user-genre-preference.entity'; | ||
|
||
@Injectable() | ||
export class GenreService { | ||
constructor( | ||
@InjectRepository(Genre) | ||
private genreRepository: Repository<Genre>, | ||
@InjectRepository(UserGenrePreference) | ||
private userPreferenceRepository: Repository<UserGenrePreference>, | ||
) {} | ||
|
||
async findAll(): Promise<Genre[]> { | ||
return this.genreRepository.find(); | ||
} | ||
|
||
async updateUserPreference(userId: string, genreId: string, performanceData: any): Promise<UserGenrePreference> { | ||
let preference = await this.userPreferenceRepository.findOne({ | ||
where: { userId, genre: { id: genreId } }, | ||
relations: ['genre'], | ||
}); | ||
|
||
if (!preference) { | ||
const genre = await this.genreRepository.findOne({ where: { id: genreId } }); | ||
preference = this.userPreferenceRepository.create({ | ||
userId, | ||
genre, | ||
preferenceScore: 0, | ||
playCount: 0, | ||
averagePerformance: 0, | ||
}); | ||
} | ||
|
||
// Update stats | ||
preference.playCount += 1; | ||
|
||
// Update average performance | ||
const totalPerformance = preference.averagePerformance * (preference.playCount - 1) + performanceData.score; | ||
preference.averagePerformance = totalPerformance / preference.playCount; | ||
|
||
// Update preference score (based on play count and performance) | ||
preference.preferenceScore = | ||
(preference.playCount * 0.1) + | ||
(preference.averagePerformance * 0.01) + | ||
(performanceData.enjoymentRating || 0); | ||
|
||
return this.userPreferenceRepository.save(preference); | ||
} | ||
|
||
async getUserGenrePreferences(userId: string): Promise<UserGenrePreference[]> { | ||
return this.userPreferenceRepository.find({ | ||
where: { userId }, | ||
relations: ['genre'], | ||
order: { preferenceScore: 'DESC' }, | ||
}); | ||
} | ||
} |
Oops, something went wrong.