From 5b8c4739f21f4047773ace7985fdebc06c63fad7 Mon Sep 17 00:00:00 2001 From: Donghyeon Kim <0916dhkim@gmail.com> Date: Thu, 20 Apr 2023 23:58:58 -0400 Subject: [PATCH] Add handler abstraction --- app/server/src/app.ts | 10 ++-- app/server/src/handlers/auth-handlers.ts | 62 ++++++++---------------- app/server/src/handlers/coach-handler.ts | 29 +++++------ app/server/src/handlers/handler.ts | 61 +++++++++++++++++++++++ 4 files changed, 101 insertions(+), 61 deletions(-) create mode 100644 app/server/src/handlers/handler.ts diff --git a/app/server/src/app.ts b/app/server/src/app.ts index 1d77073..834f3c2 100644 --- a/app/server/src/app.ts +++ b/app/server/src/app.ts @@ -1,10 +1,12 @@ +import { HELLO_WORLD, signInInputSchema, signUpInputSchema } from "@bookery/shared"; import { signinHandler, signupHandler } from "./handlers/auth-handlers"; -import { HELLO_WORLD } from "@bookery/shared"; import { Service } from "./service/services"; import express from "express"; +import { forExpress } from "./handlers/handler"; import { getCoachHandler } from "./handlers/coach-handler"; import session from "express-session"; +import { z } from "zod"; export function buildApp(service: Service) { const app = express(); @@ -16,9 +18,9 @@ export function buildApp(service: Service) { ); app.use(express.json()); - app.post("/api/auth/signup", signupHandler(service.auth)); - app.post("/api/auth/signin", signinHandler(service.auth)); - app.get("/api/coach/:id", getCoachHandler(service.coach)); + app.post("/api/auth/signup", forExpress(signupHandler(service.auth), {requestBodySchema: signUpInputSchema})); + app.post("/api/auth/signin", forExpress(signinHandler(service.auth), {requestBodySchema: signInInputSchema})); + app.get("/api/coach/:id", forExpress(getCoachHandler(service.coach), {paramsSchema: z.object({id: z.string()})})); app.get("/", (req, res) => { res.send(HELLO_WORLD); diff --git a/app/server/src/handlers/auth-handlers.ts b/app/server/src/handlers/auth-handlers.ts index 677daf6..2e14870 100644 --- a/app/server/src/handlers/auth-handlers.ts +++ b/app/server/src/handlers/auth-handlers.ts @@ -1,51 +1,31 @@ -import { NextFunction, Request, Response } from "express"; -import { signInInputSchema, signUpInputSchema } from "@bookery/shared"; +import { SignInInput, SignUpInput } from "@bookery/shared"; import { AuthService } from "../service/auth-service"; +import { Context } from "./handler"; export const signupHandler = (auth: AuthService) => - async (req: Request, res: Response, next: NextFunction) => { - try { - const inputParseResult = signUpInputSchema.safeParse(req.body); - if (!inputParseResult.success) { - return res.sendStatus(400); - } - const coach = await auth.signUp(inputParseResult.data); - req.session.coach = coach; - return res.send({ success: true }); - } catch (e) { - next(e); - } - }; + async (context: Context) => { + const coach = await auth.signUp(context.body); + context.setSession(coach); + return { + status: 200, + body: { success: true }, + }; + } export const signinHandler = (auth: AuthService) => - async (req: Request, res: Response, next: NextFunction) => { + async (context: Context) => { try { - const inputParseResult = signInInputSchema.safeParse(req.body); - if (!inputParseResult.success) { - return res.sendStatus(400); - } - await auth - .signIn(inputParseResult.data) - .then((coach) => (req.session.coach = coach)) - .then(() => res.send({ success: true })) - .catch(() => handleFailedSignIn(req, res)); - } catch (e) { - next(e); + const coach = await auth.signIn(context.body); + context.setSession(coach); + return { + status: 200, + body: { success: true }, + }; + } catch { + await context.destroySession(); + return { status: 401, body: { success: false } }; } - }; - -async function handleFailedSignIn(req: Request, res: Response) { - await new Promise((resolve, reject) => { - req.session.destroy((err) => { - if (err) { - reject(err); - } else { - resolve(undefined); - } - }); - }); - res.sendStatus(401); -} + } diff --git a/app/server/src/handlers/coach-handler.ts b/app/server/src/handlers/coach-handler.ts index 0610f7f..4225cf3 100644 --- a/app/server/src/handlers/coach-handler.ts +++ b/app/server/src/handlers/coach-handler.ts @@ -1,21 +1,18 @@ -import { NextFunction, Request, Response } from "express"; - import { CoachService } from "../service/coach-service"; +import { Context } from "./handler"; export const getCoachHandler = (coachService: CoachService) => - async (req: Request, res: Response, next: NextFunction) => { - try { - const {id} = req.params; - if (id == null) { - throw new Error("id is missing in URL params."); - } - if (id != req.session.coach?.id) { - return res.sendStatus(403); - } - const coach = await coachService.getCoach(id); - return res.send({ coach }); - } catch (e) { - next(e); + async (context: Context) => { + if (context.params.id !== context.session?.id) { + return { + status: 403, + body: "Forbidden", + }; } - } + const coach = await coachService.getCoach(context.params.id); + return { + status: 200, + body: { coach }, + }; + }; diff --git a/app/server/src/handlers/handler.ts b/app/server/src/handlers/handler.ts new file mode 100644 index 0000000..068b03c --- /dev/null +++ b/app/server/src/handlers/handler.ts @@ -0,0 +1,61 @@ +import { Coach } from "@bookery/database"; +import { RequestHandler } from "express"; +import { z } from "zod"; + +type ResponseSpec = { + status: number; + body?: unknown; +}; + +type HandlerOptions = { + requestBodySchema?: TBodySchema; + paramsSchema?: TParamsSchema; +}; + +export type Context = { + body: TBody; + params: TParams; + session?: Coach; + setSession: (coach: Coach) => void; + destroySession: () => Promise; +}; + +export type Handler = (context: Context) => Promise; + +export const forExpress = ( + handler: Handler, z.infer>, + options: HandlerOptions, +) => { + const expressHandler: RequestHandler = async (req, res, next) => { + try { + const paramsParseResult = options.paramsSchema?.safeParse(req.params); + if (paramsParseResult?.success === false) { + return res.sendStatus(400); + } + const bodyParseResult = options.requestBodySchema?.safeParse(req.body); + if (bodyParseResult?.success === false) { + return res.sendStatus(400); + } + const resSpec = await handler({ + body: bodyParseResult?.data, + params: paramsParseResult?.data, + session: req.session.coach, + setSession: (coach) => { req.session.coach = coach }, + destroySession: () => new Promise((resolve, reject) => { + req.session.destroy((err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }), + }); + return res.status(resSpec.status).send(resSpec.body); + } catch (e) { + next(e); + } + }; + + return expressHandler; +};