diff --git a/root/week06/server/src/errors.ts b/root/week06/server/src/errors.ts new file mode 100644 index 0000000..8a61e76 --- /dev/null +++ b/root/week06/server/src/errors.ts @@ -0,0 +1,17 @@ +export class AppError extends Error { + code: string; + statusCode: number; + + constructor(message: string, code: string, statusCode: number) { + super(message); + this.code = code; + this.statusCode = statusCode; + } +} + +export const errors = { + USER_NOT_FOUND: new AppError("존재하지 않는 유저입니다.", "4040", 404), + STORE_NOT_FOUND: new AppError("존재하지 않는 가게입니다.", "4041", 404), + MISSION_NOT_FOUND: new AppError("존재하지 않는 미션입니다.", "4042", 404), + ALREADY_COMPLETED: new AppError("이미 완료된 미션입니다.", "4090", 409), +}; \ No newline at end of file diff --git a/root/week06/server/src/index.ts b/root/week06/server/src/index.ts index 6e6cb36..112973e 100644 --- a/root/week06/server/src/index.ts +++ b/root/week06/server/src/index.ts @@ -1,33 +1,75 @@ import "dotenv/config"; -import express from "express"; +import express, { Request, Response, NextFunction } from "express"; +import morgan from "morgan"; +import cookieParser from "cookie-parser"; import * as repo from "./repository"; +import { AppError } from "./errors"; const app = express(); + +// 미들웨어 +app.use(morgan("dev")); +app.use(cookieParser()); app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// 성공 응답 헬퍼 +const success = (result: any) => ({ + isSuccess: true, + code: "2000", + message: "성공", + result, +}); // 내가 작성한 리뷰 목록 -app.get("/api/users/:userId/reviews", async (req, res) => { - const reviews = await repo.getMyReviews(Number(req.params.userId)); - res.json({ isSuccess: true, result: reviews }); +app.get("/api/users/:userId/reviews", async (req, res, next) => { + try { + const result = await repo.getMyReviews(Number(req.params.userId)); + res.json(success(result)); + } catch (err) { next(err); } }); // 특정 가게의 미션 목록 -app.get("/api/stores/:storeId/missions", async (req, res) => { - const missions = await repo.getStoreMissions(Number(req.params.storeId)); - res.json({ isSuccess: true, result: missions }); +app.get("/api/stores/:storeId/missions", async (req, res, next) => { + try { + const result = await repo.getStoreMissions(Number(req.params.storeId)); + res.json(success(result)); + } catch (err) { next(err); } }); // 내가 진행 중인 미션 목록 -app.get("/api/users/:userId/missions", async (req, res) => { - const status = (req.query.status as string) ?? "IN_PROGRESS"; - const missions = await repo.getMyMissions(Number(req.params.userId), status); - res.json({ isSuccess: true, result: missions }); +app.get("/api/users/:userId/missions", async (req, res, next) => { + try { + const status = (req.query.status as string) ?? "IN_PROGRESS"; + const result = await repo.getMyMissions(Number(req.params.userId), status); + res.json(success(result)); + } catch (err) { next(err); } }); // 미션 완료 처리 -app.patch("/api/user-missions/:userMissionId/complete", async (req, res) => { - const updated = await repo.completeMission(Number(req.params.userMissionId)); - res.json({ isSuccess: true, result: updated }); +app.patch("/api/user-missions/:userMissionId/complete", async (req, res, next) => { + try { + const result = await repo.completeMission(Number(req.params.userMissionId)); + res.json(success(result)); + } catch (err) { next(err); } +}); + +// 에러 핸들러 +app.use((err: any, req: Request, res: Response, next: NextFunction) => { + if (err instanceof AppError) { + res.status(err.statusCode).json({ + isSuccess: false, + code: err.code, + message: err.message, + }); + } else { + console.error(err); + res.status(500).json({ + isSuccess: false, + code: "5000", + message: "서버 에러가 발생했습니다.", + }); + } }); app.listen(3000, () => console.log("Server is running at http://localhost:3000")); \ No newline at end of file diff --git a/root/week06/server/src/repository.ts b/root/week06/server/src/repository.ts index 5192d6f..a2cb06a 100644 --- a/root/week06/server/src/repository.ts +++ b/root/week06/server/src/repository.ts @@ -1,31 +1,45 @@ import prisma from "./prisma"; +import { errors } from "./errors"; // 내가 작성한 리뷰 목록 -export const getMyReviews = (userId: number) => - prisma.review.findMany({ +export const getMyReviews = async (userId: number) => { + const user = await prisma.user.findUnique({ where: { id: userId } }); + if (!user) throw errors.USER_NOT_FOUND; + return prisma.review.findMany({ where: { userId }, include: { store: true }, orderBy: { createdAt: "desc" }, }); +}; // 특정 가게의 미션 목록 -export const getStoreMissions = (storeId: number) => - prisma.mission.findMany({ +export const getStoreMissions = async (storeId: number) => { + const store = await prisma.store.findUnique({ where: { id: storeId } }); + if (!store) throw errors.STORE_NOT_FOUND; + return prisma.mission.findMany({ where: { storeId }, orderBy: { createdAt: "desc" }, }); +}; // 내가 진행 중인 미션 목록 -export const getMyMissions = (userId: number, status: string) => - prisma.userMission.findMany({ +export const getMyMissions = async (userId: number, status: string) => { + const user = await prisma.user.findUnique({ where: { id: userId } }); + if (!user) throw errors.USER_NOT_FOUND; + return prisma.userMission.findMany({ where: { userId, status: status as any }, include: { mission: { include: { store: true } } }, orderBy: { createdAt: "desc" }, }); +}; // 미션 완료 처리 -export const completeMission = (userMissionId: number) => - prisma.userMission.update({ +export const completeMission = async (userMissionId: number) => { + const userMission = await prisma.userMission.findUnique({ where: { id: userMissionId } }); + if (!userMission) throw errors.MISSION_NOT_FOUND; + if (userMission.status === "COMPLETED") throw errors.ALREADY_COMPLETED; + return prisma.userMission.update({ where: { id: userMissionId }, data: { status: "COMPLETED" }, - }); \ No newline at end of file + }); +}; \ No newline at end of file diff --git a/root/week06/server/tsconfig.json b/root/week06/server/tsconfig.json index cec4a3a..b6631ca 100644 --- a/root/week06/server/tsconfig.json +++ b/root/week06/server/tsconfig.json @@ -1,44 +1,13 @@ { - // Visit https://aka.ms/tsconfig to read more about this file "compilerOptions": { - // File Layout - // "rootDir": "./src", - // "outDir": "./dist", - - // Environment Settings - // See also https://aka.ms/tsconfig/module - "module": "nodenext", - "target": "esnext", - "types": [], - // For nodejs: - // "lib": ["esnext"], - // "types": ["node"], - // and npm install -D @types/node - - // Other Outputs - "sourceMap": true, - "declaration": true, - "declarationMap": true, - - // Stricter Typechecking Options - "noUncheckedIndexedAccess": true, - "exactOptionalPropertyTypes": true, - - // Style Options - // "noImplicitReturns": true, - // "noImplicitOverride": true, - // "noUnusedLocals": true, - // "noUnusedParameters": true, - // "noFallthroughCasesInSwitch": true, - // "noPropertyAccessFromIndexSignature": true, - - // Recommended Options + "target": "ES2020", + "module": "commonjs", + "outDir": "./dist", + "rootDir": "./src", "strict": true, - "jsx": "react-jsx", - "verbatimModuleSyntax": true, - "isolatedModules": true, - "noUncheckedSideEffectImports": true, - "moduleDetection": "force", + "esModuleInterop": true, "skipLibCheck": true, - } -} + "sourceMap": true + }, + "include": ["src"] +} \ No newline at end of file