From 88ad76b64c36ddeacd90bd7d694c4690c8ff11c4 Mon Sep 17 00:00:00 2001 From: scarf Date: Mon, 26 Jun 2023 10:18:24 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20`createReviews`=20zod=EB=A1=9C=20?= =?UTF-8?q?=ED=8C=8C=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reviews/controller/reviews.controller.ts | 30 ++++++++++------ .../src/reviews/controller/reviews.type.ts | 36 +++++++++++++++++++ 2 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 backend/src/reviews/controller/reviews.type.ts diff --git a/backend/src/reviews/controller/reviews.controller.ts b/backend/src/reviews/controller/reviews.controller.ts index b40d70c0..7edd2096 100644 --- a/backend/src/reviews/controller/reviews.controller.ts +++ b/backend/src/reviews/controller/reviews.controller.ts @@ -1,23 +1,31 @@ import { - Request, Response, + Request, RequestHandler, Response, } from 'express'; import * as status from 'http-status'; import * as errorCheck from './utils/errorCheck'; import * as parseCheck from './utils/parseCheck'; -import { contentParseCheck } from './utils/errorCheck'; import ReviewsService from '../service/reviews.service'; +import { createReviewsSchema, userSchema } from './reviews.type'; +import ErrorResponse from '../../utils/error/errorResponse'; +import * as errorCode from '../../utils/error/errorCode'; const reviewsService = new ReviewsService(); -export const createReviews = async ( - req: Request, - res: Response, -) => { - const { id: tokenId } = req.user as any; - const bookInfoId = req?.body?.bookInfoId; - const content = req?.body?.content; - contentParseCheck(content); - await reviewsService.createReviews(tokenId, bookInfoId, content); +export const createReviews: RequestHandler = async (req, res, next) => { + const parsedId = userSchema.safeParse(req.user); + const parsedBody = createReviewsSchema.safeParse(req.body); + + if (!parsedId.success) { + return next(new ErrorResponse(errorCode.NO_MATCHING_USER, 400)); + } + if (!parsedBody.success) { + return next(new ErrorResponse(errorCode.INVALID_INPUT_REVIEWS_CONTENT, 400)); + } + + const { id } = parsedId.data; + const { bookInfoId, content } = parsedBody.data; + + await reviewsService.createReviews(id, bookInfoId, content); return res.status(status.OK).send(); }; diff --git a/backend/src/reviews/controller/reviews.type.ts b/backend/src/reviews/controller/reviews.type.ts new file mode 100644 index 00000000..5eb4974a --- /dev/null +++ b/backend/src/reviews/controller/reviews.type.ts @@ -0,0 +1,36 @@ +import { z } from 'zod'; + +export const positiveInt = z.number().int().nonnegative(); + +export const userSchema = z.object({ + id: positiveInt, +}); + +export const bookInfoIdSchema = positiveInt; +export const reviewsIdSchema = positiveInt; + +export const contentSchema = z.string().min(10).max(420); + +export const sortSchema = z.enum(['ASC', 'DESC']); + +export const createReviewsSchema = z.object({ + bookInfoId: bookInfoIdSchema, + content: contentSchema, +}); + +export const getReviewsSchema = z.object({ + isMyReview: z.boolean(), + titleOrNickname: z.string(), + disabled: z.number(), + page: positiveInt, + limit: positiveInt, + sort: sortSchema, +}); + +export const updateReviewsSchema = z.object({ + content: contentSchema, +}); + +export const reviewIdParamSchema = z.object({ + reviewsId: positiveInt, +}); From 3778fd98dbd8ca6616f58e24f8f4942c3d87d1db Mon Sep 17 00:00:00 2001 From: scarf Date: Mon, 26 Jun 2023 10:57:33 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20`getReviews`=20zod=EB=A1=9C=20?= =?UTF-8?q?=ED=8C=8C=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reviews/controller/reviews.controller.ts | 34 ++++++++++------- .../src/reviews/controller/reviews.type.ts | 37 +++++++++++-------- 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/backend/src/reviews/controller/reviews.controller.ts b/backend/src/reviews/controller/reviews.controller.ts index 7edd2096..a0c48895 100644 --- a/backend/src/reviews/controller/reviews.controller.ts +++ b/backend/src/reviews/controller/reviews.controller.ts @@ -5,7 +5,9 @@ import * as status from 'http-status'; import * as errorCheck from './utils/errorCheck'; import * as parseCheck from './utils/parseCheck'; import ReviewsService from '../service/reviews.service'; -import { createReviewsSchema, userSchema } from './reviews.type'; +import { + createReviewsSchema, getReviewsSchema, queryOptionSchema, userSchema, +} from './reviews.type'; import ErrorResponse from '../../utils/error/errorResponse'; import * as errorCode from '../../utils/error/errorCode'; @@ -29,23 +31,27 @@ export const createReviews: RequestHandler = async (req, res, next) => { return res.status(status.OK).send(); }; -export const getReviews = async ( - req: Request, - res: Response, -) => { - const { id: tokenId } = req.user as any; - const isMyReview = parseCheck.booleanQueryParse(req.query.isMyReview); - const titleOrNickname = parseCheck.stringQueryParse(req?.query?.titleOrNickname); - const disabled = parseCheck.disabledParse(Number(req?.query?.disabled)); - const page = parseCheck.pageParse(parseInt(String(req?.query?.page), 10)); - const sort = parseCheck.sortParse(req?.query?.sort); - const limit = parseCheck.limitParse(parseInt(String(req?.query?.limit), 10)); +export const getReviews: RequestHandler = async (req, res, next) => { + const parsedId = userSchema.safeParse(req.user); + const parsedQuery = getReviewsSchema.safeParse(req.query); + if (!parsedId.success) { + return next(new ErrorResponse(errorCode.NO_MATCHING_USER, 400)); + } + if (!parsedQuery.success) { + return next(new ErrorResponse(errorCode.INVALID_INPUT, 400)); + } + + const { id } = parsedId.data; + const { + isMyReview, titleOrNickname, disabled, page, sort, limit, + } = parsedQuery.data; + return res .status(status.OK) .json(await reviewsService.getReviewsPage( - tokenId, + id, isMyReview, - titleOrNickname, + titleOrNickname ?? '', disabled, page, sort, diff --git a/backend/src/reviews/controller/reviews.type.ts b/backend/src/reviews/controller/reviews.type.ts index 5eb4974a..4edcfc02 100644 --- a/backend/src/reviews/controller/reviews.type.ts +++ b/backend/src/reviews/controller/reviews.type.ts @@ -11,7 +11,23 @@ export const reviewsIdSchema = positiveInt; export const contentSchema = z.string().min(10).max(420); -export const sortSchema = z.enum(['ASC', 'DESC']); +export type Sort = 'ASC' | 'DESC'; +export const sortSchema = z.string().toUpperCase() + .refine((s): s is Sort => s === 'ASC' || s === 'DESC') + .default('DESC' as const); + +/** 0: 공개, 1: 비공개, -1: 전체 리뷰 */ +type Disabled = 0 | 1 | -1; +const disabledSchema = z.coerce.number().int().refine( + (n): n is Disabled => [-1, 0, 1].includes(n), + (n) => ({ message: `0: 공개, 1: 비공개, -1: 전체 리뷰, 입력값: ${n}` }), +); + +export const queryOptionSchema = z.object({ + page: positiveInt.default(0), + limit: positiveInt.default(10), + sort: sortSchema, +}); export const createReviewsSchema = z.object({ bookInfoId: bookInfoIdSchema, @@ -19,18 +35,7 @@ export const createReviewsSchema = z.object({ }); export const getReviewsSchema = z.object({ - isMyReview: z.boolean(), - titleOrNickname: z.string(), - disabled: z.number(), - page: positiveInt, - limit: positiveInt, - sort: sortSchema, -}); - -export const updateReviewsSchema = z.object({ - content: contentSchema, -}); - -export const reviewIdParamSchema = z.object({ - reviewsId: positiveInt, -}); + isMyReview: z.boolean().default(false), + titleOrNickname: z.string().optional(), + disabled: disabledSchema, +}).merge(queryOptionSchema); From eef1e140c190c21736f7ea66eedbd8218e0710a3 Mon Sep 17 00:00:00 2001 From: scarf Date: Mon, 26 Jun 2023 13:55:00 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20id=20=ED=8C=8C=EC=8B=B1?= =?UTF-8?q?=EC=9D=84=20userSchema=EB=A1=9C=20=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reviews/controller/reviews.controller.ts | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/backend/src/reviews/controller/reviews.controller.ts b/backend/src/reviews/controller/reviews.controller.ts index a0c48895..639f0966 100644 --- a/backend/src/reviews/controller/reviews.controller.ts +++ b/backend/src/reviews/controller/reviews.controller.ts @@ -59,36 +59,45 @@ export const getReviews: RequestHandler = async (req, res, next) => { )); }; -export const updateReviews = async ( - req: Request, - res: Response, -) => { - const { id: tokenUserId } = req.user as any; +export const updateReviews: RequestHandler = async (req, res, next) => { + const parsedId = userSchema.safeParse(req.user); + if (!parsedId.success) { + return next(new ErrorResponse(errorCode.NO_MATCHING_USER, 400)); + } + const { id } = parsedId.data; + const content = errorCheck.contentParseCheck(req?.body?.content); const reviewsId = errorCheck.reviewsIdParseCheck(req?.params?.reviewsId); - await reviewsService.updateReviews(reviewsId, tokenUserId, content); + + await reviewsService.updateReviews(reviewsId, id, content); return res.status(status.OK).send(); }; -export const deleteReviews = async ( - req: Request, - res: Response, -) => { - const { id: tokenId } = req.user as any; +export const deleteReviews: RequestHandler = async (req, res, next) => { + const parsedId = userSchema.safeParse(req.user); + if (!parsedId.success) { + return next(new ErrorResponse(errorCode.NO_MATCHING_USER, 400)); + } + const { id } = parsedId.data; + const reviewsId = errorCheck.reviewsIdParseCheck(req?.params?.reviewsId); const reviewsUserId = await errorCheck.reviewsIdExistCheck(reviewsId); - errorCheck.idAndTokenIdSameCheck(reviewsUserId, tokenId); - await reviewsService.deleteReviews(reviewsId, tokenId); + + errorCheck.idAndTokenIdSameCheck(reviewsUserId, id); + await reviewsService.deleteReviews(reviewsId, id); return res.status(status.OK).send(); }; -export const patchReviews = async ( - req: Request, - res: Response, -) => { - const { id: tokenId } = req.user as any; +export const patchReviews: RequestHandler = async (req, res, next) => { + const parsedId = userSchema.safeParse(req.user); + if (!parsedId.success) { + return next(new ErrorResponse(errorCode.NO_MATCHING_USER, 400)); + } + const { id } = parsedId.data; + const reviewsId = errorCheck.reviewsIdParseCheck(req?.params?.reviewsId); + await errorCheck.reviewsIdExistCheck(reviewsId); - await reviewsService.patchReviews(reviewsId, tokenId); + await reviewsService.patchReviews(reviewsId, id); return res.status(status.OK).send(); }; From c6e965d5de1b514366f2e724300821861a44f952 Mon Sep 17 00:00:00 2001 From: scarf Date: Wed, 28 Jun 2023 11:37:29 +0900 Subject: [PATCH 4/4] =?UTF-8?q?refactor:=20=EB=A6=AC=EB=B7=B0=20patch=20?= =?UTF-8?q?=EB=B0=8F=20delete=EC=97=90=20zod=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reviews/controller/reviews.controller.ts | 23 +++++++++++++---- .../src/reviews/controller/reviews.type.ts | 25 ++++++++++++++----- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/backend/src/reviews/controller/reviews.controller.ts b/backend/src/reviews/controller/reviews.controller.ts index 639f0966..bcb8b395 100644 --- a/backend/src/reviews/controller/reviews.controller.ts +++ b/backend/src/reviews/controller/reviews.controller.ts @@ -6,7 +6,12 @@ import * as errorCheck from './utils/errorCheck'; import * as parseCheck from './utils/parseCheck'; import ReviewsService from '../service/reviews.service'; import { - createReviewsSchema, getReviewsSchema, queryOptionSchema, userSchema, + createReviewsSchema, + deleteReviewsSchema, + getReviewsSchema, + patchReviewsSchema, + queryOptionSchema, + userSchema, } from './reviews.type'; import ErrorResponse from '../../utils/error/errorResponse'; import * as errorCode from '../../utils/error/errorCode'; @@ -75,29 +80,37 @@ export const updateReviews: RequestHandler = async (req, res, next) => { export const deleteReviews: RequestHandler = async (req, res, next) => { const parsedId = userSchema.safeParse(req.user); + const parsedParams = deleteReviewsSchema.safeParse(req.params); if (!parsedId.success) { return next(new ErrorResponse(errorCode.NO_MATCHING_USER, 400)); } + if (!parsedParams.success) { + return next(new ErrorResponse(errorCode.INVALID_INPUT_REVIEWS_ID, 400)); + } const { id } = parsedId.data; + const { reviewsId } = parsedParams.data; - const reviewsId = errorCheck.reviewsIdParseCheck(req?.params?.reviewsId); const reviewsUserId = await errorCheck.reviewsIdExistCheck(reviewsId); - errorCheck.idAndTokenIdSameCheck(reviewsUserId, id); + await reviewsService.deleteReviews(reviewsId, id); return res.status(status.OK).send(); }; export const patchReviews: RequestHandler = async (req, res, next) => { const parsedId = userSchema.safeParse(req.user); + const parsedParams = patchReviewsSchema.safeParse(req.params); if (!parsedId.success) { return next(new ErrorResponse(errorCode.NO_MATCHING_USER, 400)); } + if (!parsedParams.success) { + return next(new ErrorResponse(errorCode.INVALID_INPUT_REVIEWS_ID, 400)); + } const { id } = parsedId.data; - - const reviewsId = errorCheck.reviewsIdParseCheck(req?.params?.reviewsId); + const { reviewsId } = parsedParams.data; await errorCheck.reviewsIdExistCheck(reviewsId); + await reviewsService.patchReviews(reviewsId, id); return res.status(status.OK).send(); }; diff --git a/backend/src/reviews/controller/reviews.type.ts b/backend/src/reviews/controller/reviews.type.ts index 4edcfc02..721502ee 100644 --- a/backend/src/reviews/controller/reviews.type.ts +++ b/backend/src/reviews/controller/reviews.type.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -export const positiveInt = z.number().int().nonnegative(); +export const positiveInt = z.coerce.number().int().nonnegative(); export const userSchema = z.object({ id: positiveInt, @@ -29,13 +29,26 @@ export const queryOptionSchema = z.object({ sort: sortSchema, }); -export const createReviewsSchema = z.object({ - bookInfoId: bookInfoIdSchema, - content: contentSchema, -}); - export const getReviewsSchema = z.object({ isMyReview: z.boolean().default(false), titleOrNickname: z.string().optional(), disabled: disabledSchema, }).merge(queryOptionSchema); + +export const createReviewsSchema = z.object({ + bookInfoId: bookInfoIdSchema, + content: contentSchema, +}); + +export const updateReviewsSchema = z.object({ + reviewsId: reviewsIdSchema, + content: contentSchema, +}); + +export const deleteReviewsSchema = z.object({ + reviewsId: reviewsIdSchema, +}); + +export const patchReviewsSchema = z.object({ + reviewsId: reviewsIdSchema, +});