-
Notifications
You must be signed in to change notification settings - Fork 1
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 #175 from boostcampwm-2024/back/main
[BE] 브랜치 병합
- Loading branch information
Showing
75 changed files
with
1,459 additions
and
602 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
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 |
---|---|---|
@@ -1,6 +1,78 @@ | ||
import { Controller } from '@nestjs/common'; | ||
import { ApiTags } from '@nestjs/swagger'; | ||
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common'; | ||
import { | ||
ApiBearerAuth, | ||
ApiOperation, | ||
ApiResponse, | ||
ApiTags, | ||
} from '@nestjs/swagger'; | ||
import { Request } from 'express'; | ||
import { Cron } from '@nestjs/schedule'; | ||
import { JwtAuthGuard } from '../auth/jwt-auth-guard'; | ||
import { AssetService } from './asset.service'; | ||
import { MypageResponseDto } from './dto/mypage-response.dto'; | ||
|
||
@Controller('/api/assets') | ||
@ApiTags('자산 API') | ||
export class AssetController {} | ||
@ApiTags('사용자 자산 및 보유 주식 API') | ||
export class AssetController { | ||
constructor(private readonly assetService: AssetService) {} | ||
|
||
@Get('/stocks/:stockCode') | ||
@ApiBearerAuth() | ||
@UseGuards(JwtAuthGuard) | ||
@ApiOperation({ | ||
summary: '매도 가능 주식 개수 조회 API', | ||
description: '특정 주식 매도 시에 필요한 매도 가능한 주식 개수를 조회한다.', | ||
}) | ||
@ApiResponse({ | ||
status: 200, | ||
description: '매도 가능 주식 개수 조회 성공', | ||
example: { quantity: 0 }, | ||
}) | ||
async getUserStockByCode( | ||
@Req() request: Request, | ||
@Param('stockCode') stockCode: string, | ||
) { | ||
return this.assetService.getUserStockByCode( | ||
parseInt(request.user.userId, 10), | ||
stockCode, | ||
); | ||
} | ||
|
||
@Get('/cash') | ||
@ApiBearerAuth() | ||
@UseGuards(JwtAuthGuard) | ||
@ApiOperation({ | ||
summary: '매수 가능 금액 조회 API', | ||
description: | ||
'특정 주식 매수 시에 필요한 매수 가능한 금액(현재 가용자산)을 조회한다.', | ||
}) | ||
@ApiResponse({ | ||
status: 200, | ||
description: '매수 가능 금액 조회 성공', | ||
example: { cash_balance: 0 }, | ||
}) | ||
async getCashBalance(@Req() request: Request) { | ||
return this.assetService.getCashBalance(parseInt(request.user.userId, 10)); | ||
} | ||
|
||
@Get() | ||
@ApiBearerAuth() | ||
@UseGuards(JwtAuthGuard) | ||
@ApiOperation({ | ||
summary: '마이페이지 보유 자산 현황 조회 API', | ||
description: '마이페이지 조회 시 필요한 보유 자산 현황을 조회한다.', | ||
}) | ||
@ApiResponse({ | ||
status: 200, | ||
description: '매수 가능 금액 조회 성공', | ||
type: MypageResponseDto, | ||
}) | ||
async getMyPage(@Req() request: Request) { | ||
return this.assetService.getMyPage(parseInt(request.user.userId, 10)); | ||
} | ||
|
||
@Cron('*/10 9-16 * * 1-5') | ||
async updateAllAssets() { | ||
await this.assetService.updateAllAssets(); | ||
} | ||
} |
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
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 |
---|---|---|
@@ -1,4 +1,117 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { UserStockRepository } from './user-stock.repository'; | ||
import { AssetRepository } from './asset.repository'; | ||
import { MypageResponseDto } from './dto/mypage-response.dto'; | ||
import { StockElementResponseDto } from './dto/stock-element-response.dto'; | ||
import { AssetResponseDto } from './dto/asset-response.dto'; | ||
import { StockDetailService } from '../stock/detail/stock-detail.service'; | ||
import { UserStock } from './user-stock.entity'; | ||
import { Asset } from './asset.entity'; | ||
|
||
@Injectable() | ||
export class AssetService {} | ||
export class AssetService { | ||
constructor( | ||
private readonly userStockRepository: UserStockRepository, | ||
private readonly assetRepository: AssetRepository, | ||
private readonly stockDetailService: StockDetailService, | ||
) {} | ||
|
||
async getUserStockByCode(userId: number, stockCode: string) { | ||
const userStock = await this.userStockRepository.findOneBy({ | ||
user_id: userId, | ||
stock_code: stockCode, | ||
}); | ||
|
||
return { quantity: userStock ? userStock.quantity : 0 }; | ||
} | ||
|
||
async getCashBalance(userId: number) { | ||
const asset = await this.assetRepository.findOneBy({ user_id: userId }); | ||
|
||
return { cash_balance: asset.cash_balance }; | ||
} | ||
|
||
async getMyPage(userId: number) { | ||
const userStocks = | ||
await this.userStockRepository.findUserStockWithNameByUserId(userId); | ||
const asset = await this.assetRepository.findOneBy({ user_id: userId }); | ||
const newAsset = await this.updateMyAsset( | ||
asset, | ||
await this.getCurrPrices(), | ||
); | ||
|
||
const myStocks = userStocks.map((userStock) => { | ||
return new StockElementResponseDto( | ||
userStock.stocks_name, | ||
userStock.stocks_code, | ||
userStock.user_stocks_quantity, | ||
userStock.user_stocks_avg_price, | ||
); | ||
}); | ||
|
||
const myAsset = new AssetResponseDto( | ||
newAsset.cash_balance, | ||
newAsset.stock_balance, | ||
newAsset.total_asset, | ||
newAsset.total_profit, | ||
newAsset.total_profit_rate, | ||
newAsset.total_profit_rate >= 0, | ||
); | ||
|
||
const response = new MypageResponseDto(); | ||
response.asset = myAsset; | ||
response.stocks = myStocks; | ||
|
||
return response; | ||
} | ||
|
||
async updateAllAssets() { | ||
const currPrices = await this.getCurrPrices(); | ||
const assets = await this.assetRepository.find(); | ||
|
||
await Promise.allSettled( | ||
assets.map((asset) => this.updateMyAsset(asset, currPrices)), | ||
); | ||
} | ||
|
||
private async updateMyAsset(asset: Asset, currPrices) { | ||
const userId = asset.user_id; | ||
const userStocks = await this.userStockRepository.find({ | ||
where: { user_id: userId }, | ||
}); | ||
|
||
const totalPrice = userStocks.reduce( | ||
(sum, userStock) => | ||
sum + userStock.quantity * currPrices[userStock.stock_code], | ||
0, | ||
); | ||
|
||
const updatedAsset = { | ||
...asset, | ||
stock_balance: totalPrice, | ||
total_asset: asset.cash_balance + totalPrice, | ||
total_profit: asset.cash_balance + totalPrice - 10000000, | ||
total_profit_rate: (asset.cash_balance + totalPrice - 10000000) / 100000, | ||
last_updated: new Date(), | ||
prev_total_asset: asset.total_asset, | ||
}; | ||
return this.assetRepository.save(updatedAsset); | ||
} | ||
|
||
private async getCurrPrices() { | ||
const userStocks: UserStock[] = | ||
await this.userStockRepository.findAllDistinctCode(); | ||
const currPrices = {}; | ||
|
||
await Promise.allSettled( | ||
userStocks.map(async (userStock) => { | ||
const inquirePrice = await this.stockDetailService.getInquirePrice( | ||
userStock.stock_code, | ||
); | ||
currPrices[userStock.stock_code] = Number(inquirePrice.stck_prpr); | ||
}), | ||
); | ||
|
||
return currPrices; | ||
} | ||
} |
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,37 @@ | ||
import { ApiProperty } from '@nestjs/swagger'; | ||
|
||
export class AssetResponseDto { | ||
constructor( | ||
cash_balance, | ||
stock_balance, | ||
total_asset, | ||
total_profit, | ||
total_profit_rate, | ||
is_positive, | ||
) { | ||
this.cash_balance = cash_balance; | ||
this.stock_balance = stock_balance; | ||
this.total_asset = total_asset; | ||
this.total_profit = total_profit; | ||
this.total_profit_rate = total_profit_rate; | ||
this.is_positive = is_positive; | ||
} | ||
|
||
@ApiProperty({ description: '보유 현금' }) | ||
cash_balance: number; | ||
|
||
@ApiProperty({ description: '주식 평가 금액' }) | ||
stock_balance: number; | ||
|
||
@ApiProperty({ description: '총 자산' }) | ||
total_asset: number; | ||
|
||
@ApiProperty({ description: '총 수익금' }) | ||
total_profit: number; | ||
|
||
@ApiProperty({ description: '총 수익률' }) | ||
total_profit_rate: number; | ||
|
||
@ApiProperty({ description: '수익률 부호' }) | ||
is_positive: boolean; | ||
} |
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,17 @@ | ||
import { ApiProperty } from '@nestjs/swagger'; | ||
import { StockElementResponseDto } from './stock-element-response.dto'; | ||
import { AssetResponseDto } from './asset-response.dto'; | ||
|
||
export class MypageResponseDto { | ||
@ApiProperty({ | ||
description: '보유 자산', | ||
type: AssetResponseDto, | ||
}) | ||
asset: AssetResponseDto; | ||
|
||
@ApiProperty({ | ||
description: '보유 주식 리스트', | ||
type: [StockElementResponseDto], | ||
}) | ||
stocks: StockElementResponseDto[]; | ||
} |
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,22 @@ | ||
import { ApiProperty } from '@nestjs/swagger'; | ||
|
||
export class StockElementResponseDto { | ||
constructor(name, code, quantity, avg_price) { | ||
this.name = name; | ||
this.code = code; | ||
this.quantity = quantity; | ||
this.avg_price = avg_price; | ||
} | ||
|
||
@ApiProperty({ description: '종목 이름' }) | ||
name: string; | ||
|
||
@ApiProperty({ description: '종목 코드' }) | ||
code: string; | ||
|
||
@ApiProperty({ description: '보유량' }) | ||
quantity: number; | ||
|
||
@ApiProperty({ description: '평균 매수가' }) | ||
avg_price: number; | ||
} |
Oops, something went wrong.