From c7e8188010929e6bfca2350a71dd897bd0fc7d2d Mon Sep 17 00:00:00 2001 From: Yu-Jia Huang Date: Thu, 20 Jun 2024 22:12:04 +0800 Subject: [PATCH 1/2] fix: date range not required (#249) --- src/dto/group/groupFilterDto.ts | 6 +-- src/routes/groupRoute.ts | 16 +++---- src/service/groupService.ts | 9 ---- src/validator/group/getGroup.pipe.ts | 71 ++++++++++++++++++++++++---- 4 files changed, 70 insertions(+), 32 deletions(-) diff --git a/src/dto/group/groupFilterDto.ts b/src/dto/group/groupFilterDto.ts index ca6ecca..3312b42 100644 --- a/src/dto/group/groupFilterDto.ts +++ b/src/dto/group/groupFilterDto.ts @@ -38,7 +38,7 @@ export class GroupFilterDto { amount: { $eq: this._participantCount }, }), ...(this._haveTicket && { haveTicket: this._haveTicket }), - $or: this._dateRanges, + ...(this._dateRanges.length > 0 && { $or: this._dateRanges }), }; } @@ -61,10 +61,6 @@ export class GroupFilterDto { }; } - get dateRanges() { - return this._dateRanges; - } - constructor(req: IGetGroupsReq) { const { title, diff --git a/src/routes/groupRoute.ts b/src/routes/groupRoute.ts index 415030a..abcff9c 100644 --- a/src/routes/groupRoute.ts +++ b/src/routes/groupRoute.ts @@ -260,29 +260,29 @@ export class GroupRoute extends BaseRoute { } #swagger.parameters['startDate'] = { in: 'query', - required: true, - description: '開始活動日期-起 (一定要遵循以下格式)', + required: false, + description: '開始活動日期-起,一定要遵循以下格式,若要以時段搜尋則 startDate、endDate、startTime、endTime 皆為必填。', type: 'string', example: '2024/01/12' } #swagger.parameters['endDate'] = { in: 'query', - required: true, - description: '開始活動日期-迄 (一定要遵循以下格式)', + required: false, + description: '開始活動日期-迄,一定要遵循以下格式,若要以時段搜尋則 startDate、endDate、startTime、endTime 皆為必填。', type: 'string', example:'2024/07/12' } #swagger.parameters['startTime'] = { in: 'query', - required: true, - description: '開始活動時間-起 (一定要遵循以下格式)', + required: false, + description: '開始活動時間-起,一定要遵循以下格式,若要以時段搜尋則 startDate、endDate、startTime、endTime 皆為必填。', type: 'string', example: '01:00' } #swagger.parameters['endTime'] = { in: 'query', - required: true, - description: '開始活動時間-迄 (一定要遵循以下格式)', + required: false, + description: '開始活動時間-迄,一定要遵循以下格式,若要以時段搜尋則 startDate、endDate、startTime、endTime 皆為必填。', type: 'string', example: '23:00' } diff --git a/src/service/groupService.ts b/src/service/groupService.ts index 9c238f2..e81901b 100644 --- a/src/service/groupService.ts +++ b/src/service/groupService.ts @@ -145,15 +145,6 @@ export class GroupService { } } public async findGroups(groupFilterDto: GroupFilterDto) { - const { dateRanges } = groupFilterDto; - - if (dateRanges.length === 0) { - throwError( - CustomResponseType.INVALID_GROUP_FILTER_MESSAGE + '時間區間有誤', - CustomResponseType.INVALID_GROUP_FILTER, - ); - } - return await this.groupRepository.findGroups(groupFilterDto); } diff --git a/src/validator/group/getGroup.pipe.ts b/src/validator/group/getGroup.pipe.ts index 8e96860..6fd8ab5 100644 --- a/src/validator/group/getGroup.pipe.ts +++ b/src/validator/group/getGroup.pipe.ts @@ -1,12 +1,35 @@ import { PipeBase } from '../pipe.base'; import { query } from 'express-validator'; import { CustomResponseType } from '../../types/customResponseType'; -import { GroupSortField, GroupStatus } from '../../types/group.type'; -import { OptionType } from '../index.type'; +import { + GroupSortField, + GroupStatus, + IGetGroupsReq, +} from '../../types/group.type'; +import { OptionType, TCustomValidator } from '../index.type'; import { SortOrder } from '../../types/common.type'; import { booleanStrings, nullableOption } from '../../utils/constants'; +const timeFormat = /^([01]\d|2[0-3]):([0-5]\d)$/; // HH:mm 格式 +const dateFormat = /^\d{4}\/\d{2}\/\d{2}$/; // YYYY/MM/DD 格式 + export class GetGroupsPipe extends PipeBase { + private validateTimeRange: TCustomValidator = (value, { req }) => { + const { startTime, endTime, startDate, endDate } = (req as IGetGroupsReq) + .query; + + const hasTimeRange = !!startTime && !!endTime && !!startDate && !!endDate; + const hasNoTimeRange = !startTime && !endTime && !startDate && !endDate; + + return hasTimeRange || hasNoTimeRange; + }; + + private validateTimeFormat: TCustomValidator = (value) => + timeFormat.test(value); + + private validateDateFormat: TCustomValidator = (value) => + dateFormat.test(value); + public transform = () => [ this.limitValidation( query('limit'), @@ -33,21 +56,49 @@ export class GetGroupsPipe extends PipeBase { CustomResponseType.INVALID_GROUP_FILTER_MESSAGE + 'participantCount', ), query('startTime') - .exists() + .optional() + .custom(this.validateTimeFormat) + .withMessage( + CustomResponseType.INVALID_GROUP_FILTER_MESSAGE + 'startTime 格式錯誤', + ) + .custom(this.validateTimeRange) .withMessage( - CustomResponseType.INVALID_GROUP_FILTER_MESSAGE + 'startTime', + CustomResponseType.INVALID_GROUP_FILTER_MESSAGE + + '使用時間區段搜尋時,startTime、endTime、startDate、endDate 皆為必填', ), query('endTime') - .exists() - .withMessage(CustomResponseType.INVALID_GROUP_FILTER_MESSAGE + 'endTime'), + .optional() + .custom(this.validateTimeFormat) + .withMessage( + CustomResponseType.INVALID_GROUP_FILTER_MESSAGE + 'endTime 格式錯誤', + ) + .custom(this.validateTimeRange) + .withMessage( + CustomResponseType.INVALID_GROUP_FILTER_MESSAGE + + '使用時間區段搜尋時,startTime、endTime、startDate、endDate 皆為必填', + ), query('startDate') - .exists() + .optional() + .custom(this.validateDateFormat) + .withMessage( + CustomResponseType.INVALID_GROUP_FILTER_MESSAGE + 'startDate 格式錯誤', + ) + .custom(this.validateTimeRange) .withMessage( - CustomResponseType.INVALID_GROUP_FILTER_MESSAGE + 'startDate', + CustomResponseType.INVALID_GROUP_FILTER_MESSAGE + + '使用時間區段搜尋時,startTime、endTime、startDate、endDate 皆為必填', ), query('endDate') - .exists() - .withMessage(CustomResponseType.INVALID_GROUP_FILTER_MESSAGE + 'endDate'), + .optional() + .custom(this.validateDateFormat) + .withMessage( + CustomResponseType.INVALID_GROUP_FILTER_MESSAGE + 'endDate 格式錯誤', + ) + .custom(this.validateTimeRange) + .withMessage( + CustomResponseType.INVALID_GROUP_FILTER_MESSAGE + + '使用時間區段搜尋時,startTime、endTime、startDate、endDate 皆為必填', + ), query('sortField') .optional(nullableOption) .custom(this.validateOption(OptionType.item, GroupSortField)) From 31e70d1357e74166439ec7f154c54d7022da9087 Mon Sep 17 00:00:00 2001 From: Yu-Jia Huang Date: Thu, 20 Jun 2024 22:34:51 +0800 Subject: [PATCH 2/2] fix: empty query string (#249) --- src/dto/group/groupFilterDto.ts | 72 +++++++++++++++------------- src/validator/group/getGroup.pipe.ts | 4 +- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/dto/group/groupFilterDto.ts b/src/dto/group/groupFilterDto.ts index 3312b42..84e1d90 100644 --- a/src/dto/group/groupFilterDto.ts +++ b/src/dto/group/groupFilterDto.ts @@ -6,7 +6,7 @@ import { } from '../../types/group.type'; import { SortOrder } from '../../types/common.type'; -type TDateRange = { +type TTimeRange = { $and: { time: { $gte?: Date; @@ -25,7 +25,7 @@ export class GroupFilterDto { private readonly _page: number; private readonly _limit: number; private readonly _sort: Record; - private readonly _dateRanges: TDateRange[] = []; + private readonly _timeRanges: TTimeRange[] = []; get filter() { const titleRegex = this._title ? new RegExp(this._title) : undefined; @@ -38,7 +38,7 @@ export class GroupFilterDto { amount: { $eq: this._participantCount }, }), ...(this._haveTicket && { haveTicket: this._haveTicket }), - ...(this._dateRanges.length > 0 && { $or: this._dateRanges }), + ...(this._timeRanges.length > 0 && { $or: this._timeRanges }), }; } @@ -95,40 +95,44 @@ export class GroupFilterDto { // date range - const dateFrom = moment(startDate, 'YYYY/MM/DD').startOf('day'); - const dateTo = moment(endDate, 'YYYY/MM/DD').endOf('day'); - const timeFrom = moment(startTime, 'HH:mm'); - const timeTo = moment(endTime, 'HH:mm'); + const hasTimeRange = !!startDate && !!endDate && !!startTime && !!endTime; - const dateRanges: TDateRange[] = []; - for ( - let date = dateFrom.clone(); - date.isSameOrBefore(dateTo); - date.add(1, 'days') - ) { - const start = { - hour: timeFrom.hours(), - minute: timeFrom.minutes(), - second: 0, - millisecond: 0, - }; - const end = { - hour: timeTo.hours(), - minute: timeTo.minutes(), - second: 0, - millisecond: 0, - }; + if (hasTimeRange) { + const dateFrom = moment(startDate, 'YYYY/MM/DD').startOf('day'); + const dateTo = moment(endDate, 'YYYY/MM/DD').endOf('day'); + const timeFrom = moment(startTime, 'HH:mm'); + const timeTo = moment(endTime, 'HH:mm'); - const dateRange = { - $and: [ - { time: { $gte: new Date(date.clone().set(start).toISOString()) } }, - { time: { $lte: new Date(date.clone().set(end).toISOString()) } }, - ], - }; + const dateRanges: TTimeRange[] = []; + for ( + let date = dateFrom.clone(); + date.isSameOrBefore(dateTo); + date.add(1, 'days') + ) { + const start = { + hour: timeFrom.hours(), + minute: timeFrom.minutes(), + second: 0, + millisecond: 0, + }; + const end = { + hour: timeTo.hours(), + minute: timeTo.minutes(), + second: 0, + millisecond: 0, + }; - dateRanges.push(dateRange); - } + const dateRange = { + $and: [ + { time: { $gte: new Date(date.clone().set(start).toISOString()) } }, + { time: { $lte: new Date(date.clone().set(end).toISOString()) } }, + ], + }; + + dateRanges.push(dateRange); + } - this._dateRanges = dateRanges; + this._timeRanges = dateRanges; + } } } diff --git a/src/validator/group/getGroup.pipe.ts b/src/validator/group/getGroup.pipe.ts index 6fd8ab5..a1a2188 100644 --- a/src/validator/group/getGroup.pipe.ts +++ b/src/validator/group/getGroup.pipe.ts @@ -25,10 +25,10 @@ export class GetGroupsPipe extends PipeBase { }; private validateTimeFormat: TCustomValidator = (value) => - timeFormat.test(value); + timeFormat.test(value) || !value; private validateDateFormat: TCustomValidator = (value) => - dateFormat.test(value); + dateFormat.test(value) || !value; public transform = () => [ this.limitValidation(