From 4f8b48cfd2acc03addc155906a3521af6ed8ca79 Mon Sep 17 00:00:00 2001 From: RogerLi Date: Sun, 16 Jun 2024 14:18:58 +0800 Subject: [PATCH 1/3] fix: modify sorted field --- src/controller/ticketController.ts | 8 ++- src/dto/ticket/getSharedTicketsDto.ts | 57 ++++++++++++++++ src/repository/ticketRepository.ts | 5 +- src/routes/ticketRoute.ts | 28 +++++--- src/routes/userRoute.ts | 2 +- src/service/ticketService.ts | 7 +- src/types/ticket.type.ts | 4 ++ .../aggregate/ticket/getTickets.pipeline.ts | 3 +- src/validator/ticket/getTicket.pipe.ts | 66 ------------------- 9 files changed, 97 insertions(+), 83 deletions(-) create mode 100644 src/dto/ticket/getSharedTicketsDto.ts diff --git a/src/controller/ticketController.ts b/src/controller/ticketController.ts index 551b59d..9b861f2 100644 --- a/src/controller/ticketController.ts +++ b/src/controller/ticketController.ts @@ -19,6 +19,7 @@ import { EditTicketsDTO } from '../dto/ticket/editTicketsDto'; import { CreateShareCodeDTO } from '../dto/ticket/createShareCodeDto'; import { TransferTicketDTO } from '../dto/ticket/transferTicketDto'; import { GetTicketDetailDto } from '../dto/ticket/getTicketDetailDto'; +import { GetSharedTicketsDto } from '../dto/ticket/getSharedTicketsDto'; export class TicketController extends BaseController { private readonly ticketService = new TicketService(); @@ -47,9 +48,10 @@ export class TicketController extends BaseController { }; public getSharedTickets: TMethod = async (req) => { - const getTicketsDto = new GetTicketsDto(req); - const { page, limit } = getTicketsDto; - const info = await this.ticketService.findSharedTickets(getTicketsDto); + const getSharedTicketsDto = new GetSharedTicketsDto(req); + const { page, limit } = getSharedTicketsDto; + const info = + await this.ticketService.findSharedTickets(getSharedTicketsDto); return this.formatResponse( CustomResponseType.OK_MESSAGE, CustomResponseType.OK, diff --git a/src/dto/ticket/getSharedTicketsDto.ts b/src/dto/ticket/getSharedTicketsDto.ts new file mode 100644 index 0000000..7346df9 --- /dev/null +++ b/src/dto/ticket/getSharedTicketsDto.ts @@ -0,0 +1,57 @@ +import { IGetTicketsReq, SharedTicketSortField } from '../../types/ticket.type'; +import { SortOrder } from '../../types/common.type'; + +export class GetSharedTicketsDto { + private readonly _page: number; + private readonly _limit: number; + private readonly _sort: Record; + get page() { + return this._page; + } + + get limit() { + return this._limit; + } + + get sort() { + return this._sort; + } + + get options() { + const productSelect = { + title: 1, + photoPath: 1, + price: 1, + theater: 1, + startAt: 1, + }; + const ticketSelect = { + _id: 1, + productId: 1, + userId: 1, + orderId: 1, + status: 1, + isPublished: 1, + expiredAt: 1, + createdAt: 1, + updatedAt: 1, + product: 1, + }; + return { + productSelect, + ticketSelect, + page: this._page, + limit: this._limit, + sort: this._sort, + }; + } + constructor(req: IGetTicketsReq) { + const { page, limit, sortField, sortOrder } = req.query; + this._sort = { + [`${sortField || SharedTicketSortField.updatedAt}`]: + sortOrder === SortOrder.desc ? 1 : -1, + }; + this._limit = Number(limit); + this._page = Number(page); + } +} diff --git a/src/repository/ticketRepository.ts b/src/repository/ticketRepository.ts index bbf68e1..d73dd65 100644 --- a/src/repository/ticketRepository.ts +++ b/src/repository/ticketRepository.ts @@ -23,6 +23,7 @@ import { import { GetTicketDetailDto } from '../dto/ticket/getTicketDetailDto'; import { createGetTicketDetailPipeline } from '../utils/aggregate/ticket/getTicketDetail.pipeline'; import { SellTicketDto } from '../dto/ticket/sellTicketDto'; +import { GetSharedTicketsDto } from '../dto/ticket/getSharedTicketsDto'; export class TicketRepository { public async createTicket(createTicketDto: CreateTicketDto) { @@ -37,9 +38,9 @@ export class TicketRepository { return results[0]; }; public findSharedTickets = async ( - ticketFilterDto: GetTicketsDto, + getSharedTicketsDto: GetSharedTicketsDto, ): Promise => { - const pipeline = createGetSharedTicketPipeline(ticketFilterDto); + const pipeline = createGetSharedTicketPipeline(getSharedTicketsDto); const results = await TicketModel.aggregate(pipeline); return results[0]; }; diff --git a/src/routes/ticketRoute.ts b/src/routes/ticketRoute.ts index 18223ee..a9353e3 100644 --- a/src/routes/ticketRoute.ts +++ b/src/routes/ticketRoute.ts @@ -177,25 +177,37 @@ export class TicketRoute extends BaseRoute { '/v1/ticket-shared', /** * #swagger.tags = ['Ticket'] - * #swagger.summary = '取得票券詳細資料' - * #swagger.security=[{"Bearer": []}], + * #swagger.summary = '取得已上架分票票券' */ /* - #swagger.parameters['id'] = { - in: 'path', - description: '票券 id', - example: 'abcdefg123124', + #swagger.parameters['page'] = { + in: 'query', + required: true, + description: '頁數', + type: 'number', + schema:{ + $ref: "#/definitions/CustomPageQuery" } + } + #swagger.parameters['limit'] = { + in: 'query', + required: false, + description: '每頁資料數', + type: 'number', + schema:{ + $ref: "#/definitions/CustomLimitQuery" + } + } */ /* #swagger.responses[200] = { description:'OK', schema:{ - $ref: "#/definitions/GetTicketDetailSuccess" + $ref: "#/definitions/GetSharedTicketSuccess" } } */ - UserVerify, + this.usePipe(GetTicketPipe), this.responseHandler(this.controller.getSharedTickets), ); diff --git a/src/routes/userRoute.ts b/src/routes/userRoute.ts index 5248909..26f68a4 100644 --- a/src/routes/userRoute.ts +++ b/src/routes/userRoute.ts @@ -54,7 +54,7 @@ export class UserRoute extends BaseRoute { "gender": "none", "phone": "0912345678", "address": "aaaabbb", - "avatarPath": "avatarPath" + "imgUrl": "imgimg" } } */ diff --git a/src/service/ticketService.ts b/src/service/ticketService.ts index bded439..9497435 100644 --- a/src/service/ticketService.ts +++ b/src/service/ticketService.ts @@ -14,6 +14,7 @@ import { Types } from 'mongoose'; import * as crypto from 'node:crypto'; import { TransferTicketDTO } from '../dto/ticket/transferTicketDto'; import { GetTicketDetailDto } from '../dto/ticket/getTicketDetailDto'; +import { GetSharedTicketsDto } from '../dto/ticket/getSharedTicketsDto'; const logger = log4js.getLogger(`TicketService`); @@ -34,8 +35,10 @@ export class TicketService { return await this.ticketRepository.findTickets(ticketFilterDto); }; - public findSharedTickets = async (ticketFilterDto: GetTicketsDto) => { - return await this.ticketRepository.findSharedTickets(ticketFilterDto); + public findSharedTickets = async ( + getSharedTicketsDto: GetSharedTicketsDto, + ) => { + return await this.ticketRepository.findSharedTickets(getSharedTicketsDto); }; public async createTickets(order: IOrder) { diff --git a/src/types/ticket.type.ts b/src/types/ticket.type.ts index a1ee55a..df2613e 100644 --- a/src/types/ticket.type.ts +++ b/src/types/ticket.type.ts @@ -64,6 +64,10 @@ export enum TicketSortField { status = 'status', } +export enum SharedTicketSortField { + updatedAt = 'updatedAt', +} + export interface IVerifyTicketsReq extends IUserReq { body: { tickets: { diff --git a/src/utils/aggregate/ticket/getTickets.pipeline.ts b/src/utils/aggregate/ticket/getTickets.pipeline.ts index 4d60e5f..86ac3bf 100644 --- a/src/utils/aggregate/ticket/getTickets.pipeline.ts +++ b/src/utils/aggregate/ticket/getTickets.pipeline.ts @@ -1,4 +1,5 @@ import { GetTicketsDto } from '../../../dto/ticket/getTicketsDto'; +import { GetSharedTicketsDto } from '../../../dto/ticket/getSharedTicketsDto'; export const createGetTicketPipeline = ({ productNameRegex, @@ -73,7 +74,7 @@ export const createGetSharedTicketPipeline = ({ sort, limit, page, -}: GetTicketsDto) => { +}: GetSharedTicketsDto) => { return [ { $match: { diff --git a/src/validator/ticket/getTicket.pipe.ts b/src/validator/ticket/getTicket.pipe.ts index 5419860..569d329 100644 --- a/src/validator/ticket/getTicket.pipe.ts +++ b/src/validator/ticket/getTicket.pipe.ts @@ -11,16 +11,6 @@ import { SortOrder } from '../../types/common.type'; import { booleanStrings } from '../../utils/constants'; export class GetTicketPipe extends PipeBase { - private validateExpiredAtFrom: TCustomValidator = (value, { req }) => { - const { expiredAtTo } = (req as IGetTicketsReq).query; - return this.validatePeriod(value, expiredAtTo, (a, b) => b.isAfter(a)); - }; - - private validateExpiredAtTo: TCustomValidator = (value, { req }) => { - const { expiredAtFrom } = (req as IGetTicketsReq).query; - return this.validatePeriod(value, expiredAtFrom, (a, b) => a.isBefore(b)); - }; - public transform = () => [ this.limitValidation( query('limit'), @@ -30,62 +20,6 @@ export class GetTicketPipe extends PipeBase { query('page'), CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'page', ), - query('ids') - .custom(this.isAdminOnly) - .withMessage( - CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + - CustomResponseType.PERMISSION_DENIED_MESSAGE + - 'ids', - ), - query('productName') - .custom(this.isAdminOnly) - .withMessage( - CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + - CustomResponseType.PERMISSION_DENIED_MESSAGE + - 'productName', - ), - query('expiredAtFrom') - .optional() - .custom(this.validateDate) - .withMessage( - CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'expiredAtFrom', - ) - .custom(this.validateExpiredAtFrom) - .withMessage( - CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'expiredAtFrom', - ), - query('expiredAtTo') - .optional() - .custom(this.validateDate) - .withMessage( - CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'expiredAtTo', - ) - .custom(this.validateExpiredAtTo) - .withMessage( - CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'expiredAtTo', - ), - query('isPublished') - .optional() - .isIn(booleanStrings) - .withMessage( - CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'isPublished', - ), - query('sortField') - .optional() - .custom(this.validateOption(OptionType.item, TicketSortField)) - .withMessage( - CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'sortField', - ), - query('sortOrder') - .optional() - .custom(this.validateOption(OptionType.item, SortOrder)) - .withMessage( - CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'sortOrder', - ), - query('status') - .optional() - .isIn(Object.keys(TicketStatus)) - .withMessage(CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'status'), this.validationHandler, ]; From a48a6d8ef940b1cc461a398da37f6629a4bd7710 Mon Sep 17 00:00:00 2001 From: RogerLi Date: Sun, 16 Jun 2024 14:21:33 +0800 Subject: [PATCH 2/3] fix: modify sorted field --- src/routes/ticketRoute.ts | 3 +- src/validator/ticket/getSharedTicket.pipe.ts | 21 +++++++ src/validator/ticket/getTicket.pipe.ts | 66 ++++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/validator/ticket/getSharedTicket.pipe.ts diff --git a/src/routes/ticketRoute.ts b/src/routes/ticketRoute.ts index a9353e3..a434134 100644 --- a/src/routes/ticketRoute.ts +++ b/src/routes/ticketRoute.ts @@ -9,6 +9,7 @@ import { TransferTicketPipe } from '../validator/ticket/TransferTicket.pipe'; import { ClaimTransferTicketPipe } from '../validator/ticket/claimTransferTicket.pipe'; import { DeleteTicketsPipe } from '../validator/ticket/deleteTickets.pipe'; import { GetTicketDetailPipe } from '../validator/ticket/getTicketDetail.pipe'; +import { GetSharedTicketPipe } from '../validator/ticket/getSharedTicket.pipe'; export class TicketRoute extends BaseRoute { protected controller!: TicketController; @@ -207,7 +208,7 @@ export class TicketRoute extends BaseRoute { } } */ - this.usePipe(GetTicketPipe), + this.usePipe(GetSharedTicketPipe), this.responseHandler(this.controller.getSharedTickets), ); diff --git a/src/validator/ticket/getSharedTicket.pipe.ts b/src/validator/ticket/getSharedTicket.pipe.ts new file mode 100644 index 0000000..9bf2a51 --- /dev/null +++ b/src/validator/ticket/getSharedTicket.pipe.ts @@ -0,0 +1,21 @@ +import { PipeBase } from '../pipe.base'; +import { query } from 'express-validator'; +import { CustomResponseType } from '../../types/customResponseType'; + +export class GetSharedTicketPipe extends PipeBase { + public transform = () => [ + this.limitValidation( + query('limit'), + CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'limit', + ), + this.positiveIntValidation( + query('page'), + CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'page', + ), + this.validationHandler, + ]; + + constructor() { + super(); + } +} diff --git a/src/validator/ticket/getTicket.pipe.ts b/src/validator/ticket/getTicket.pipe.ts index 569d329..5419860 100644 --- a/src/validator/ticket/getTicket.pipe.ts +++ b/src/validator/ticket/getTicket.pipe.ts @@ -11,6 +11,16 @@ import { SortOrder } from '../../types/common.type'; import { booleanStrings } from '../../utils/constants'; export class GetTicketPipe extends PipeBase { + private validateExpiredAtFrom: TCustomValidator = (value, { req }) => { + const { expiredAtTo } = (req as IGetTicketsReq).query; + return this.validatePeriod(value, expiredAtTo, (a, b) => b.isAfter(a)); + }; + + private validateExpiredAtTo: TCustomValidator = (value, { req }) => { + const { expiredAtFrom } = (req as IGetTicketsReq).query; + return this.validatePeriod(value, expiredAtFrom, (a, b) => a.isBefore(b)); + }; + public transform = () => [ this.limitValidation( query('limit'), @@ -20,6 +30,62 @@ export class GetTicketPipe extends PipeBase { query('page'), CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'page', ), + query('ids') + .custom(this.isAdminOnly) + .withMessage( + CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + + CustomResponseType.PERMISSION_DENIED_MESSAGE + + 'ids', + ), + query('productName') + .custom(this.isAdminOnly) + .withMessage( + CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + + CustomResponseType.PERMISSION_DENIED_MESSAGE + + 'productName', + ), + query('expiredAtFrom') + .optional() + .custom(this.validateDate) + .withMessage( + CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'expiredAtFrom', + ) + .custom(this.validateExpiredAtFrom) + .withMessage( + CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'expiredAtFrom', + ), + query('expiredAtTo') + .optional() + .custom(this.validateDate) + .withMessage( + CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'expiredAtTo', + ) + .custom(this.validateExpiredAtTo) + .withMessage( + CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'expiredAtTo', + ), + query('isPublished') + .optional() + .isIn(booleanStrings) + .withMessage( + CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'isPublished', + ), + query('sortField') + .optional() + .custom(this.validateOption(OptionType.item, TicketSortField)) + .withMessage( + CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'sortField', + ), + query('sortOrder') + .optional() + .custom(this.validateOption(OptionType.item, SortOrder)) + .withMessage( + CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'sortOrder', + ), + query('status') + .optional() + .isIn(Object.keys(TicketStatus)) + .withMessage(CustomResponseType.INVALID_TICKET_FILTER_MESSAGE + 'status'), this.validationHandler, ]; From e7f578ff79171e3ad8c920a3b2ab29e57c7dd9ba Mon Sep 17 00:00:00 2001 From: RogerLi Date: Sun, 16 Jun 2024 14:26:58 +0800 Subject: [PATCH 3/3] doc: update swagger --- src/routes/ticketRoute.ts | 2 +- src/swagger/definition/ticket/general.ts | 26 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/routes/ticketRoute.ts b/src/routes/ticketRoute.ts index a434134..8106516 100644 --- a/src/routes/ticketRoute.ts +++ b/src/routes/ticketRoute.ts @@ -204,7 +204,7 @@ export class TicketRoute extends BaseRoute { #swagger.responses[200] = { description:'OK', schema:{ - $ref: "#/definitions/GetSharedTicketSuccess" + $ref: "#/definitions/GetSharedTicketsSuccess" } } */ diff --git a/src/swagger/definition/ticket/general.ts b/src/swagger/definition/ticket/general.ts index e0817ab..ad66fcf 100644 --- a/src/swagger/definition/ticket/general.ts +++ b/src/swagger/definition/ticket/general.ts @@ -31,6 +31,18 @@ export const Ticket = { writeOffStaffId: 'rrr', giverId: 'iiddidididi', }; + +export const SharedTicket = { + $_id: 'asdfasdfasd', + $productId: 'cdscsdcsdc', + $userId: '123235564364567', + $orderId: 'iiddidididi', + $status: TicketStatus.unverified, + $isPublished: false, + $createdAt: new Date().toISOString(), + $updatedAt: new Date().toISOString(), + $expiredAt: new Date().toISOString(), +}; /** * @description swagger autogen 可以自動生成,通常用於 response 的 general 資料 */ @@ -61,6 +73,20 @@ export const GetTicketsSuccess = { ...PaginationSuccess, }, }; +export const GetSharedTicketsSuccess = { + $status: CustomResponseType.OK, + $message: CustomResponseType.OK_MESSAGE, + $data: { + tickets: [ + { + ...SharedTicket, + product: TicketProductDetail, + count: 2, + }, + ], + ...PaginationSuccess, + }, +}; export const GetTicketDetailSuccess = { $status: CustomResponseType.OK,