Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] - Feature be #131 account, asset, favorite 리팩토링 #133

Merged
merged 4 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/server/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module.exports = {
'error',
{
endOfLine: 'auto',
useTabs: 'false',
useTabs: false,
},
],
'no-console': ['warn', { allow: ['warn', 'error'] }],
Expand Down
53 changes: 38 additions & 15 deletions packages/server/src/account/account.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,60 @@ import {
Get,
HttpCode,
HttpStatus,
Request,
Res,
UnauthorizedException,
Req,
UseGuards,
Logger,
} from '@nestjs/common';
import { AuthGuard } from '../auth/auth.guard';
import { ApiBearerAuth, ApiSecurity } from '@nestjs/swagger';
import {
ApiBearerAuth,
ApiOperation,
ApiResponse,
ApiTags,
ApiSecurity,
} from '@nestjs/swagger';
import { AccountService } from './account.service';
import { Response } from 'express';
import { Request } from 'express';
import { MyAccountResponseDto } from './dtos/my-account.response.dto';

@ApiTags('계정 API')
@Controller('account')
export class AccountController {
constructor(private accountService: AccountService) {}
private readonly logger = new Logger(AccountController.name);

constructor(private readonly accountService: AccountService) {}

@ApiOperation({
summary: '내 계정 정보 조회',
description: '현재 로그인한 사용자의 계정 정보를 조회합니다.',
})
@ApiResponse({
status: HttpStatus.OK,
description: '계정 정보 조회 성공',
type: MyAccountResponseDto,
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: '인증되지 않은 사용자',
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: '서버 에러',
})
@HttpCode(HttpStatus.OK)
@ApiBearerAuth('access-token')
@ApiSecurity('access-token')
@UseGuards(AuthGuard)
@Get('myaccount')
async signIn(@Request() req, @Res() res: Response) {
async getMyAccount(@Req() req: Request): Promise<MyAccountResponseDto> {
this.logger.log(`계정 정보 조회 시작: ${req.user['userId']}`);
try {
const response = await this.accountService.getMyAccountData(req.user);
if (response instanceof UnauthorizedException) {
return res
.status(HttpStatus.UNAUTHORIZED)
.json({ message: response.message });
}

return res.status(response.statusCode).json(response.message);
this.logger.log(`계정 정보 조회 완료: ${req.user['userId']}`);
return response;
} catch (error) {
return res.status(error.statusCode).json(error);
this.logger.error(`계정 정보 조회 실패: ${error.message}`, error.stack);
throw error;
}
}
}
141 changes: 93 additions & 48 deletions packages/server/src/account/account.repository.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,129 @@
import { DataSource, Repository } from 'typeorm';
import { DataSource, Repository, QueryRunner } from 'typeorm';
import {
Injectable,
UnprocessableEntityException,
Logger,
} from '@nestjs/common';
import { Account } from './account.entity';
import { Injectable, UnprocessableEntityException } from '@nestjs/common';
import { User } from 'src/auth/user.entity';
import { User } from '@src/auth/user.entity';
import { CURRENCY_CONSTANTS } from './constants/currency.constants';
import { UserDto } from './dtos/my-account.response.dto';

@Injectable()
export class AccountRepository extends Repository<Account> {
constructor(private dataSource: DataSource) {
private readonly logger = new Logger(AccountRepository.name);

constructor(private readonly dataSource: DataSource) {
super(Account, dataSource.createEntityManager());
}
async createAccountForAdmin(adminUser: User) {
const account = new Account();
account.KRW = 300000000;
account.USDT = 300000;
account.BTC = 0;
account.user = adminUser;
await this.save(account);
console.log('admin 계정에 Account가 성공적으로 생성되었습니다.');

async createAccountForAdmin(adminUser: User): Promise<void> {
this.logger.log(`관리자 계정 생성 시작: ${adminUser.id}`);
try {
const account = new Account();
account.KRW = CURRENCY_CONSTANTS.ADMIN_INITIAL_KRW;
account.USDT = CURRENCY_CONSTANTS.ADMIN_INITIAL_USDT;
account.BTC = CURRENCY_CONSTANTS.ADMIN_INITIAL_BTC;
account.user = adminUser;

await this.save(account);
this.logger.log(`관리자 계정 생성 완료: ${adminUser.id}`);
} catch (error) {
this.logger.error(`관리자 계정 생성 실패: ${error.message}`, error.stack);
throw error;
}
}
async getMyMoney(user, moneyType: string) {
const account = await this.findOne({
where: { user: { id: user.userId } },
});

if (!account[moneyType]) return 0;
return account[moneyType];
async getMyMoney(user: UserDto, moneyType: string): Promise<number> {
try {
const account = await this.findOne({
where: { user: { id: user.userId } },
});

return account?.[moneyType] || 0;
} catch (error) {
this.logger.error(`잔액 조회 실패: ${error.message}`, error.stack);
throw error;
}
}

async updateAccountCurrency(
typeGiven,
accountBalance,
accountId,
queryRunner,
) {
typeGiven: string,
accountBalance: number,
accountId: number,
queryRunner: QueryRunner,
): Promise<void> {
this.logger.log(
`계정 통화 업데이트 시작: accountId=${accountId}, type=${typeGiven}`,
);
try {
await queryRunner.manager
.createQueryBuilder()
.update(Account)
.set({
[typeGiven]: accountBalance,
})
.set({ [typeGiven]: accountBalance })
.where('id = :id', { id: accountId })
.execute();

this.logger.log(`계정 통화 업데이트 완료: accountId=${accountId}`);
} catch (error) {
console.log(error);
this.logger.error(
`계정 통화 업데이트 실패: ${error.message}`,
error.stack,
);
throw error;
}
}
async updateAccountBTC(id, quantity, queryRunner) {
await queryRunner.manager
.createQueryBuilder()
.update(Account)
.set({
BTC: quantity,
})
.where('id = :id', { id: id })
.execute();

async updateAccountBTC(
id: number,
quantity: number,
queryRunner: QueryRunner,
): Promise<void> {
this.logger.log(`BTC 잔액 업데이트 시작: accountId=${id}`);
try {
await queryRunner.manager
.createQueryBuilder()
.update(Account)
.set({ BTC: quantity })
.where('id = :id', { id })
.execute();

this.logger.log(`BTC 잔액 업데이트 완료: accountId=${id}`);
} catch (error) {
this.logger.error(
`BTC 잔액 업데이트 실패: ${error.message}`,
error.stack,
);
throw error;
}
}

async validateUserAccount(userId: number) {
async validateUserAccount(userId: number): Promise<Account> {
this.logger.log(`사용자 계정 검증 시작: userId=${userId}`);
const userAccount = await this.findOne({
where: { user: { id: userId } },
});

if (!userAccount) {
throw new UnprocessableEntityException({
message: '유저가 존재하지 않습니다.',
statusCode: 422,
});
this.logger.warn(`존재하지 않는 사용자 계정: userId=${userId}`);
throw new UnprocessableEntityException('유저가 존재하지 않습니다.');
}

return userAccount;
}
async getAccount(id, queryRunner) {

async getAccount(id: number, queryRunner: QueryRunner): Promise<Account> {
this.logger.log(`계정 조회 시작: userId=${id}`);
try {
return await queryRunner.manager.findOne(Account, {
where: {
user: { id: id },
},
const account = await queryRunner.manager.findOne(Account, {
where: { user: { id } },
});

this.logger.log(`계정 조회 완료: userId=${id}`);
return account;
} catch (error) {
console.error('Error fetching account:', error);
throw new Error('Failed to fetch account');
this.logger.error(`계정 조회 실패: ${error.message}`, error.stack);
throw error;
}
}
}
107 changes: 72 additions & 35 deletions packages/server/src/account/account.service.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,98 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import {
HttpStatus,
Injectable,
Logger,
UnauthorizedException,
} from '@nestjs/common';
import { AssetRepository } from '@src/asset/asset.repository';
import { UPBIT_IMAGE_URL } from '@src/upbit/constants';
import { AccountRepository } from 'src/account/account.repository';
import { MyAccountDto } from './dtos/myAccount.dto';
import { CoinDto, MyAccountDto } from './dtos/my-account.dto';
import { CoinDataUpdaterService } from '@src/upbit/coin-data-updater.service';
import { CURRENCY_CONSTANTS } from './constants/currency.constants';
import { MyAccountResponseDto, UserDto } from './dtos/my-account.response.dto';
import { AccountRepository } from './account.repository';
import { Asset } from '@src/asset/asset.entity';

@Injectable()
export class AccountService {
private readonly logger = new Logger(AccountService.name);

constructor(
private accountRepository: AccountRepository,
private assetRepository: AssetRepository,
private coinDataUpdaterService: CoinDataUpdaterService,
private readonly accountRepository: AccountRepository,
private readonly assetRepository: AssetRepository,
private readonly coinDataUpdaterService: CoinDataUpdaterService,
) {}

async getMyAccountData(user) {
const accountData: MyAccountDto = new MyAccountDto();
async getMyAccountData(user: UserDto): Promise<MyAccountResponseDto> {
this.logger.log(`계정 데이터 조회 시작: ${user.userId}`);

const account = await this.accountRepository.findOne({
where: { user: { id: user.userId } },
});

if (!account) {
return new UnauthorizedException({
statusCode: 401,
message: '등록되지 않은 사용자입니다.',
this.logger.warn(`등록되지 않은 사용자 접근: ${user.userId}`);
throw new UnauthorizedException('등록되지 않은 사용자입니다.');
}

try {
const accountData = new MyAccountDto();
let totalPrice = 0;

const myCoins = await this.assetRepository.find({
where: { account: { id: account.id } },
});

const coinNameData = this.coinDataUpdaterService.getCoinNameList();
const coins: CoinDto[] = this.mapCoinsData(myCoins, coinNameData);
totalPrice = this.calculateTotalPrice(coins);

accountData.KRW = account.KRW;
accountData.total_bid = totalPrice;
accountData.coins = coins;

this.logger.log(`계정 데이터 조회 완료: ${user.userId}`);

return {
statusCode: HttpStatus.OK,
message: accountData,
};
} catch (error) {
this.logger.error(`계정 데이터 조회 실패: ${error.message}`, error.stack);
throw error;
}
const KRW = account.KRW;
let total_price = 0;
}

const myCoins = await this.assetRepository.find({
where: { account: { id: account.id } },
});
const coinNameData = this.coinDataUpdaterService.getCoinNameList();
const coins = [];
myCoins.forEach((myCoin) => {
private mapCoinsData(
myCoins: Asset[],
coinNameData: Map<string, string>,
): CoinDto[] {
return myCoins.map((myCoin) => {
const name = myCoin.assetName;
const coin = {
img_url: `${UPBIT_IMAGE_URL}${name}.png`,
koreanName:
coinNameData.get(`KRW-${name}`) ||
coinNameData.get(`BTC-${name}`) ||
coinNameData.get(`USDT-${name}`),
return {
img_url: `${CURRENCY_CONSTANTS.UPBIT_IMAGE_URL}${name}.png`,
koreanName: this.getKoreanName(name, coinNameData),
market: name,
quantity: myCoin.quantity,
availableQuantity: myCoin.availableQuantity,
price: myCoin.price,
averagePrice: myCoin.price / myCoin.quantity,
};
coins.push(coin);
total_price += myCoin.price;
});
accountData.KRW = KRW;
accountData.total_bid = total_price;
accountData.coins = coins;
return {
statusCode: 200,
message: accountData,
};
}

private getKoreanName(
name: string,
coinNameData: Map<string, string>,
): string {
const markets = ['KRW', 'BTC', 'USDT'];
for (const market of markets) {
const koreanName = coinNameData.get(`${market}-${name}`);
if (koreanName) return koreanName;
}
return '';
}

private calculateTotalPrice(coins: CoinDto[]): number {
return coins.reduce((total, coin) => total + coin.price, 0);
}
}
Loading