From 2f15ae9929d9ef32436e95e78c66821ede771c9a Mon Sep 17 00:00:00 2001 From: RogerLi Date: Sat, 11 May 2024 18:00:44 +0800 Subject: [PATCH 1/3] feat: add create group function doc: dockerfile add timezone setting and swagger command --- Dockerfile | 3 + src/app.ts | 2 +- src/controller/baseController.ts | 12 +- src/controller/groupController.ts | 26 + src/controller/indexController.ts | 11 - src/dto/createGroupDto.ts | 56 ++ src/models/baseModel.ts | 5 + src/models/group.ts | 7 +- src/repository/groupRepository.ts | 19 + src/routes/groupRoute.ts | 65 ++ src/routes/router.ts | 2 + src/service/groupService.ts | 23 + src/swagger-output.json | 1460 ----------------------------- src/swagger/definition/group.ts | 71 ++ src/swagger/definition/index.ts | 3 + src/swagger/index.ts | 2 +- 16 files changed, 289 insertions(+), 1478 deletions(-) create mode 100644 src/controller/groupController.ts create mode 100644 src/dto/createGroupDto.ts create mode 100644 src/models/baseModel.ts create mode 100644 src/repository/groupRepository.ts create mode 100644 src/routes/groupRoute.ts create mode 100644 src/service/groupService.ts delete mode 100644 src/swagger-output.json create mode 100644 src/swagger/definition/group.ts diff --git a/Dockerfile b/Dockerfile index f951bf4..8f5a1fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,11 @@ FROM node:latest +ENV TZ=Asia/Taipei +RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build +RUN npm run swagger EXPOSE 3000 CMD ["npm","start"] \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index fd477c9..afabd9c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -8,7 +8,7 @@ import { router } from './routes/router'; import connection from './config/dbConnection'; import { globalMiddleware } from './middleware/globalMiddleware'; import swaggerUi from 'swagger-ui-express'; -import swaggerFile from './swagger-output.json'; +import swaggerFile from './dist/swagger-output.json'; import passport from 'passport'; import { AppError } from './utils/errorHandler'; import { DefaultException } from './utils/defaultException'; diff --git a/src/controller/baseController.ts b/src/controller/baseController.ts index 8a54f03..0710e75 100644 --- a/src/controller/baseController.ts +++ b/src/controller/baseController.ts @@ -1,5 +1,7 @@ import { CustomResponseType } from '../types/customResponseType'; import { ResponseObject } from '../utils/responseObject'; +import { Request } from 'express'; +import { validationResult } from 'express-validator'; export abstract class BaseController { public formatResponse( @@ -12,7 +14,15 @@ export abstract class BaseController { message: message, data: data, }; - return new ResponseObject(options); } + public paramVerify(req: Request): ResponseObject | void { + const result = validationResult(req); + if (!result.isEmpty()) { + return this.formatResponse( + result.array()[0].msg, + CustomResponseType.FORMAT_ERROR, + ); + } + } } diff --git a/src/controller/groupController.ts b/src/controller/groupController.ts new file mode 100644 index 0000000..5652ed3 --- /dev/null +++ b/src/controller/groupController.ts @@ -0,0 +1,26 @@ +import { Request } from 'express'; +import { BaseController } from './baseController'; +import { CustomResponseType } from '../types/customResponseType'; +import { ResponseObject } from '../utils/responseObject'; +import { CreateGroupDto } from '../dto/createGroupDto'; +import { GroupService } from '../service/groupService'; +import { IGroup } from '../models/group'; + +export class GroupController extends BaseController { + private readonly groupService = new GroupService(); + + public createGroup = async (req: Request): Promise => { + this.paramVerify(req); + const createGroupDto = new CreateGroupDto(req); + const group = (await this.groupService.createGroup( + createGroupDto, + )) as IGroup; + return this.formatResponse( + CustomResponseType.OK_MESSAGE, + CustomResponseType.OK, + { + groupId: group.id, + }, + ); + }; +} diff --git a/src/controller/indexController.ts b/src/controller/indexController.ts index 9cb7d98..41fdcb2 100644 --- a/src/controller/indexController.ts +++ b/src/controller/indexController.ts @@ -2,7 +2,6 @@ import { NextFunction, Request, Response } from 'express'; import { BaseController } from './baseController'; import { ResponseObject } from '../utils/responseObject'; import { CustomResponseType } from '../types/customResponseType'; -import { validationResult } from 'express-validator'; import bcrypt from 'bcrypt'; import { LoginVo } from '../vo/loginVo'; import { UserService } from '../service/userService'; @@ -106,16 +105,6 @@ class IndexController extends BaseController { ); } }; - - private paramVerify(req: Request) { - const result = validationResult(req); - if (!result.isEmpty()) { - return this.formatResponse( - result.array()[0].msg, - CustomResponseType.FORMAT_ERROR, - ); - } - } } export default IndexController; diff --git a/src/dto/createGroupDto.ts b/src/dto/createGroupDto.ts new file mode 100644 index 0000000..9bcbed9 --- /dev/null +++ b/src/dto/createGroupDto.ts @@ -0,0 +1,56 @@ +import { Request } from 'express'; +import { parseDate } from '../utils/common'; +import { Types } from 'mongoose'; + +export class CreateGroupDto { + private readonly userId: Types.ObjectId; + private readonly title: string; + private readonly theater: string; + private readonly movieTitle: string; + private readonly time: Date; + private readonly amount: number; + private readonly haveTicket: boolean; + private readonly content: string; + private readonly status: string; + + get getUserId(): Types.ObjectId { + return this.userId; + } + get getTitle(): string { + return this.title; + } + get getTheater(): string { + return this.theater; + } + get getMovieTitle(): string { + return this.movieTitle; + } + get getTime(): Date { + return this.time; + } + get getHaveTicket(): boolean { + return this.haveTicket; + } + get getAmount(): number { + return this.amount; + } + get getContent(): string { + return this.content; + } + get getStatus(): string { + return this.status; + } + constructor(req: Request) { + const { title, location, movieTitle, time, amount, haveTicket, content } = + req.body; + this.userId = new Types.ObjectId((req.user as any).id as string); + this.title = title; + this.theater = location; + this.movieTitle = movieTitle; + this.time = parseDate('time', time) as Date; + this.amount = amount; + this.haveTicket = haveTicket; + this.content = content; + this.status = 'ongoing'; + } +} diff --git a/src/models/baseModel.ts b/src/models/baseModel.ts new file mode 100644 index 0000000..eaabcfb --- /dev/null +++ b/src/models/baseModel.ts @@ -0,0 +1,5 @@ +import { Schema } from 'mongoose'; + +export interface BaseModel extends Document { + id: Schema.Types.ObjectId; +} diff --git a/src/models/group.ts b/src/models/group.ts index 9da5024..ba8015f 100644 --- a/src/models/group.ts +++ b/src/models/group.ts @@ -2,8 +2,9 @@ import { Schema, model } from 'mongoose'; import { ITimestamp } from '../types/common.type'; import { schemaOption } from '../utils/constants'; import { GroupStatus } from '../types/group.type'; +import { BaseModel } from './baseModel'; -interface IGroup extends Document, ITimestamp { +export interface IGroup extends Document, ITimestamp, BaseModel { userId: Schema.Types.ObjectId; title: string; theater: string; @@ -69,6 +70,4 @@ const schema = new Schema( schemaOption, ); -const GroupModel = model('Group', schema); - -export default GroupModel; +export const GroupModel = model('Group', schema); diff --git a/src/repository/groupRepository.ts b/src/repository/groupRepository.ts new file mode 100644 index 0000000..c5e6f6f --- /dev/null +++ b/src/repository/groupRepository.ts @@ -0,0 +1,19 @@ +import { CreateGroupDto } from '../dto/createGroupDto'; +import { GroupModel, IGroup } from '../models/group'; + +export class GroupRepository { + public async createGroup(createGroupDto: CreateGroupDto): Promise { + return GroupModel.create(new GroupModel(createGroupDto)); + } +} +// { +// userId: new Types.ObjectId(createGroupDto.getUserId), +// title: createGroupDto.getTitle, +// theater: createGroupDto.getTheater, +// movieTitle: createGroupDto.getMovieTitle, +// time: createGroupDto.getTime, +// amount: createGroupDto.getAmount, +// haveTicket: createGroupDto.getHaveTicket, +// content: createGroupDto.getContent, +// status: 'ongoing', +// } diff --git a/src/routes/groupRoute.ts b/src/routes/groupRoute.ts new file mode 100644 index 0000000..8b55fb5 --- /dev/null +++ b/src/routes/groupRoute.ts @@ -0,0 +1,65 @@ +import { BaseRoute } from './baseRoute'; +import { GroupController } from '../controller/groupController'; +import { UserVerify } from '../middleware/userVerify'; +import { body } from 'express-validator'; +import { CustomResponseType } from '../types/customResponseType'; + +export class GroupRoute extends BaseRoute { + protected controller!: GroupController; + + constructor() { + super(); + this.initial(); + } + + protected initial(): void { + this.controller = new GroupController(); + this.setRouters(); + } + + protected setRouters(): void { + this.router.post( + '/v1/group', + /** + * #swagger.tags = ['Group'] + * #swagger.summary = '建立揪團' + */ + /* + #swagger.parameters['obj'] ={ + in:'body', + description:'欲建立的揪團資料', + schema:{ + $ref:"#/definitions/CustomCreateGroupObj" + } + } + */ + /** + #swagger.responses[200] = { + description: 'OK', + schema: { + $ref: '#/definitions/CreateGroupSuccess' } + } + */ + UserVerify, + body('title') + .exists() + .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '活動標題'), + body('location') + .exists() + .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '活動地點'), + body('movieTitle') + .exists() + .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '電影名稱'), + body('time') + .exists() + .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '活動時間'), + body('amount') + .exists() + .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '人數'), + body('haveTicket') + .exists() + .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '是否持有票券'), + this.responseHandler(this.controller.createGroup), + ); + } +} diff --git a/src/routes/router.ts b/src/routes/router.ts index 9a01ddc..cf40a40 100644 --- a/src/routes/router.ts +++ b/src/routes/router.ts @@ -2,9 +2,11 @@ import { IndexRoute } from './indexRoute'; import { UserRoute } from './userRoute'; import { BaseRoute } from './baseRoute'; import { ProductRoute } from './productRoute'; +import { GroupRoute } from './groupRoute'; export const router: Array = [ new IndexRoute(), new UserRoute(), new ProductRoute(), + new GroupRoute(), ]; diff --git a/src/service/groupService.ts b/src/service/groupService.ts new file mode 100644 index 0000000..b08eea3 --- /dev/null +++ b/src/service/groupService.ts @@ -0,0 +1,23 @@ +import { CustomResponseType } from '../types/customResponseType'; +import { throwError } from '../utils/errorHandler'; +import log4js from '../config/log4js'; +import { CreateGroupDto } from '../dto/createGroupDto'; +import { GroupRepository } from '../repository/groupRepository'; +import { IGroup } from '../models/group'; +const logger = log4js.getLogger(`GroupService`); + +export class GroupService { + private readonly groupRepository: GroupRepository = new GroupRepository(); + + public async createGroup( + createGroupDto: CreateGroupDto, + ): Promise { + return this.groupRepository.createGroup(createGroupDto).catch((err) => { + logger.error('create user error', err); + throwError( + CustomResponseType.INSERT_ERROR_MESSAGE, + CustomResponseType.INSERT_ERROR, + ); + }); + } +} diff --git a/src/swagger-output.json b/src/swagger-output.json deleted file mode 100644 index 5325ec7..0000000 --- a/src/swagger-output.json +++ /dev/null @@ -1,1460 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "title": "Movie Go", - "description": "This is the project for a Node.js course.", - "version": "1.0.0" - }, - "basePath": "/api", - "tags": [ - { - "name": "Home", - "description": "首頁" - }, - { - "name": "Sign-in", - "description": "登入相關" - }, - { - "name": "Account", - "description": "會員管理" - }, - { - "name": "Product", - "description": "商品相關" - }, - { - "name": "Ticket", - "description": "票券相關" - }, - { - "name": "Payment", - "description": "訂單相關" - }, - { - "name": "Group", - "description": "揪團" - }, - { - "name": "Admin", - "description": "後台管理" - } - ], - "schemes": [ - "http", - "https" - ], - "securityDefinitions": { - "Bearer": { - "type": "apiKey", - "in": "header", - "name": "Authorization", - "description": "JWT Token" - } - }, - "paths": { - "/v1/user/sign-up": { - "post": { - "tags": [ - "Sign-in" - ], - "summary": "註冊", - "description": "", - "parameters": [ - { - "name": "obj", - "in": "body", - "description": "Sign up information", - "required": true, - "schema": { - "$ref": "#/definitions/SignUpForm" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/Success" - } - } - } - } - }, - "/v1/user/login": { - "post": { - "tags": [ - "Sign-in" - ], - "summary": "登入", - "description": "", - "parameters": [ - { - "name": "obj", - "in": "body", - "description": "User information.", - "required": true, - "schema": { - "$ref": "#/definitions/LoginForm" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/LoginSuccess" - } - } - } - } - }, - "/v1/user/forgot-pwd": { - "post": { - "tags": [ - "Sign-in" - ], - "summary": "忘記密碼", - "description": "", - "parameters": [ - { - "name": "obj", - "in": "body", - "description": "Forgot Password User Email.", - "required": true, - "schema": { - "$ref": "#/definitions/ForgotPwdForm" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/Success" - } - } - } - } - }, - "/v1/user/reset-pwd": { - "post": { - "tags": [ - "Sign-in" - ], - "summary": "重設密碼", - "description": "", - "parameters": [ - { - "name": "authorization", - "in": "header", - "type": "string" - }, - { - "name": "obj", - "in": "body", - "description": "Reset Password.", - "required": true, - "schema": { - "$ref": "#/definitions/ResetPwdForm" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/Success" - } - } - }, - "security": [ - { - "Bearer": [] - } - ] - } - }, - "/v1/user/google-login": { - "get": { - "tags": [ - "Sign-in" - ], - "summary": "Google第三方登入", - "description": "", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/Success" - } - } - } - } - }, - "/v1/user/google/callback": { - "get": { - "description": "", - "responses": { - "default": { - "description": "" - } - } - } - }, - "/v1/user/google-update": { - "post": { - "tags": [ - "Sign-in" - ], - "summary": "首次Google第三方登入後進行一般註冊", - "description": "", - "parameters": [ - { - "name": "authorization", - "in": "header", - "type": "string" - }, - { - "name": "obj", - "in": "body", - "description": "Sign up after Google Login", - "required": true, - "schema": { - "$ref": "#/definitions/SignUpForm" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/Success" - } - } - } - } - }, - "/v1/product": { - "get": { - "tags": [ - "Product" - ], - "summary": "取得商品列表", - "description": "", - "parameters": [ - { - "name": "authorization", - "in": "header", - "type": "string" - }, - { - "name": "title", - "in": "query", - "required": false, - "description": "模糊搜尋:商品名稱", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductTitleQuery" - } - }, - { - "name": "types", - "in": "query", - "required": false, - "description": "精準搜尋:商品類別 (多個則以逗號分開)", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductTypesQuery" - } - }, - { - "name": "genres", - "in": "query", - "required": false, - "description": "精準搜尋:電影分類 (多個則以逗號分開)", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductGenresQuery" - } - }, - { - "name": "vendors", - "in": "query", - "required": false, - "description": "精準搜尋:供應商 (多個則以逗號分開)", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductVendorsQuery" - } - }, - { - "name": "theaters", - "in": "query", - "required": false, - "description": "精準搜尋:位置 (多個則以逗號分開)", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductTheatersQuery" - } - }, - { - "name": "isLaunched", - "in": "query", - "required": false, - "description": "是否販售", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductIsLaunchedQuery" - } - }, - { - "name": "isPublic", - "in": "query", - "required": false, - "description": "是否公開", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductIsPublicQuery" - } - }, - { - "name": "startAtFrom", - "in": "query", - "required": false, - "description": "開始活動時間-起", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductStartAtFromQuery" - } - }, - { - "name": "startAtTo", - "in": "query", - "required": false, - "description": "開始活動時間-迄", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductStartAtToQuery" - } - }, - { - "name": "sellStartAtFrom", - "in": "query", - "required": false, - "description": "開始販售時間-迄", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductSellStartFromQuery" - } - }, - { - "name": "sellStartAtTo", - "in": "query", - "required": false, - "description": "開始販售時間-迄", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductSellStartToQuery" - } - }, - { - "name": "recommendWeights", - "in": "query", - "required": false, - "description": "精準搜尋:推薦權重 (多個則以逗號分開)", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductRecommendWeightQuery" - } - }, - { - "name": "priceMax", - "in": "query", - "required": false, - "description": "價格區間-最大值", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductPriceMaxQuery" - } - }, - { - "name": "priceMin", - "in": "query", - "required": false, - "description": "價格區間-最小值", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductPriceMinQuery" - } - }, - { - "name": "tags", - "in": "query", - "required": false, - "description": "精準搜尋:標籤 (多個則以逗號分開),先不要用", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductTagQuery" - } - }, - { - "name": "page", - "in": "query", - "required": true, - "description": "頁數", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductPageQuery" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "description": "每頁資料數", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductLimitQuery" - } - }, - { - "name": "sortBy", - "in": "query", - "required": false, - "description": "排序根據, e.g. startAt, price, sellStartAt, type, vendor, theater, title, _id, soldAmount,createdAt,降冪則在前面加上 - ", - "type": "string", - "schema": { - "$ref": "#/definitions/CustomGetProductSortByQuery" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/CreateProductsSuccess" - } - } - }, - "security": [ - { - "Bearer": [] - } - ] - }, - "post": { - "tags": [ - "Product" - ], - "summary": "新增商品", - "description": "", - "parameters": [ - { - "name": "authorization", - "in": "header", - "type": "string" - }, - { - "name": "obj", - "in": "body", - "description": "欲新增之商品列表", - "schema": { - "$ref": "#/definitions/CustomCreateProductsObj" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/CreateProductsSuccess" - } - }, - "6213": { - "description": "新增失敗", - "schema": { - "$ref": "#/definitions/CreateProductsError" - } - } - }, - "security": [ - { - "Bearer": [] - } - ] - } - }, - "/v1/user": { - "get": { - "tags": [ - "Account" - ], - "summary": "取得使用者資料", - "description": "", - "parameters": [ - { - "name": "authorization", - "in": "header", - "type": "string" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/UserDetail" - } - } - }, - "security": [ - { - "Bearer": [] - } - ] - }, - "patch": { - "tags": [ - "Account" - ], - "summary": "更新使用者資料", - "description": "", - "parameters": [ - { - "name": "authorization", - "in": "header", - "type": "string" - }, - { - "name": "obj", - "in": "body", - "description": "可更新部分欄位", - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string", - "example": "roger" - }, - "birthDate": {}, - "email": { - "type": "string", - "example": "roger@gmail.com" - }, - "gender": { - "type": "string", - "example": "none" - }, - "phone": { - "type": "string", - "example": "0912345678" - }, - "address": { - "type": "string", - "example": "aaaabbb" - }, - "imgUrl": { - "type": "string", - "example": "" - } - } - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/Success" - } - } - }, - "security": [ - { - "Bearer": [] - } - ] - } - } - }, - "definitions": { - "Success": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "6000" - }, - "message": { - "type": "string", - "example": "成功" - }, - "data": { - "type": "object", - "properties": {} - } - }, - "required": [ - "status", - "message", - "data" - ] - }, - "Error6202": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "6202" - }, - "message": { - "type": "string", - "example": "密碼錯誤" - } - }, - "required": [ - "status", - "message" - ] - }, - "ErrorToken": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "6303" - }, - "message": { - "type": "string", - "example": "尚未登入" - } - }, - "required": [ - "status", - "message" - ] - }, - "Error404": { - "type": "object", - "properties": { - "status": { - "type": "number", - "example": 404 - }, - "message": { - "type": "string", - "example": "找不到頁面" - } - }, - "required": [ - "status", - "message" - ] - }, - "Error500": { - "type": "object", - "properties": { - "status": { - "type": "number", - "example": 500 - }, - "message": { - "type": "string", - "example": "系統錯誤,請稍後再試" - } - }, - "required": [ - "status", - "message" - ] - }, - "SignUpForm": { - "type": "object", - "properties": { - "account": { - "type": "string", - "example": "roger" - }, - "email": { - "type": "string", - "example": "roger@gmail.com" - }, - "pwd": { - "type": "string", - "example": "12345678" - }, - "confirmPwd": { - "type": "string", - "example": "12345678" - } - } - }, - "ForgotPwdForm": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": "roger@gmail.com" - } - } - }, - "ResetPwdForm": { - "type": "object", - "properties": { - "oldPwd": { - "type": "string", - "example": "12345678" - }, - "pwd": { - "type": "string", - "example": "111222333" - }, - "confirmPwd": { - "type": "string", - "example": "111222333" - } - } - }, - "LoginForm": { - "type": "object", - "properties": { - "account": { - "type": "string", - "example": "rrroger" - }, - "pwd": { - "type": "string", - "example": "123123123" - } - } - }, - "LoginSuccess": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "6000" - }, - "message": { - "type": "string", - "example": "成功" - }, - "data": { - "type": "object", - "properties": { - "account": { - "type": "string", - "example": "rrroger" - }, - "email": { - "type": "string", - "example": "roger@gmail.com" - }, - "token": { - "type": "string", - "example": "token" - }, - "accountType": { - "type": "string", - "example": "member" - } - } - } - }, - "required": [ - "status", - "message", - "data" - ] - }, - "UserDetail": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "6000" - }, - "message": { - "type": "string", - "example": "成功" - }, - "data": { - "type": "object", - "properties": { - "name": { - "type": "string", - "example": "roger" - }, - "createdAt": { - "type": "string", - "example": "2024-05-02T14:54:58.972Z" - }, - "birthDate": {}, - "email": { - "type": "string", - "example": "roger@gmail.com" - }, - "gender": { - "type": "string", - "example": "none" - }, - "phone": { - "type": "string", - "example": "0912345678" - }, - "address": { - "type": "string", - "example": "aaaabbb" - }, - "imgUrl": { - "type": "string", - "example": "" - } - } - } - }, - "required": [ - "status", - "message", - "data" - ] - }, - "UpdateUserDetail": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "6000" - }, - "message": { - "type": "string", - "example": "成功" - }, - "data": { - "type": "object", - "properties": {} - } - }, - "required": [ - "status", - "message", - "data" - ] - }, - "RegisterEmailSuccess": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "6000" - }, - "message": { - "type": "string", - "example": "請至信箱確認是否收到驗證信" - } - }, - "required": [ - "status", - "message" - ] - }, - "RegisterEmailError": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "6504" - }, - "message": { - "type": "string", - "example": "請稍後重試或聯絡管理員" - } - }, - "required": [ - "status", - "message" - ] - }, - "ValidateEmailError": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "6505" - }, - "message": { - "type": "string", - "example": "信箱驗證失敗" - } - }, - "required": [ - "status", - "message" - ] - }, - "CreateProductsSuccess": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "6000" - }, - "message": { - "type": "string", - "example": "成功" - }, - "data": { - "type": "object", - "properties": { - "products": { - "type": "array", - "items": { - "type": "object", - "properties": { - "title": { - "type": "string", - "example": "這是一個商品名稱" - }, - "type": { - "type": "string", - "example": "premier" - }, - "genre": { - "type": "string", - "example": "action" - }, - "vendor": { - "type": "string", - "example": "貓咪影業" - }, - "theater": { - "type": "string", - "example": "信義威秀" - }, - "price": { - "type": "number", - "example": 1100 - }, - "amount": { - "type": "number", - "example": 100 - }, - "soldAmount": { - "type": "number", - "example": 0 - }, - "plans": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "example": "三人同行好棒棒" - }, - "discount": { - "type": "number", - "example": 0.5 - }, - "headCount": { - "type": "number", - "example": 10 - } - } - } - }, - "introduction": { - "type": "string", - "example": "

很棒的商品

" - }, - "isLaunched": { - "type": "boolean", - "example": false - }, - "isPublic": { - "type": "boolean", - "example": false - }, - "recommendWeight": { - "type": "number", - "example": 1 - }, - "sellEndAt": { - "type": "string", - "example": "2024-05-12T15:46:07.182Z" - }, - "sellStartAt": { - "type": "string", - "example": "2024-05-12T13:46:07.183Z" - }, - "endAt": { - "type": "string", - "example": "2024-05-12T19:46:07.183Z" - }, - "startAt": { - "type": "string", - "example": "2024-05-12T17:46:07.183Z" - }, - "tags": { - "type": "array", - "items": { - "type": "object", - "properties": { - "tagId": { - "type": "string", - "example": "123" - } - } - } - }, - "photoPath": {}, - "notifications": { - "type": "array", - "example": [ - "通知一", - "通知二" - ], - "items": { - "type": "string" - } - }, - "highlights": { - "type": "array", - "example": [ - "亮點一", - "亮點二" - ], - "items": { - "type": "string" - } - }, - "cautions": { - "type": "array", - "example": [ - "事項一", - "事項二" - ], - "items": { - "type": "string" - } - }, - "confirmations": { - "type": "array", - "example": [ - "確認詳情一", - "確認詳情二" - ], - "items": { - "type": "string" - } - }, - "cancelPolicies": { - "type": "array", - "example": [ - "取消政策一", - "取消政策二" - ], - "items": { - "type": "string" - } - }, - "certificates": { - "type": "array", - "example": [ - "憑證類型一", - "憑證類型二" - ], - "items": { - "type": "string" - } - } - }, - "required": [ - "title", - "type", - "genre", - "vendor", - "theater", - "price", - "amount", - "soldAmount", - "introduction", - "isLaunched", - "isPublic", - "recommendWeight", - "sellEndAt", - "sellStartAt", - "endAt", - "startAt" - ] - } - }, - "page": { - "type": "number", - "example": 1 - }, - "limit": { - "type": "number", - "example": 10 - }, - "totalCount": { - "type": "number", - "example": 1 - } - }, - "required": [ - "products", - "page", - "limit", - "totalCount" - ] - } - }, - "required": [ - "status", - "message", - "data" - ] - }, - "CreateProductsError": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "6213" - }, - "message": { - "type": "string", - "example": "新增錯誤" - } - }, - "required": [ - "status", - "message" - ] - }, - "CustomCreateProductsObj": { - "type": "object", - "required": [ - "products" - ], - "properties": { - "products": { - "type": "array", - "items": { - "type": "object", - "required": [ - "title", - "type", - "genre", - "vendor", - "theater", - "price", - "amount", - "soldAmount", - "startAt", - "endAt", - "sellStartAt", - "sellEndAt", - "recommendWeight", - "isPublic", - "isLaunched", - "introduction" - ], - "properties": { - "title": { - "type": "string", - "example": "這是一個商品名稱", - "description": "商品名稱" - }, - "type": { - "type": "string", - "example": "premier", - "enum": [ - "premier", - "corporateBooking", - "privateBooking", - "preScreeningMeeting", - "postScreeningMeeting", - "specialEvent", - "openAir" - ], - "description": "商品類別" - }, - "genre": { - "type": "string", - "example": "action", - "enum": [ - "action", - "adventure", - "comedy", - "drama", - "horror", - "fantasy", - "romance", - "animation", - "thriller", - "mystery", - "scienceFiction", - "musical", - "music", - "war", - "western", - "epic", - "historical" - ], - "description": "電影分類" - }, - "vendor": { - "type": "string", - "example": "貓咪影業", - "description": "供應商" - }, - "theater": { - "type": "string", - "example": "信義威秀", - "description": "位置" - }, - "price": { - "type": "number", - "example": 1100, - "min": 100, - "description": "單張票價" - }, - "amount": { - "type": "number", - "example": 100, - "min": 0, - "description": "票券總量,不得低於最大人數方案。" - }, - "soldAmount": { - "type": "number", - "example": 0, - "min": 0, - "description": "已銷售數量" - }, - "plans": { - "type": "array", - "description": "銷售方案", - "items": { - "type": "object", - "required": [ - "name", - "discount", - "headCount" - ], - "properties": { - "name": { - "type": "string", - "description": "方案名稱", - "example": "三人同行好棒棒", - "min": 2 - }, - "discount": { - "type": "number", - "max": 1, - "min": 0.1, - "example": 0.5, - "description": "方案折扣數" - }, - "headCount": { - "type": "number", - "min": 0, - "example": 10, - "description": "方案包含人數" - } - } - } - }, - "introduction": { - "type": "string", - "example": "

很棒的商品

", - "description": "商品介紹 (html)" - }, - "isLaunched": { - "type": "boolean", - "example": false, - "description": "是否開始販賣,尚未公開的情況下,商品不可以進行販賣,可販賣的商品須同時在販賣時間的區間且 isLaunched 為 true" - }, - "isPublic": { - "type": "boolean", - "example": false, - "description": "是否公開" - }, - "recommendWeight": { - "type": "number", - "min": 1, - "max": 10, - "example": 1, - "description": "推薦權重" - }, - "sellEndAt": { - "type": "Date", - "example": "2024-05-12T15:46:07.182Z", - "min": "2024-05-11T14:46:07.183Z", - "description": "販賣結束時間,必須晚於販賣開始時間至少一個小時" - }, - "sellStartAt": { - "type": "Date", - "example": "2024-05-12T13:46:07.183Z", - "min": "2024-05-11T13:46:07.184Z", - "description": "販賣開始時間,必須晚於現在時間至少一天" - }, - "endAt": { - "type": "Date", - "example": "2024-05-12T19:46:07.183Z", - "min": "2024-05-11T16:46:07.184Z", - "description": "活動結束時間,必須晚於活動開始時間至少一個小時" - }, - "startAt": { - "type": "Date", - "example": "2024-05-12T17:46:07.183Z", - "min": "2024-05-11T15:46:07.184Z", - "description": "活動開始時間,必須晚於販售結束時間至少一個小時" - }, - "tags": { - "type": "array", - "description": "標籤列表", - "items": { - "type": "object", - "properties": { - "tagId": { - "type": "string", - "example": "AAA", - "description": "標籤,先把這個拿掉" - } - } - } - }, - "photoPath": { - "type": "string", - "description": "商品圖片 Url", - "example": null - }, - "notifications": { - "type": "array", - "description": "通知列表", - "example": [ - "通知一", - "通知二" - ], - "items": { - "type": "string" - } - }, - "highlights": { - "type": "array", - "description": "活動亮點列表", - "example": [ - "亮點一", - "亮點二" - ], - "items": { - "type": "string" - } - }, - "cautions": { - "type": "array", - "description": "注意事項列表", - "example": [ - "事項一", - "事項二" - ], - "items": { - "type": "string" - } - }, - "confirmations": { - "type": "array", - "description": "確認詳情列表", - "example": [ - "確認詳情一", - "確認詳情二" - ], - "items": { - "type": "string" - } - }, - "cancelPolicies": { - "type": "array", - "description": "取消政策列表", - "example": [ - "取消政策一", - "取消政策二" - ], - "items": { - "type": "string" - } - }, - "certificates": { - "type": "array", - "description": "憑證類型列表", - "example": [ - "憑證類型一", - "憑證類型二" - ], - "items": { - "type": "string" - } - } - } - } - } - } - }, - "CustomGetProductTitleQuery": { - "example": "很棒的特映會" - }, - "CustomGetProductTypesQuery": { - "example": "corporateBooking,openAir" - }, - "CustomGetProductGenresQuery": { - "example": "action,drama" - }, - "CustomGetProductVendorsQuery": { - "example": "貓咪影業,小狗影業" - }, - "CustomGetProductTheatersQuery": { - "example": "信義威秀,晶站威秀" - }, - "CustomGetProductIsLaunchedQuery": { - "example": "true" - }, - "CustomGetProductIsPublicQuery": { - "example": "true" - }, - "CustomGetProductStartAtFromQuery": { - "example": "2024-05-16T03:33:20.000+00:00" - }, - "CustomGetProductStartAtToQuery": { - "example": "2024-05-17T03:33:20.000+00:00" - }, - "CustomGetProductSellStartFromQuery": { - "example": "2024-05-18T03:33:20.000+00:00" - }, - "CustomGetProductSellStartToQuery": { - "example": "2024-05-19T03:33:20.000+00:00" - }, - "CustomGetProductRecommendWeightQuery": { - "example": "1,2,3" - }, - "CustomGetProductPriceMaxQuery": { - "example": "110" - }, - "CustomGetProductPriceMinQuery": { - "example": "10" - }, - "CustomGetProductTagQuery": { - "example": "日舞影展,金馬影展" - }, - "CustomGetProductPageQuery": { - "example": "1" - }, - "CustomGetProductLimitQuery": { - "example": "10" - }, - "CustomGetProductSortByQuery": { - "example": "-createdAt" - } - } -} \ No newline at end of file diff --git a/src/swagger/definition/group.ts b/src/swagger/definition/group.ts new file mode 100644 index 0000000..c853261 --- /dev/null +++ b/src/swagger/definition/group.ts @@ -0,0 +1,71 @@ +import { CustomResponseType } from '../../types/customResponseType'; + +const propName = { + title: '活動標題', + location: '活動地點', + movieTitle: '電影名稱', + time: '活動時間', + amount: '活動人數', + haveTicket: '是否持有票券', + content: '活動內容', +}; + +const customGroup = { + title: '活動', + location: '威秀影城', + movieTitle: '好看的電影', + time: '2024-05-10 10:10', + amount: 5, + haveTicket: true, + content: '好活動', +}; + +export const CreateGroupSuccess = { + $status: CustomResponseType.OK, + $message: CustomResponseType.OK_MESSAGE, + $data: { + $groupId: 'aaabbbcc123123', + }, +}; + +export const CustomCreateGroupObj = { + type: 'object', + required: ['title', 'location', 'movieTitle', 'time', 'amount', 'haveTicket'], + properties: { + title: { + type: 'string', + description: propName.title, + example: customGroup.title, + }, + location: { + type: 'string', + description: propName.location, + example: customGroup.location, + }, + movieTitle: { + type: 'string', + description: propName.movieTitle, + example: customGroup.movieTitle, + }, + time: { + type: 'Date', + description: propName.time, + example: customGroup.time, + }, + amount: { + type: 'number', + description: propName.amount, + example: customGroup.amount, + }, + haveTicket: { + type: 'boolean', + description: propName.haveTicket, + example: customGroup.haveTicket, + }, + content: { + type: 'string', + description: propName.content, + example: customGroup.content, + }, + }, +}; diff --git a/src/swagger/definition/index.ts b/src/swagger/definition/index.ts index 2b7504e..67eb913 100644 --- a/src/swagger/definition/index.ts +++ b/src/swagger/definition/index.ts @@ -38,6 +38,7 @@ import { CustomGetProductSortByQuery, CustomGetProductTagQuery, } from './product'; +import { CreateGroupSuccess, CustomCreateGroupObj } from './group'; export const definitions = { Success, @@ -57,6 +58,7 @@ export const definitions = { ValidateEmailError, CreateProductsSuccess, CreateProductsError, + CreateGroupSuccess, }; export const customDefinitions = { @@ -79,4 +81,5 @@ export const customDefinitions = { CustomGetProductPageQuery, CustomGetProductLimitQuery, CustomGetProductSortByQuery, + CustomCreateGroupObj, }; diff --git a/src/swagger/index.ts b/src/swagger/index.ts index 59637ba..1b0e804 100644 --- a/src/swagger/index.ts +++ b/src/swagger/index.ts @@ -34,7 +34,7 @@ const doc = { }, }; -const outputFile = '../../swagger-output.json'; +const outputFile = '../swagger-output.json'; const routes = ['src/routes/*.ts']; // 進入點/注入點,分析 router 和自動生成 swaggerAutogen(outputFile, routes, doc).then(() => { From 9e33cdd33c05798ea696604d523791d27dc567c5 Mon Sep 17 00:00:00 2001 From: RogerLi Date: Sun, 12 May 2024 14:16:12 +0800 Subject: [PATCH 2/3] feat: add pipe middleware to handle format validation, use custom type in createGroupDto constructor --- src/controller/userController.ts | 2 +- src/dto/createGroupDto.ts | 51 +++++++------------------------ src/repository/groupRepository.ts | 11 ------- src/routes/baseRoute.ts | 6 ++++ src/routes/groupRoute.ts | 34 +++++---------------- src/types/group.type.ts | 16 ++++++++++ src/validator/createGroup.pipe.ts | 29 ++++++++++++++++++ src/validator/pipe.base.ts | 17 +++++++++++ 8 files changed, 88 insertions(+), 78 deletions(-) create mode 100644 src/validator/createGroup.pipe.ts create mode 100644 src/validator/pipe.base.ts diff --git a/src/controller/userController.ts b/src/controller/userController.ts index 0d303d0..12ad8a5 100644 --- a/src/controller/userController.ts +++ b/src/controller/userController.ts @@ -1,4 +1,4 @@ -import { NextFunction, Request, Response } from 'express'; +import { Request } from 'express'; import { BaseController } from './baseController'; import { CustomResponseType } from '../types/customResponseType'; import { ResponseObject } from '../utils/responseObject'; diff --git a/src/dto/createGroupDto.ts b/src/dto/createGroupDto.ts index 9bcbed9..0df70fa 100644 --- a/src/dto/createGroupDto.ts +++ b/src/dto/createGroupDto.ts @@ -1,6 +1,6 @@ -import { Request } from 'express'; import { parseDate } from '../utils/common'; import { Types } from 'mongoose'; +import { GroupStatus, TCreateGroupReq } from '../types/group.type'; export class CreateGroupDto { private readonly userId: Types.ObjectId; @@ -10,47 +10,18 @@ export class CreateGroupDto { private readonly time: Date; private readonly amount: number; private readonly haveTicket: boolean; - private readonly content: string; + private readonly content?: string; private readonly status: string; - get getUserId(): Types.ObjectId { - return this.userId; - } - get getTitle(): string { - return this.title; - } - get getTheater(): string { - return this.theater; - } - get getMovieTitle(): string { - return this.movieTitle; - } - get getTime(): Date { - return this.time; - } - get getHaveTicket(): boolean { - return this.haveTicket; - } - get getAmount(): number { - return this.amount; - } - get getContent(): string { - return this.content; - } - get getStatus(): string { - return this.status; - } - constructor(req: Request) { - const { title, location, movieTitle, time, amount, haveTicket, content } = - req.body; + constructor(req: TCreateGroupReq) { this.userId = new Types.ObjectId((req.user as any).id as string); - this.title = title; - this.theater = location; - this.movieTitle = movieTitle; - this.time = parseDate('time', time) as Date; - this.amount = amount; - this.haveTicket = haveTicket; - this.content = content; - this.status = 'ongoing'; + this.title = req.body.title; + this.theater = req.body.theater; + this.movieTitle = req.body.movieTitle; + this.time = parseDate('time', req.body.time.toString()) as Date; + this.amount = req.body.amount; + this.haveTicket = req.body.haveTicket; + this.content = req.body.content; + this.status = GroupStatus.ongoing; } } diff --git a/src/repository/groupRepository.ts b/src/repository/groupRepository.ts index c5e6f6f..d6c15e0 100644 --- a/src/repository/groupRepository.ts +++ b/src/repository/groupRepository.ts @@ -6,14 +6,3 @@ export class GroupRepository { return GroupModel.create(new GroupModel(createGroupDto)); } } -// { -// userId: new Types.ObjectId(createGroupDto.getUserId), -// title: createGroupDto.getTitle, -// theater: createGroupDto.getTheater, -// movieTitle: createGroupDto.getMovieTitle, -// time: createGroupDto.getTime, -// amount: createGroupDto.getAmount, -// haveTicket: createGroupDto.getHaveTicket, -// content: createGroupDto.getContent, -// status: 'ongoing', -// } diff --git a/src/routes/baseRoute.ts b/src/routes/baseRoute.ts index edffc62..cf311d9 100644 --- a/src/routes/baseRoute.ts +++ b/src/routes/baseRoute.ts @@ -3,6 +3,7 @@ import { ResponseObject } from '../utils/responseObject'; import { BaseController } from '../controller/baseController'; import { HttpStatus } from '../types/responseType'; import log4js from '../config/log4js'; +import { PipeBase } from '../validator/pipe.base'; const logger = log4js.getLogger(`BaseRoute`); export abstract class BaseRoute { @@ -23,6 +24,11 @@ export abstract class BaseRoute { return this.prefix; } + protected usePipe(prototype: any): any[] { + const pipe = new prototype(); + return (pipe as PipeBase).transform(); + } + protected responseHandler( method: ( req: Request, diff --git a/src/routes/groupRoute.ts b/src/routes/groupRoute.ts index 8b55fb5..6e58bc4 100644 --- a/src/routes/groupRoute.ts +++ b/src/routes/groupRoute.ts @@ -1,8 +1,7 @@ import { BaseRoute } from './baseRoute'; import { GroupController } from '../controller/groupController'; import { UserVerify } from '../middleware/userVerify'; -import { body } from 'express-validator'; -import { CustomResponseType } from '../types/customResponseType'; +import { CreateGroupPipe } from '../validator/createGroup.pipe'; export class GroupRoute extends BaseRoute { protected controller!: GroupController; @@ -34,31 +33,14 @@ export class GroupRoute extends BaseRoute { } */ /** - #swagger.responses[200] = { - description: 'OK', - schema: { - $ref: '#/definitions/CreateGroupSuccess' } - } - */ + #swagger.responses[200] = { + description: 'OK', + schema: { + $ref: '#/definitions/CreateGroupSuccess' } + } + */ UserVerify, - body('title') - .exists() - .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '活動標題'), - body('location') - .exists() - .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '活動地點'), - body('movieTitle') - .exists() - .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '電影名稱'), - body('time') - .exists() - .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '活動時間'), - body('amount') - .exists() - .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '人數'), - body('haveTicket') - .exists() - .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '是否持有票券'), + this.usePipe(CreateGroupPipe), this.responseHandler(this.controller.createGroup), ); } diff --git a/src/types/group.type.ts b/src/types/group.type.ts index a060d82..d659dc8 100644 --- a/src/types/group.type.ts +++ b/src/types/group.type.ts @@ -1,5 +1,21 @@ +import { Request } from 'express'; +import { Types } from 'mongoose'; + export enum GroupStatus { ongoing = 'ongoing', // 正在揪團 cancelled = 'cancelled', // 取消揪團 completed = 'completed', // 完成揪團 } + +export interface TCreateGroupReq extends Request { + body: { + userId: Types.ObjectId; + title: string; + theater: string; + movieTitle: string; + time: Date; + amount: number; + haveTicket: boolean; + content?: string; + }; +} diff --git a/src/validator/createGroup.pipe.ts b/src/validator/createGroup.pipe.ts new file mode 100644 index 0000000..524ccd5 --- /dev/null +++ b/src/validator/createGroup.pipe.ts @@ -0,0 +1,29 @@ +import { PipeBase } from './pipe.base'; +import { body } from 'express-validator'; +import { CustomResponseType } from '../types/customResponseType'; + +export class CreateGroupPipe extends PipeBase { + public transform(): any[] { + return [ + body('title') + .exists() + .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '活動標題'), + body('location') + .exists() + .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '活動地點'), + body('movieTitle') + .exists() + .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '電影名稱'), + body('time') + .exists() + .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '活動時間'), + body('amount') + .exists() + .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '人數'), + body('haveTicket') + .exists() + .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '是否持有票券'), + this.validationHandler, + ]; + } +} diff --git a/src/validator/pipe.base.ts b/src/validator/pipe.base.ts new file mode 100644 index 0000000..6d39cd9 --- /dev/null +++ b/src/validator/pipe.base.ts @@ -0,0 +1,17 @@ +import { NextFunction } from 'express'; +import { validationResult } from 'express-validator'; +import { CustomResponseType } from '../types/customResponseType'; +import { throwError } from '../utils/errorHandler'; + +export abstract class PipeBase { + public abstract transform(): any[]; + + protected validationHandler(req: Request, res: Response, next: NextFunction) { + const errors = validationResult(req); + if (!errors.isEmpty()) { + const arr = errors.array(); + throwError(arr[0].msg, CustomResponseType.FORMAT_ERROR); + } + next(); + } +} From fe7fb647a01331b37adc99e506bdc04804606bff Mon Sep 17 00:00:00 2001 From: RogerLi Date: Sun, 12 May 2024 14:20:03 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E6=B4=BB=E5=8B=95?= =?UTF-8?q?=E5=9C=B0=E9=BB=9E=E6=AC=84=E4=BD=8D=E5=90=8D=E7=A8=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/validator/createGroup.pipe.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validator/createGroup.pipe.ts b/src/validator/createGroup.pipe.ts index 524ccd5..9b92090 100644 --- a/src/validator/createGroup.pipe.ts +++ b/src/validator/createGroup.pipe.ts @@ -8,7 +8,7 @@ export class CreateGroupPipe extends PipeBase { body('title') .exists() .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '活動標題'), - body('location') + body('theater') .exists() .withMessage(CustomResponseType.FORMAT_ERROR_MESSAGE + '活動地點'), body('movieTitle')