diff --git a/server/src/auth/auth.controller.spec.ts b/server/src/auth/auth.controller.spec.ts index af190b8..15b7ca4 100644 --- a/server/src/auth/auth.controller.spec.ts +++ b/server/src/auth/auth.controller.spec.ts @@ -3,7 +3,7 @@ import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; import { getRepositoryToken } from '@nestjs/typeorm'; import { User } from 'src/entity/user.entity'; -import { Repository } from 'typeorm'; +import { Repository, DataSource } from 'typeorm'; import { JwtModule, JwtService } from '@nestjs/jwt'; import { Playlist } from 'src/entity/playlist.entity'; import { Music } from 'src/entity/music.entity'; @@ -16,6 +16,7 @@ describe('AuthController', () => { let service: AuthService; let jwtModule: JwtModule; let userRepository: Repository; + let mockDataSource: jest.Mocked; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -40,6 +41,10 @@ describe('AuthController', () => { provide: getRepositoryToken(Music_Playlist), useClass: Repository, }, + { + provide: DataSource, + useValue: mockDataSource, + }, ], }).compile(); diff --git a/server/src/auth/auth.service.spec.ts b/server/src/auth/auth.service.spec.ts index 6c63367..282732d 100644 --- a/server/src/auth/auth.service.spec.ts +++ b/server/src/auth/auth.service.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; -import { Repository } from 'typeorm'; +import { Repository, DataSource } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; import { User } from 'src/entity/user.entity'; import { JwtModule, JwtService } from '@nestjs/jwt'; @@ -15,6 +15,7 @@ describe('AuthService', () => { let jwtModule: JwtModule; let userRepository: Repository; let playlistService: PlaylistService; + let mockDataSource: jest.Mocked; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -38,6 +39,10 @@ describe('AuthService', () => { useClass: Repository, }, PlaylistService, + { + provide: DataSource, + useValue: mockDataSource, + }, ], }).compile(); diff --git a/server/src/auth/auth.service.ts b/server/src/auth/auth.service.ts index c2ee415..f8d4955 100644 --- a/server/src/auth/auth.service.ts +++ b/server/src/auth/auth.service.ts @@ -6,7 +6,7 @@ import { ERROR_CODE } from 'src/config/errorCode.enum'; import { UserCreateDto } from 'src/dto/userCreate.dto'; import { User } from 'src/entity/user.entity'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; -import { Repository } from 'typeorm'; +import { Repository, DataSource } from 'typeorm'; import { v4 as uuid } from 'uuid'; @Injectable() @@ -15,6 +15,7 @@ export class AuthService { constructor( @InjectRepository(User) private userRepository: Repository, private jwtService: JwtService, + private readonly dataSource: DataSource, ) {} async login(email: string): Promise<{ accessToken: string }> { @@ -27,14 +28,14 @@ export class AuthService { const accessToken = this.jwtService.sign(payload); return { accessToken }; - } else { - this.logger.error(`auth.service - login : NOT_EXIST_USER`); - throw new CatchyException( - 'NOT_EXIST_USER', - HTTP_STATUS_CODE['WRONG_TOKEN'], - ERROR_CODE.NOT_EXIST_USER, - ); } + + this.logger.error(`auth.service - login : NOT_EXIST_USER`); + throw new CatchyException( + 'NOT_EXIST_USER', + HTTP_STATUS_CODE['WRONG_TOKEN'], + ERROR_CODE.NOT_EXIST_USER, + ); } async signup(userCreateDto: UserCreateDto): Promise<{ accessToken: string }> { @@ -49,24 +50,38 @@ export class AuthService { ERROR_CODE.ALREADY_EXIST_EMAIL, ); } - if (email) { - const newUser: User = this.userRepository.create({ - user_id: uuid(), - nickname, - photo: null, - user_email: email, - created_at: new Date(), - }); - await this.userRepository.save(newUser); - - return this.login(email); + + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.startTransaction(); + + try { + if (email) { + const newUser: User = this.userRepository.create({ + user_id: uuid(), + nickname, + photo: null, + user_email: email, + created_at: new Date(), + }); + + await queryRunner.manager.save(newUser); + + await queryRunner.commitTransaction(); + + return await this.login(email); + } + + this.logger.error(`auth.service - signup : WRONG_TOKEN`); + throw new CatchyException( + 'WRONG_TOKEN', + HTTP_STATUS_CODE.WRONG_TOKEN, + ERROR_CODE.WRONG_TOKEN, + ); + } catch { + await queryRunner.rollbackTransaction(); + } finally { + await queryRunner.release(); } - this.logger.error(`auth.service - signup : WRONG_TOKEN`); - throw new CatchyException( - 'WRONG_TOKEN', - HTTP_STATUS_CODE.WRONG_TOKEN, - ERROR_CODE.WRONG_TOKEN, - ); } async getGoogleEmail(googleIdToken: string): Promise { @@ -84,6 +99,7 @@ export class AuthService { ERROR_CODE.EXPIRED_TOKEN, ); } + return userInfo.email; } @@ -100,7 +116,18 @@ export class AuthService { } async deleteUser(user: User): Promise<{ userId: string }> { - await this.userRepository.delete(user.user_id); - return { userId: user.user_id }; + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.startTransaction(); + + try { + await queryRunner.manager.remove(user); + await queryRunner.commitTransaction(); + + return { userId: user.user_id }; + } catch { + await queryRunner.rollbackTransaction(); + } finally { + await queryRunner.release(); + } } } diff --git a/server/src/music/music.controller.spec.ts b/server/src/music/music.controller.spec.ts index 6539d59..cf2fe9a 100644 --- a/server/src/music/music.controller.spec.ts +++ b/server/src/music/music.controller.spec.ts @@ -6,7 +6,7 @@ import { UploadService } from 'src/upload/upload.service'; import { NcloudConfigService } from 'src/config/ncloud.config'; import { ConfigService } from '@nestjs/config'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { DataSource, Repository } from 'typeorm'; import { Music } from 'src/entity/music.entity'; import { musicCreateInfo, user } from 'test/constants/music.mockData'; import { MusicController } from './music.controller'; @@ -27,6 +27,7 @@ describe('UploadController', () => { let authService: AuthService; let musicRepository: Repository; let mockJwtStrategy = { validate: () => user }; + let mockDataSource: jest.Mocked; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -49,6 +50,10 @@ describe('UploadController', () => { provide: getRepositoryToken(User), useClass: Repository, }, + { + provide: DataSource, + useValue: mockDataSource, + }, ], }) .overrideProvider(JwtStrategy) @@ -62,6 +67,9 @@ describe('UploadController', () => { musicController = module.get(MusicController); musicService = module.get(MusicService); musicRepository = module.get(getRepositoryToken(Music)); + mockDataSource = { + createQueryRunner: jest.fn(), + } as unknown as jest.Mocked; app = module.createNestApplication(); await app.init(); diff --git a/server/src/music/music.service.spec.ts b/server/src/music/music.service.spec.ts index 2f01b83..a3ff10a 100644 --- a/server/src/music/music.service.spec.ts +++ b/server/src/music/music.service.spec.ts @@ -5,7 +5,7 @@ import { UploadService } from 'src/upload/upload.service'; import { NcloudConfigService } from 'src/config/ncloud.config'; import { ConfigService } from '@nestjs/config'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { DataSource, Repository } from 'typeorm'; import { Music } from 'src/entity/music.entity'; import { MusicCreateDto } from 'src/dto/musicCreate.dto'; import { CatchyException } from 'src/config/catchyException'; @@ -24,6 +24,7 @@ describe('UploadController', () => { let cloudService: NcloudConfigService; let configService: ConfigService; let mockRepository: jest.Mocked>; + let mockDataSource: jest.Mocked; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -43,12 +44,24 @@ describe('UploadController', () => { provide: getRepositoryToken(Recent_Played), useClass: Repository, }, + { + provide: DataSource, + useValue: mockDataSource, + }, ], }).compile(); configService = module.get(ConfigService); cloudService = module.get(NcloudConfigService); uploadService = module.get(UploadService); + mockDataSource = { + createQueryRunner: jest.fn().mockResolvedValue({ + startTransaction: jest.fn(), + commitTransaction: jest.fn(), + rollbackTransaction: jest.fn(), + release: jest.fn(), + }), + } as unknown as jest.Mocked; mockRepository = { create: jest.fn(), save: jest.fn(), @@ -57,6 +70,7 @@ describe('UploadController', () => { mockRepository, uploadService, cloudService, + mockDataSource, ); }); diff --git a/server/src/music/music.service.ts b/server/src/music/music.service.ts index 9c12f3f..6007874 100644 --- a/server/src/music/music.service.ts +++ b/server/src/music/music.service.ts @@ -1,6 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; -import { Repository } from 'typeorm'; +import { Repository, DataSource } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; import { MusicCreateDto } from 'src/dto/musicCreate.dto'; import { Music } from 'src/entity/music.entity'; @@ -19,6 +19,7 @@ export class MusicService { @InjectRepository(Music) private musicRepository: Repository, private uploadService: UploadService, private readonly ncloudConfigService: NcloudConfigService, + private readonly dataSource: DataSource, ) { this.objectStorage = ncloudConfigService.createObjectStorageOption(); } @@ -35,24 +36,21 @@ export class MusicService { musicCreateDto: MusicCreateDto, user_id: string, ): Promise { - try { - const { - music_id, - title, - cover, - file: music_file, - genre, - } = musicCreateDto; + const { music_id, title, cover, file: music_file, genre } = musicCreateDto; - if (!this.isValidGenre(genre)) { - this.logger.error(`music.service - createMusic : NOT_EXIST_GENRE`); - throw new CatchyException( - 'NOT_EXIST_GENRE', - HTTP_STATUS_CODE.BAD_REQUEST, - ERROR_CODE.NOT_EXIST_GENRE, - ); - } + if (!this.isValidGenre(genre)) { + this.logger.error(`music.service - createMusic : NOT_EXIST_GENRE`); + throw new CatchyException( + 'NOT_EXIST_GENRE', + HTTP_STATUS_CODE.BAD_REQUEST, + ERROR_CODE.NOT_EXIST_GENRE, + ); + } + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.startTransaction(); + + try { const newMusic: Music = this.musicRepository.create({ music_id, title, @@ -63,10 +61,14 @@ export class MusicService { user: { user_id }, }); - const savedMusic: Music = await this.musicRepository.save(newMusic); + await queryRunner.manager.save(newMusic); - return savedMusic.music_id; + await queryRunner.commitTransaction(); + + return music_id; } catch (err) { + await queryRunner.rollbackTransaction(); + if (err instanceof CatchyException) { throw err; } @@ -77,6 +79,8 @@ export class MusicService { HTTP_STATUS_CODE.SERVER_ERROR, ERROR_CODE.SERVICE_ERROR, ); + } finally { + await queryRunner.release(); } } diff --git a/server/src/playlist/playlist.service.ts b/server/src/playlist/playlist.service.ts index 7926759..0748638 100644 --- a/server/src/playlist/playlist.service.ts +++ b/server/src/playlist/playlist.service.ts @@ -7,7 +7,7 @@ import { Music } from 'src/entity/music.entity'; import { Music_Playlist } from 'src/entity/music_playlist.entity'; import { Playlist } from 'src/entity/playlist.entity'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; -import { Repository } from 'typeorm'; +import { DataSource, Repository } from 'typeorm'; @Injectable() export class PlaylistService { @@ -19,12 +19,16 @@ export class PlaylistService { private music_playlistRepository: Repository, @InjectRepository(Music) private MusicRepository: Repository, + private readonly dataSource: DataSource, ) {} async createPlaylist( userId: string, playlistCreateDto: PlaylistCreateDto, ): Promise { + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.startTransaction(); + try { const title: string = playlistCreateDto.title; const newPlaylist: Playlist = this.playlistRepository.create({ @@ -34,16 +38,24 @@ export class PlaylistService { user: { user_id: userId }, }); - const result: Playlist = await this.playlistRepository.save(newPlaylist); + const result: Playlist = await queryRunner.manager.save(newPlaylist); + + await queryRunner.commitTransaction(); + const playlistId: number = result.playlist_id; + return playlistId; } catch { + await queryRunner.rollbackTransaction(); + this.logger.error(`playlist.service - createPlaylist : SERVICE_ERROR`); throw new CatchyException( 'SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR, ERROR_CODE.SERVICE_ERROR, ); + } finally { + await queryRunner.release(); } } @@ -87,6 +99,9 @@ export class PlaylistService { ); } + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.startTransaction(); + // 관계테이블에 추가 try { const new_music_playlist: Music_Playlist = @@ -96,11 +111,20 @@ export class PlaylistService { created_at: new Date(), }); - const result: Music_Playlist = - await this.music_playlistRepository.save(new_music_playlist); - this.setUpdatedAtNow(playlistId); - return result.music_playlist_id; + const targetPlaylist: Playlist = await this.playlistRepository.findOne({ + where: { playlist_id: playlistId }, + }); + targetPlaylist.updated_at = new Date(); + + await queryRunner.manager.save(new_music_playlist); + await queryRunner.manager.save(targetPlaylist); + + await queryRunner.commitTransaction(); + + return new_music_playlist.music_playlist_id; } catch { + await queryRunner.rollbackTransaction(); + this.logger.error( `playlist.service - addMusicToPlaylist : SERVICE_ERROR`, ); @@ -109,6 +133,8 @@ export class PlaylistService { HTTP_STATUS_CODE.SERVER_ERROR, ERROR_CODE.SERVICE_ERROR, ); + } finally { + await queryRunner.release(); } } @@ -168,23 +194,6 @@ export class PlaylistService { } } - async setUpdatedAtNow(playlistId: number): Promise { - try { - const targetPlaylist: Playlist = await this.playlistRepository.findOne({ - where: { playlist_id: playlistId }, - }); - targetPlaylist.updated_at = new Date(); - this.playlistRepository.save(targetPlaylist); - } catch { - this.logger.error(`playlist.service - setUpdatedAtNow : SERVICE_ERROR`); - throw new CatchyException( - 'SERVER_ERROR', - HTTP_STATUS_CODE.SERVER_ERROR, - ERROR_CODE.SERVICE_ERROR, - ); - } - } - async getUserPlaylists(userId: string): Promise { try { const playlists: Playlist[] = await Playlist.getPlaylistsByUserId(userId); @@ -265,6 +274,9 @@ export class PlaylistService { ); } + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.startTransaction(); + try { const target: Music_Playlist = await this.music_playlistRepository.findOne({ @@ -273,6 +285,9 @@ export class PlaylistService { playlist: { playlist_id: playlistId }, }, }); + + const deletedMusicId: number = target.music_playlist_id; + if (target == undefined) { this.logger.error( `playlist.service - deleteMusicInPlaylist : NOT_ADDED_MUSIC`, @@ -283,9 +298,15 @@ export class PlaylistService { ERROR_CODE.NOT_ADDED_MUSIC, ); } - this.music_playlistRepository.delete(target.music_playlist_id); - return target.music_playlist_id; + + await queryRunner.manager.remove(target); + + await queryRunner.commitTransaction(); + + return deletedMusicId; } catch (error) { + await queryRunner.rollbackTransaction(); + if (error instanceof CatchyException) { throw error; } @@ -298,6 +319,8 @@ export class PlaylistService { HTTP_STATUS_CODE.BAD_REQUEST, ERROR_CODE.SERVICE_ERROR, ); + } finally { + await queryRunner.release(); } } }