diff --git a/BE/src/auth/dto/rename-user.dto.ts b/BE/src/auth/dto/rename-user.dto.ts new file mode 100644 index 00000000..fe809699 --- /dev/null +++ b/BE/src/auth/dto/rename-user.dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class RenameUserDto { + @ApiProperty({ description: '변경할 닉네임' }) + nickname: string; +} diff --git a/BE/src/auth/user.controller.ts b/BE/src/auth/user.controller.ts index 881a2b7c..1476f727 100644 --- a/BE/src/auth/user.controller.ts +++ b/BE/src/auth/user.controller.ts @@ -1,7 +1,8 @@ -import { Controller, Get, Req, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Patch, Req, UseGuards } from '@nestjs/common'; import { Request } from 'express'; import { ApiBearerAuth, + ApiBody, ApiOperation, ApiResponse, ApiTags, @@ -9,6 +10,7 @@ import { import { UserService } from './user.service'; import { JwtAuthGuard } from './jwt-auth-guard'; import { ProfileResponseDto } from './dto/profile-response.dto'; +import { RenameUserDto } from './dto/rename-user.dto'; @Controller('/api/user') @ApiTags('프로필 API') @@ -27,4 +29,20 @@ export class UserController { getProfile(@Req() request: Request) { return this.userService.getProfile(parseInt(request.user.userId, 10)); } + + @Patch('/rename') + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: '유저 닉네임 변경 API', + }) + @ApiBody({ + type: RenameUserDto, + }) + @ApiBearerAuth() + renameUser(@Req() request: Request, @Body() body: RenameUserDto) { + const userId = parseInt(request.user.userId, 10); + const newName = body.nickname; + + return this.userService.renameUser(userId, newName); + } } diff --git a/BE/src/auth/user.repository.ts b/BE/src/auth/user.repository.ts index 4259adc1..1b37c53b 100644 --- a/BE/src/auth/user.repository.ts +++ b/BE/src/auth/user.repository.ts @@ -26,6 +26,10 @@ export class UserRepository extends Repository { const hashedPassword: string = await bcrypt.hash(password, salt); const user = this.create({ email, password: hashedPassword }); await queryRunner.manager.save(user); + + user.nickname = `익명의 투자자${user.id}`; + await queryRunner.manager.save(user); + const asset = this.assetRepository.create({ user_id: user.id }); await queryRunner.manager.save(asset); @@ -52,6 +56,10 @@ export class UserRepository extends Repository { password: hashedPassword, }); await this.save(user); + + user.nickname = `익명의 투자자${user.id}`; + await queryRunner.manager.save(user); + const asset = this.assetRepository.create({ user_id: user.id }); await queryRunner.manager.save(asset); diff --git a/BE/src/auth/user.service.ts b/BE/src/auth/user.service.ts index fe860a50..dd9df95c 100644 --- a/BE/src/auth/user.service.ts +++ b/BE/src/auth/user.service.ts @@ -1,4 +1,8 @@ -import { Injectable } from '@nestjs/common'; +import { + BadRequestException, + Injectable, + NotFoundException, +} from '@nestjs/common'; import { UserRepository } from './user.repository'; import { ProfileResponseDto } from './dto/profile-response.dto'; @@ -10,4 +14,36 @@ export class UserService { const user = await this.userRepository.findOneBy({ id: userId }); return new ProfileResponseDto(user.nickname, user.email); } + + async renameUser(userId: number, newName: string) { + const user = await this.userRepository.findOneBy({ id: userId }); + if (!user) { + throw new NotFoundException('존재하지 않는 유저입니다.'); + } + + this.validateName(newName); + await this.checkNameDuplicate(newName); + + return this.userRepository.update({ id: userId }, { nickname: newName }); + } + + private validateName(nickname: string) { + const regex = /^[가-힣a-zA-Z0-9]+$/; + if (!regex.test(nickname)) { + throw new BadRequestException('한글, 영문, 숫자만 사용 가능합니다.'); + } + + if (nickname.includes('익명의투자자')) { + throw new BadRequestException('사용 불가능한 단어가 포함되어 있습니다.'); + } + } + + private async checkNameDuplicate(nickname: string) { + const isDuplicated = await this.userRepository.existsBy({ + nickname, + }); + if (isDuplicated) { + throw new BadRequestException('이미 존재하는 닉네임입니다.'); + } + } } diff --git a/BE/src/ranking/ranking.service.ts b/BE/src/ranking/ranking.service.ts index b5782877..b7559f8f 100644 --- a/BE/src/ranking/ranking.service.ts +++ b/BE/src/ranking/ranking.service.ts @@ -104,7 +104,7 @@ export class RankingService { }; } - @Cron('*/1 * * * 1-5') + @Cron('0 16 * * 1-5') async updateRanking() { const [profitRateRanking, assetRanking] = await Promise.all([ this.calculateRanking(SortType.PROFIT_RATE), diff --git a/BE/src/stockSocket/stock-execute-order.repository.ts b/BE/src/stockSocket/stock-execute-order.repository.ts index af60a937..1c14ec80 100644 --- a/BE/src/stockSocket/stock-execute-order.repository.ts +++ b/BE/src/stockSocket/stock-execute-order.repository.ts @@ -29,7 +29,7 @@ export class StockExecuteOrderRepository extends Repository { async checkExecutableOrder(stockCode, value) { const queryRunner = this.dataSource.createQueryRunner(); - await queryRunner.startTransaction(); + await queryRunner.startTransaction('SERIALIZABLE'); try { const buyOrders = await queryRunner.manager.find(Order, { diff --git a/FE/src/utils/chart/drawUpperYAxis.ts b/FE/src/utils/chart/drawUpperYAxis.ts index 1542cff3..769a7e0f 100644 --- a/FE/src/utils/chart/drawUpperYAxis.ts +++ b/FE/src/utils/chart/drawUpperYAxis.ts @@ -16,6 +16,7 @@ export const drawUpperYAxis = ( ) => { const values = data .map((d) => { + if (d.mov_avg_20 && d.mov_avg_5) { return [ +d.stck_hgpr, @@ -25,6 +26,7 @@ export const drawUpperYAxis = ( Math.floor(+d.mov_avg_5), Math.floor(+d.mov_avg_20), ]; + } else if (d.mov_avg_5) { return [ +d.stck_hgpr, @@ -33,6 +35,7 @@ export const drawUpperYAxis = ( +d.stck_oprc, Math.floor(+d.mov_avg_5), ]; + } else if (d.mov_avg_20) { return [ +d.stck_hgpr,