Skip to content

Commit

Permalink
Merge pull request #200 from addegbenga/feat/lyrics-crud
Browse files Browse the repository at this point in the history
Feat/lyrics crud
  • Loading branch information
Xaxxoo authored Feb 18, 2025
2 parents 773f14e + e016bc3 commit 5cc8280
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 4 deletions.
7 changes: 3 additions & 4 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { AccessTokenGuard } from './auth/guard/access-token/access-token.guard';
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { ConfigModule } from './config/config.module';
import { GlobalInterceptor } from './interceptors/global.interceptor';
import { SongsModule } from './songs/songs.module';
import { ScoringModule } from './scoring/scoring.module';
import { ChatRoomModule } from './chat-room/chat-room.module';

Expand All @@ -35,12 +36,10 @@ import { ChatRoomModule } from './chat-room/chat-room.module';
autoLoadEntities: true,
synchronize: process.env.NODE_ENV === 'development',
}),
<<<<<<< HEAD

=======
SongsModule,
ChatRoomModule,
ScoringModule,
>>>>>>> c658da9e73156dd12a469e16aa11262be3f80820

],
controllers: [AppController],
providers: [
Expand Down
45 changes: 45 additions & 0 deletions backend/src/songs/dto/create-song.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { IsNotEmpty, IsString, IsNumber, IsArray, Min, Max, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

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

@ApiProperty()
@IsNotEmpty()
@IsString()
artist: string;

@ApiProperty()
@IsNotEmpty()
@IsString()
lyrics: string;

@ApiProperty()
@IsNotEmpty()
@IsString()
genre: string;

@ApiProperty()
@IsNumber()
@Min(1)
@Max(10)
difficulty: number;

@ApiProperty()
@IsNumber()
@Min(1900)
releaseYear: number;

@ApiProperty()
@IsString()
language: string;

@ApiProperty()
@IsArray()
@IsOptional()
tags: string[];
}

4 changes: 4 additions & 0 deletions backend/src/songs/dto/update-song.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { CreateSongDto } from './create-song.dto';

export class UpdateSongDto extends PartialType(CreateSongDto) {}
40 changes: 40 additions & 0 deletions backend/src/songs/entities/song.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';

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

@Column()
title: string;

@Column()
artist: string;

@Column('text')
lyrics: string;

@Column()
genre: string;

@Column()
difficulty: number;

@Column({ default: 0 })
playCount: number;

@Column('simple-array', { nullable: true })
tags: string[];

@Column()
releaseYear: number;

@Column()
language: string;

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;
}
20 changes: 20 additions & 0 deletions backend/src/songs/songs.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SongsController } from './songs.controller';
import { SongsService } from './songs.service';

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

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

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

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
66 changes: 66 additions & 0 deletions backend/src/songs/songs.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// src/songs/songs.controller.ts
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common';
import { SongsService } from './songs.service';
import { CreateSongDto } from './dto/create-song.dto';
import { UpdateSongDto } from './dto/update-song.dto';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';

@ApiTags('songs')
@Controller('songs')
export class SongsController {
constructor(private readonly songsService: SongsService) {}

@Post()
@ApiOperation({ summary: 'Create a new song' })
create(@Body() createSongDto: CreateSongDto) {
return this.songsService.create(createSongDto);
}

@Get()
@ApiOperation({ summary: 'Get all songs' })
findAll() {
return this.songsService.findAll();
}

@Get('search')
@ApiOperation({ summary: 'Search songs' })
search(@Query('q') query: string) {
return this.songsService.searchSongs(query);
}

@Get('genre/:genre')
@ApiOperation({ summary: 'Get songs by genre' })
findByGenre(@Param('genre') genre: string) {
return this.songsService.findByGenre(genre);
}

@Get('random')
@ApiOperation({ summary: 'Get a random song' })
getRandomSong() {
return this.songsService.getRandomSong();
}

@Get(':id')
@ApiOperation({ summary: 'Get a song by id' })
findOne(@Param('id') id: string) {
return this.songsService.findOne(id);
}

@Patch(':id')
@ApiOperation({ summary: 'Update a song' })
update(@Param('id') id: string, @Body() updateSongDto: UpdateSongDto) {
return this.songsService.update(id, updateSongDto);
}

@Delete(':id')
@ApiOperation({ summary: 'Delete a song' })
remove(@Param('id') id: string) {
return this.songsService.remove(id);
}

@Post(':id/play')
@ApiOperation({ summary: 'Increment play count' })
incrementPlayCount(@Param('id') id: string) {
return this.songsService.updatePlayCount(id);
}
}
14 changes: 14 additions & 0 deletions backend/src/songs/songs.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// src/songs/songs.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { SongsService } from './songs.service';
import { SongsController } from './songs.controller';
import { Song } from './entities/song.entity';

@Module({
imports: [TypeOrmModule.forFeature([Song])],
controllers: [SongsController],
providers: [SongsService],
exports: [SongsService],
})
export class SongsModule {}
18 changes: 18 additions & 0 deletions backend/src/songs/songs.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SongsService } from './songs.service';

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

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

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

it('should be defined', () => {
expect(service).toBeDefined();
});
});
73 changes: 73 additions & 0 deletions backend/src/songs/songs.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateSongDto } from './dto/create-song.dto';
import { UpdateSongDto } from './dto/update-song.dto';
import { Song } from './entities/song.entity';

@Injectable()
export class SongsService {
constructor(
@InjectRepository(Song)
private songsRepository: Repository<Song>,
) {}

create(createSongDto: CreateSongDto) {
const song = this.songsRepository.create(createSongDto);
return this.songsRepository.save(song);
}

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

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

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

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

async findByGenre(genre: string) {
return this.songsRepository.find({ where: { genre } });
}

async searchSongs(query: string) {
return this.songsRepository
.createQueryBuilder('song')
.where('song.title ILIKE :query OR song.artist ILIKE :query', {
query: `%${query}%`,
})
.getMany();
}

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

async findByDifficulty(level: number) {
return this.songsRepository.find({ where: { difficulty: level } });
}

async getRandomSong() {
return this.songsRepository
.createQueryBuilder('song')
.orderBy('RANDOM()')
.take(1)
.getOne();
}
}

0 comments on commit 5cc8280

Please sign in to comment.