diff --git a/src/common/dtos/page/page-options.dto.ts b/src/common/dtos/page/page-options.dto.ts index 4c06a3c..b3a11d1 100644 --- a/src/common/dtos/page/page-options.dto.ts +++ b/src/common/dtos/page/page-options.dto.ts @@ -17,7 +17,6 @@ export class PageOptionsDto { @Type(() => Number) @IsInt() @Min(1) - @IsOptional() @Expose() readonly page: number = 1; @@ -30,7 +29,6 @@ export class PageOptionsDto { @IsInt() @Min(1) @Max(50) - @IsOptional() @Expose() readonly take: number = 10; diff --git a/src/common/dtos/paging.dto.ts b/src/common/dtos/paging.dto.ts deleted file mode 100644 index b67d37b..0000000 --- a/src/common/dtos/paging.dto.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsNumber, Min } from 'class-validator'; - -export class PagingDto { - @ApiProperty({ - minimum: 1, - default: 1, - description: '페이지 (최소값:1)' - }) - @IsNumber() - @Type(() => Number) - @Min(1) - page: number; - - @ApiProperty({ - minimum: 1, - default: 10, - description: '페이지의 게시물 갯수 (최소값:1)' - }) - @Type(() => Number) - @IsNumber() - @Min(1) - size: number; -} diff --git a/src/database/repositories/ticket.repository.ts b/src/database/repositories/ticket.repository.ts index 646434e..3134155 100644 --- a/src/database/repositories/ticket.repository.ts +++ b/src/database/repositories/ticket.repository.ts @@ -1,14 +1,9 @@ -import { - Injectable, - NotFoundException, - UnauthorizedException -} from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Role, TicketStatus } from 'src/common/consts/enum'; +import { TicketStatus } from 'src/common/consts/enum'; import { PageMetaDto } from 'src/common/dtos/page/page-meta.dto'; import { PageOptionsDto } from 'src/common/dtos/page/page-options.dto'; import { PageDto } from 'src/common/dtos/page/page.dto'; -import { PagingDto } from 'src/common/dtos/paging.dto'; import { EnterReportDto } from 'src/orders/dtos/enter-report.dto'; import { TicketReportDto } from 'src/orders/dtos/ticket-report.dto'; import { CreateTicketDto } from 'src/tickets/dtos/create-ticket.dto'; @@ -16,7 +11,6 @@ import { TicketFindDto } from 'src/tickets/dtos/ticket-find.dto'; import { Repository } from 'typeorm'; import { Ticket } from '../entities/ticket.entity'; -import { User } from '../entities/user.entity'; @Injectable() export class TicketRepository { @@ -107,11 +101,9 @@ export class TicketRepository { } queryBuilder - .orderBy('ticket.createdAt', pageOptionsDto.order) - .leftJoin('ticket.user', 'user') - .addSelect(['user.name', 'user.phoneNumber']) - .leftJoin('ticket.admin', 'admin') - .addSelect(['admin.name']) + .orderBy('ticket.id', pageOptionsDto.order) + .leftJoinAndSelect('ticket.user', 'user') + .leftJoinAndSelect('ticket.admin', 'admin') .skip(pageOptionsDto.skip) .take(pageOptionsDto.take); diff --git a/src/tickets/dtos/ticket-on-socket.dto.ts b/src/tickets/dtos/ticket-on-socket.dto.ts deleted file mode 100644 index 7981b35..0000000 --- a/src/tickets/dtos/ticket-on-socket.dto.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { PickType, PartialType } from '@nestjs/swagger'; -import { Ticket } from 'src/database/entities/ticket.entity'; - -export class TicketOnSocketDto extends PickType(PartialType(Ticket), [ - 'uuid', - 'date', - 'status' -] as const) {} diff --git a/src/tickets/tickets.controller.ts b/src/tickets/tickets.controller.ts index 098c7b7..5c30efa 100644 --- a/src/tickets/tickets.controller.ts +++ b/src/tickets/tickets.controller.ts @@ -1,13 +1,16 @@ import { + BadRequestException, Body, Controller, Delete, Get, HttpStatus, + NotFoundException, Param, Patch, Post, Query, + UnauthorizedException, UseGuards } from '@nestjs/common'; import { @@ -19,16 +22,13 @@ import { ApiUnauthorizedResponse } from '@nestjs/swagger'; import { AccessTokenGuard } from 'src/auth/guards/AccessToken.guard'; -import { PerformanceDate, Role } from 'src/common/consts/enum'; +import { Role } from 'src/common/consts/enum'; import { Roles } from 'src/common/decorators/roles.decorator'; -import { ApiPaginatedDto } from 'src/common/decorators/ApiPaginatedDto.decorator'; import { ReqUser } from 'src/common/decorators/user.decorator'; import { PageOptionsDto } from 'src/common/dtos/page/page-options.dto'; import { TicketEntryDateValidationDto } from 'src/tickets/dtos/ticket-entry-date-validation.dto copy'; -import { Order } from 'src/database/entities/order.entity'; import { Ticket } from 'src/database/entities/ticket.entity'; import { User } from 'src/database/entities/user.entity'; -import { UsersService } from 'src/users/users.service'; import { TicketsService } from './tickets.service'; import { TicketFindDto } from './dtos/ticket-find.dto'; import { UpdateTicketStatusDto } from './dtos/update-ticket-status.dto'; @@ -36,16 +36,14 @@ import { SuccessResponse } from 'src/common/decorators/SuccessResponse.decorator import { PageDto } from 'src/common/dtos/page/page.dto'; import { NoAuth } from 'src/auth/guards/NoAuth.guard'; import { TicketCountDto } from './dtos/ticket-count.dto'; +import { ErrorResponse } from 'src/common/decorators/ErrorResponse.decorator'; @ApiTags('tickets') @ApiBearerAuth('accessToken') @Controller('tickets') @UseGuards(AccessTokenGuard) export class TicketsController { - constructor( - private ticketService: TicketsService, - private usersService: UsersService //삭제예정 - ) {} + constructor(private ticketService: TicketsService) {} //실제 사용 // @Get() @@ -71,25 +69,6 @@ export class TicketsController { return this.ticketService.findAllByUserId(user.id); } - /* 테스트용 라우팅 */ - @ApiOperation({ - summary: '[테스트용, 삭제예정]조건없이 모든 티켓을 불러온다' - }) - @ApiResponse({ - status: 200, - description: '요청 성공시', - type: Ticket - }) - @ApiUnauthorizedResponse({ - status: 401, - description: 'AccessToken이 없거나 어드민이 아닐 경우' - }) - @Get('all') - @Roles(Role.Admin) - getAllTickets() { - return this.ticketService.findAll(); - } - @ApiOperation({ summary: '[어드민]해당 조건의 티켓을 모두 불러온다, querystring으로 전달' }) @@ -125,24 +104,6 @@ export class TicketsController { return this.ticketService.findAllWith(ticketFindDto, pageOptionsDto); } - @ApiOperation({ summary: '[테스트용] 임시 티켓 생성' }) - @ApiResponse({ - status: 200, - description: '요청 성공시', - type: Ticket - }) - @Post('/create') - async testCreateTicket(@ReqUser() user: User) { - const createTicketDto = { - date: PerformanceDate.YB, - order: new Order(), - user: user, - createdAt: new Date(), - updatedAt: new Date() - }; - return this.ticketService.createTicket(createTicketDto); - } - @ApiOperation({ summary: '[랜딩페이지] 티켓 개수를 반환한다' }) @@ -158,8 +119,48 @@ export class TicketsController { return { count: count }; } + @ErrorResponse(HttpStatus.BAD_REQUEST, [ + { + model: BadRequestException, + exampleDescription: 'Body 파라미터 검증 오류입니다', + exampleTitle: 'status:400 BadRequestException', + message: '검증 오류' + } + ]) + @ErrorResponse(HttpStatus.UNAUTHORIZED, [ + { + model: UnauthorizedException, + exampleDescription: '권한 없는 유저가 접근했을때 생기는 오류입니다', + exampleTitle: 'status:401 UnauthorizedException', + message: '잘못된 헤더 요청' + } + ]) + @ErrorResponse(HttpStatus.NOT_FOUND, [ + { + model: NotFoundException, + exampleDescription: 'Ticket Id 입력 오류입니다', + exampleTitle: 'status:404 NotFoundException', + message: "Can't find Ticket with id {ticketId}" + } + ]) + @ApiOperation({ summary: '[어드민] 티켓 하나의 status를 변경한다' }) + @ApiBody({ type: UpdateTicketStatusDto }) + @ApiResponse({ + status: 200, + description: '요청 성공시', + type: Ticket + }) + @Roles(Role.Admin) + @Patch('/status') + updateTicketStatus( + @Body('') updateTicketStatusDto: UpdateTicketStatusDto, + @ReqUser() user: User + ) { + return this.ticketService.updateTicketStatus(updateTicketStatusDto, user); + } + @ApiOperation({ - summary: '해당 uuid를 포함하는 티켓을 가져온다, req.user 필요' + summary: '해당 uuid를 포함하는 자신의 티켓을 가져온다' }) @ApiResponse({ status: 200, @@ -181,7 +182,7 @@ export class TicketsController { } @ApiOperation({ - summary: '[어드민] 티켓 QR코드 찍었을 때 uuid를 받아 소켓 이벤트를 전송' + summary: '[어드민] 티켓 QR코드 찍었을 때 uuid를 받아 소켓 이벤트를 전송한다' }) @ApiBody({ type: TicketEntryDateValidationDto }) @ApiResponse({ @@ -207,26 +208,6 @@ export class TicketsController { ); } - @ApiOperation({ summary: '[어드민] 티켓 하나의 status를 변경한다' }) - @ApiBody({ type: UpdateTicketStatusDto }) - @ApiResponse({ - status: 200, - description: '요청 성공시', - type: Ticket - }) - @ApiUnauthorizedResponse({ - status: 401, - description: '어드민이 아닐 경우' - }) - @Roles(Role.Admin) - @Patch('/status') - updateTicketStatus( - @Body('') updateTicketStatusDto: UpdateTicketStatusDto, - @ReqUser() user: User - ) { - return this.ticketService.updateTicketStatus(updateTicketStatusDto, user); - } - @ApiOperation({ summary: '[어드민] 해당 id의 티켓을 제거한다' }) @ApiResponse({ status: 200, @@ -243,9 +224,47 @@ export class TicketsController { return this.ticketService.deleteTicketByUuid(ticketUuid); } - @ApiOperation({ summary: '[테스트용] 티켓 모두 제거' }) - @Delete('/deleteAll') - deleteAllTickets() { - return this.ticketService.deleteAllTickets(); - } + + // /* 테스트용 라우팅 */ + // @ApiOperation({ + // summary: '[테스트용, 삭제예정]조건없이 모든 티켓을 불러온다' + // }) + // @ApiResponse({ + // status: 200, + // description: '요청 성공시', + // type: Ticket + // }) + // @ApiUnauthorizedResponse({ + // status: 401, + // description: 'AccessToken이 없거나 어드민이 아닐 경우' + // }) + // @Get('all') + // @Roles(Role.Admin) + // getAllTickets() { + // return this.ticketService.findAll(); + // } + + // @ApiOperation({ summary: '[테스트용] 임시 티켓 생성' }) + // @ApiResponse({ + // status: 200, + // description: '요청 성공시', + // type: Ticket + // }) + // @Post('/create') + // async testCreateTicket(@ReqUser() user: User) { + // const createTicketDto = { + // date: PerformanceDate.YB, + // order: new Order(), + // user: user, + // createdAt: new Date(), + // updatedAt: new Date() + // }; + // return this.ticketService.createTicket(createTicketDto); + // } + + // @ApiOperation({ summary: '[테스트용] 티켓 모두 제거' }) + // @Delete('/deleteAll') + // deleteAllTickets() { + // return this.ticketService.deleteAllTickets(); + // } } diff --git a/src/tickets/tickets.service.ts b/src/tickets/tickets.service.ts index 3d58b4b..7030648 100644 --- a/src/tickets/tickets.service.ts +++ b/src/tickets/tickets.service.ts @@ -1,7 +1,5 @@ import { BadRequestException, - forwardRef, - Inject, Injectable, InternalServerErrorException, Logger, @@ -16,7 +14,6 @@ import { Ticket } from 'src/database/entities/ticket.entity'; import { User } from 'src/database/entities/user.entity'; import { TicketRepository } from 'src/database/repositories/ticket.repository'; import { SocketService } from 'src/socket/socket.service'; -import { UserRepository } from 'src/database/repositories/user.repository'; import { QueueService } from 'src/queue/queue.service'; import { DataSource } from 'typeorm'; import { CreateTicketDto } from './dtos/create-ticket.dto'; @@ -118,29 +115,32 @@ export class TicketsService { try { const { date } = ticketEntryDateValidationDto; const ticket = await connectedRepository.findByUuid(uuid); + const response = new TicketEntryResponseDto(ticket, admin.name, false); + /** Enum 받아서 입장 실패 메세지 가져오는 람다식 */ + const getFailureMessage = (status: TicketStatus) => { + if (status == TicketStatus.DONE) { + return '이미 입장 완료된 티켓입니다'; + } else if (status == TicketStatus.EXPIRE) { + return '입금 기한이 만료된 티켓입니다'; + } else if (status == TicketStatus.ORDERWAIT) { + return '입금 대기중인 티켓입니다'; + } + return '검증 오류'; + }; // 티켓 날짜 오류(공연 날짜가 일치하지 않음) if (ticket.date !== date) { - const failureResponse = new TicketEntryResponseDto( - ticket, - admin.name, - false, - '[입장실패] 공연 날짜가 일치하지 않습니다' - ); - this.socketService.emitToAll(failureResponse); + response.message = '[입장실패] 공연 날짜가 일치하지 않습니다'; + this.socketService.emitToAll(response); throw new BadRequestException('공연 날짜가 일치하지 않습니다'); } // 티켓 상태 오류('입장대기'가 아님) if (ticket.status !== TicketStatus.ENTERWAIT) { - const failureResponse = new TicketEntryResponseDto( - ticket, - admin.name, - false, - '[입장실패] 이미 입장 완료된 티켓입니다' - ); - this.socketService.emitToAll(failureResponse); - throw new BadRequestException('이미 입장 완료된 티켓입니다'); + + response.message = '[입장실패]' + getFailureMessage(ticket.status); + this.socketService.emitToAll(response); + throw new BadRequestException(getFailureMessage(ticket.status)); } //성공 시 @@ -149,17 +149,12 @@ export class TicketsService { await connectedRepository.saveTicket(ticket); - await queryRunner.commitTransaction(); - - const successResponse = new TicketEntryResponseDto( - ticket, - admin.name, - true, - `[입장성공] ${ticket.user?.name}님이 입장하셨습니다` - ); + response.message = `[입장성공] ${ticket.user?.name}님이 입장하셨습니다`; this.logger.log(`${ticket.user?.name}님이 입장하셨습니다`); - this.socketService.emitToAll(successResponse); - return successResponse; + this.socketService.emitToAll(response); + + await queryRunner.commitTransaction(); + return response; } catch (e) { await queryRunner.rollbackTransaction(); this.logger.error(`티켓 상태 오류 - ${e.message}`); @@ -204,11 +199,8 @@ export class TicketsService { return ticket; } catch (e) { await queryRunner.rollbackTransaction(); - //티켓 찾을때 Not Found Error 캐치 - if (e) { - throw e; - } - throw new InternalServerErrorException('Ticket db 에러'); + this.logger.error(`티켓 status 변경 오류 - ${e.message}`); + throw e; } finally { await queryRunner.release(); }