From 9b6c9f3679def49c6bcebd85ad537cc005078805 Mon Sep 17 00:00:00 2001 From: DongKey777 Date: Mon, 2 Dec 2024 11:53:57 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[feat]=20=EC=A3=BC=EB=AC=B8=20=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20API=EC=97=90=20=EC=A7=84=ED=96=89=EC=A4=91=EC=9D=B8?= =?UTF-8?q?=20=EC=98=A4=EB=8D=94=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/global/filter/errorExceptionFilter.ts | 8 +++- apps/backend/src/order/enums/orderType.ts | 9 ++++ apps/backend/src/order/order.controller.ts | 43 ++++++++++++++++--- apps/backend/src/order/order.repository.ts | 20 ++++++++- apps/backend/src/order/order.service.ts | 4 ++ 5 files changed, 75 insertions(+), 9 deletions(-) diff --git a/apps/backend/src/global/filter/errorExceptionFilter.ts b/apps/backend/src/global/filter/errorExceptionFilter.ts index e61b1c9..78cfceb 100644 --- a/apps/backend/src/global/filter/errorExceptionFilter.ts +++ b/apps/backend/src/global/filter/errorExceptionFilter.ts @@ -7,9 +7,15 @@ export class ErrorExceptionFilter implements ExceptionFilter { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const status = exception.getStatus(); + const exceptionResponse = exception.getResponse() as { message: string; data?: any }; // 타입 단언 + + const message = exceptionResponse.message; + const data = exceptionResponse.data; + response.status(status).json({ code: status, - message: exception.message + message, + data // 추가 데이터 포함 }); } } diff --git a/apps/backend/src/order/enums/orderType.ts b/apps/backend/src/order/enums/orderType.ts index bf452b5..b2401ee 100644 --- a/apps/backend/src/order/enums/orderType.ts +++ b/apps/backend/src/order/enums/orderType.ts @@ -28,3 +28,12 @@ export function toTradingType(tradingType: string): TradingType { throw new Error(`잘못된 트레이딩 타입입니다. ${tradingType}`); } + +export function toOrderStatus(orderStatus: string): OrderStatus { + if (orderStatus === 'pending') return OrderStatus.PENDING; + if (orderStatus === 'partially_filled') return OrderStatus.PARTIALLY_FILLED; + if (orderStatus === 'completed') return OrderStatus.COMPLETED; + if (orderStatus === 'canceled') return OrderStatus.CANCELED; + + throw new Error(`잘못된 오더 상태입니다. ${orderStatus}`); +} diff --git a/apps/backend/src/order/order.controller.ts b/apps/backend/src/order/order.controller.ts index 80518ec..db13e5a 100644 --- a/apps/backend/src/order/order.controller.ts +++ b/apps/backend/src/order/order.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, HttpException, HttpStatus, Post, UseGuards } from '@nestjs/common'; import { OrderService } from './order.service'; import { OrderBookService } from './orderBook.service'; import DtoTransformer from './utils/dtoTransformer'; @@ -6,7 +6,7 @@ import { LimitOrderDto } from './dto/limitOrder.dto'; import { ApiOperation } from '@nestjs/swagger'; import { MatchingService } from './matching.service'; import { successhandler, successMessage } from '../global/successhandler'; -import { cancelOrderResponseDecorator, orderResponseDecorator } from './decorator/order.decorator'; +import { orderResponseDecorator } from './decorator/order.decorator'; import { transactionResponseDecorator } from './decorator/getTransactions.decorator'; import { HasSufficientCashGuard } from '../account/guards/hasSufficientCashGuard'; import { AccountService } from '../account/account.service'; @@ -15,6 +15,7 @@ import { MarketOrderDto } from './dto/marketOrder.dto'; import { User } from '../global/utils/memberData'; import { CancelOrderDto } from './dto/cancelOrder.dto'; import { pendingOrdersDecorator } from './decorator/getPendingOrders.decorator'; +import { OrderStatus } from './enums/orderType'; @Controller('api/order') export class OrderController { @@ -129,13 +130,43 @@ export class OrderController { @Post('cancel') @ApiOperation({ summary: '주문 취소' }) - @cancelOrderResponseDecorator() + @pendingOrdersDecorator() async cancelOrder(@User() user: { memberId: number }, @Body() cancelOrderDto: CancelOrderDto) { const { memberId } = user; const { cropId, orderId, orderType, tradingType } = cancelOrderDto; - await this.orderBookService.removeOrder(memberId, cropId, orderId, orderType, tradingType); - await this.orderService.cancelOrder(memberId, orderId, cropId, orderType); - return successhandler(successMessage.DELETE_ORDER_SUCCESS); + try { + const orderStatus = await this.orderService.getOrderStatus(memberId, orderId); + if (orderStatus === OrderStatus.CANCELED || orderStatus === OrderStatus.COMPLETED) { + const pendingOrders = await this.orderService.getPendingOrdersByMemberId(memberId); + throw new HttpException( + { + message: '이미 취소되었거나 완료된 주문입니다.', + data: pendingOrders + }, + HttpStatus.BAD_REQUEST + ); + } + + await this.orderBookService.removeOrder(memberId, cropId, orderId, orderType, tradingType); + await this.orderService.cancelOrder(memberId, orderId, cropId, orderType); + const pendingOrders = await this.orderService.getPendingOrdersByMemberId(memberId); + + return { + code: 200, + message: successMessage.DELETE_ORDER_SUCCESS, + data: pendingOrders + }; + } catch (error) { + if (error instanceof HttpException && error.getStatus() === HttpStatus.NOT_FOUND) { + const pendingOrders = await this.orderService.getPendingOrdersByMemberId(memberId); + return { + code: HttpStatus.NOT_FOUND, + message: '주문이 존재하지 않습니다.', + data: pendingOrders + }; + } + throw error; + } } } diff --git a/apps/backend/src/order/order.repository.ts b/apps/backend/src/order/order.repository.ts index 36dca66..9c250a3 100644 --- a/apps/backend/src/order/order.repository.ts +++ b/apps/backend/src/order/order.repository.ts @@ -1,7 +1,7 @@ -import { Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { DatabaseService } from '../database/database.service'; import { OrderDto } from './dto/order.dto'; -import { OrderStatus, TradingType } from './enums/orderType'; +import { OrderStatus, toOrderStatus, TradingType } from './enums/orderType'; import { OrderBookDto } from './dto/orderBook.dto'; import { TransactionDto } from './dto/transaction.dto'; import { Client } from 'pg'; @@ -228,4 +228,20 @@ export class OrderRepository { async runInTransaction(callback: (client: Client) => Promise): Promise { await this.databaseService.runInTransaction(callback); } + + async getOrderStatus(memberId: number, orderId: number): Promise { + const query = ` + SELECT status + FROM orders + WHERE order_id = $1 + AND member_id = $2 + `; + const values = [orderId, memberId]; + const result = await this.databaseService.query(query, values); + + if (result.rows.length === 0) { + throw new HttpException('주문이 존재하지 않습니다.', HttpStatus.NOT_FOUND); + } + return toOrderStatus(result.rows[0]?.status); + } } diff --git a/apps/backend/src/order/order.service.ts b/apps/backend/src/order/order.service.ts index c42fba6..d803802 100644 --- a/apps/backend/src/order/order.service.ts +++ b/apps/backend/src/order/order.service.ts @@ -96,4 +96,8 @@ export class OrderService { async getPendingOrdersByMemberId(memberId: number): Promise { return await this.orderRepository.getPendingOrdersByMemberId(memberId); } + + async getOrderStatus(memberId: number, orderId: number): Promise { + return await this.orderRepository.getOrderStatus(memberId, orderId); + } } From 7a5fba94aa63645cd0aa4bb0c4a88ebb868d955c Mon Sep 17 00:00:00 2001 From: DongKey777 Date: Mon, 2 Dec 2024 11:58:08 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[refactor]=20=EC=A7=84=ED=96=89=EC=A4=91=20?= =?UTF-8?q?=EC=A3=BC=EB=AC=B8=20=EC=A0=95=EB=B3=B4=20=EA=B1=B0=EB=9E=98=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/backend/src/order/dto/pendingOrder.dto.ts | 3 +++ apps/backend/src/order/order.repository.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/apps/backend/src/order/dto/pendingOrder.dto.ts b/apps/backend/src/order/dto/pendingOrder.dto.ts index 237843a..5403f58 100644 --- a/apps/backend/src/order/dto/pendingOrder.dto.ts +++ b/apps/backend/src/order/dto/pendingOrder.dto.ts @@ -10,6 +10,9 @@ export class PendingOrderDto { @ApiProperty({ description: '주문 타입', example: 'buy' }) orderType: string; + @ApiProperty({ description: '거래 타입', example: 'limit' }) + tradingType: string; + @ApiProperty({ description: '주문 가격', example: 100 }) price: number; diff --git a/apps/backend/src/order/order.repository.ts b/apps/backend/src/order/order.repository.ts index 9c250a3..4eba87c 100644 --- a/apps/backend/src/order/order.repository.ts +++ b/apps/backend/src/order/order.repository.ts @@ -178,6 +178,7 @@ export class OrderRepository { orderId: data.order_id, cropId: data.crop_id, orderType: data.order_type, + tradingType: data.trading_type, price: data.price, quantity: data.quantity, filledQuantity: data.filled_quantity,