diff --git a/seminar6/advanced/.gitignore b/seminar6/advanced/.gitignore new file mode 100644 index 0000000..11ddd8d --- /dev/null +++ b/seminar6/advanced/.gitignore @@ -0,0 +1,3 @@ +node_modules +# Keep environment variables out of version control +.env diff --git a/seminar6/advanced/nodemon.json b/seminar6/advanced/nodemon.json new file mode 100644 index 0000000..bd415b2 --- /dev/null +++ b/seminar6/advanced/nodemon.json @@ -0,0 +1,6 @@ +{ + "watch": ["src", ".env"], + "ext": "js,ts,json", + "ignore": ["src/**/*.spec.ts"], + "exec": "ts-node --transpile-only ./src/index.ts" +} diff --git a/seminar6/advanced/package.json b/seminar6/advanced/package.json new file mode 100644 index 0000000..e94951c --- /dev/null +++ b/seminar6/advanced/package.json @@ -0,0 +1,26 @@ +{ + "name": "seminar4", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "dev": "nodemon", + "build": "tsc && node dist" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.2", + "@types/express": "^4.17.14", + "@types/express-validator": "^3.0.0", + "@types/jsonwebtoken": "^8.5.9", + "@types/node": "^18.11.9", + "nodemon": "^2.0.20" + }, + "dependencies": { + "@prisma/client": "^4.5.0", + "bcryptjs": "^2.4.3", + "express": "^4.18.2", + "express-validator": "^6.14.2", + "jsonwebtoken": "^8.5.1", + "prisma": "^4.5.0" + } +} diff --git a/seminar6/advanced/prisma/schema.prisma b/seminar6/advanced/prisma/schema.prisma new file mode 100644 index 0000000..023f54c --- /dev/null +++ b/seminar6/advanced/prisma/schema.prisma @@ -0,0 +1,43 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id Int @id @default(autoincrement()) + userName String + age Int? + email String? @db.VarChar(400) + password String @db.VarChar(400) + LikeContent LikeContent[] +} + +model Content { + id Int @id(map: "content_pkey") @default(autoincrement()) + contentName String? @db.VarChar(100) + genre String? @db.VarChar(100) + ageLimit Int? + episode Episode[] + likeContent LikeContent[] +} + +model LikeContent { + id Int @id(map: "FavoriteContent_pkey") @default(autoincrement()) + userId Int? + contentId Int? + content Content? @relation(fields: [contentId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "likecontent_content_id_fk") + user User? @relation(fields: [userId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "likecontent_user_id_fk") +} + +model Episode { + id Int @id @default(autoincrement()) + contentId Int? + episodeNumber Int? + runningTime Int? + description String? @db.VarChar(500) + content Content? @relation(fields: [contentId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "episode_content_id_fk") +} diff --git a/seminar6/advanced/src/constants/index.ts b/seminar6/advanced/src/constants/index.ts new file mode 100644 index 0000000..eaeae62 --- /dev/null +++ b/seminar6/advanced/src/constants/index.ts @@ -0,0 +1,3 @@ +export { default as message } from "./responseMessage"; +export { default as statusCode } from "./statusCode"; +export { default as tokenType } from "./tokenType"; diff --git a/seminar6/advanced/src/constants/response.ts b/seminar6/advanced/src/constants/response.ts new file mode 100644 index 0000000..1441aab --- /dev/null +++ b/seminar6/advanced/src/constants/response.ts @@ -0,0 +1,16 @@ +export const success = (status: number, message: string, data?: any) => { + return { + status, + success: true, + message, + data, + }; +}; + +export const fail = (status: number, message: string) => { + return { + status, + success: false, + message, + }; +}; diff --git a/seminar6/advanced/src/constants/responseMessage.ts b/seminar6/advanced/src/constants/responseMessage.ts new file mode 100644 index 0000000..8fafc60 --- /dev/null +++ b/seminar6/advanced/src/constants/responseMessage.ts @@ -0,0 +1,42 @@ +export default { + NULL_VALUE: "필요한 값이 없습니다.", + OUT_OF_VALUE: "파라미터 값이 잘못되었습니다.", + NOT_FOUND: "잘못된 경로입니다.", + BAD_REQUEST: "잘못된 요청입니다.", + + // 회원가입 및 로그인 + SIGNUP_SUCCESS: "회원 가입 성공", + SIGNUP_FAIL: "회원 가입 실패", + SIGNIN_SUCCESS: "로그인 성공", + SIGNIN_FAIL: "로그인 실패", + ALREADY_NICKNAME: "이미 사용중인 닉네임입니다.", + INVALID_PASSWORD: "잘못된 비밀번호입니다.", + + // 유저 + READ_USER_SUCCESS: "유저 조회 성공", + READ_USER_FAIL: "유저 조회 실패", + READ_ALL_USERS_SUCCESS: "모든 유저 조회 성공", + READ_ALL_USERS_FAIL: "모든 유저 조회 실패", + UPDATE_USER_SUCCESS: "유저 수정 성공", + UPDATE_USER_FAIL: "유저 수정 실패", + DELETE_USER_SUCCESS: "유저 탈퇴 성공", + DELETE_USER_FAIL: "유저 탈퇴 실패", + NO_USER: "탈퇴했거나 가입하지 않은 유저입니다.", + + // 콘텐츠 + READ_CONTENT_SUCCESS: "콘텐츠 정보 조회 성공", + CREATE_LIKE_CONTENT_SUCCESS: "찜한 콘텐츠 생성 성공", + READ_LIKE_CONTENT_SUCCESS: "찜한 콘텐츠 정보 조회 성공", + DELETE_LIKE_CONTENT_SUCCESS: "찜한 콘텐츠 삭제 성공", + + // 토큰 + CREATE_TOKEN_SUCCESS: "토큰 재발급 성공", + EXPIRED_TOKEN: "토큰이 만료되었습니다.", + EXPIRED_ALL_TOKEN: "모든 토큰이 만료되었습니다.", + INVALID_TOKEN: "유효하지 않은 토큰입니다.", + VALID_TOKEN: "유효한 토큰입니다.", + EMPTY_TOKEN: "토큰 값이 없습니다.", + + // 서버 내 오류 + INTERNAL_SERVER_ERROR: "서버 내 오류", +}; diff --git a/seminar6/advanced/src/constants/statusCode.ts b/seminar6/advanced/src/constants/statusCode.ts new file mode 100644 index 0000000..c9f04ce --- /dev/null +++ b/seminar6/advanced/src/constants/statusCode.ts @@ -0,0 +1,15 @@ +export default { + OK: 200, // 목록, 상세, 수정 성공 + CREATED: 201, // POST나 PUT으로 데이터 등록할 경우 사용 + // 어떠한 생성 작업을 요청받아 생성 작업을 성공 + ACCEPTED: 202, // 요청은 받았지만, 아직 동작을 수행하지 않은 상태로 요청이 적절함을 의미한다. + NO_CONTENT: 204, // 요청이 성공은 했지만 응답할 콘텐츠가 없을 경우를 뜻한다. + BAD_REQUEST: 400, // 클라이언트가 올바르지 못한 요청을 보내고 있음을 의미 + UNAUTHORIZED: 401, // 로그인을 하지 않아 권한이 없음. 권한 인증 요구 + FORBIDDEN: 403, // 요청이 서버에 의해 거부 되었음을 의미. 금지된 페이지 + NOT_FOUND: 404, // 요청한 URL을 찾을 수 없음을 의미 + CONFLICT: 409, // 클라이언트 요청에 대해 서버에서 충돌 요소가 발생 할수 있음을 의미 + INTERNAL_SERVER_ERROR: 500, // 서버에 오류가 발생하여 응답 할 수 없음을 의미 + SERVICE_UNAVAILABLE: 503, // 현재 서버가 유지보수 등의 이유로 일시적인 사용 불가함을 의미 + DB_ERROR: 600, +}; diff --git a/seminar6/advanced/src/constants/tokenType.ts b/seminar6/advanced/src/constants/tokenType.ts new file mode 100644 index 0000000..23cde90 --- /dev/null +++ b/seminar6/advanced/src/constants/tokenType.ts @@ -0,0 +1,4 @@ +export default { + TOKEN_EXPIRED: -3, + TOKEN_INVALID: -2, +}; diff --git a/seminar6/advanced/src/controller/contentController.ts b/seminar6/advanced/src/controller/contentController.ts new file mode 100644 index 0000000..332e367 --- /dev/null +++ b/seminar6/advanced/src/controller/contentController.ts @@ -0,0 +1,98 @@ +import { LikeContentCreateDto } from "./../interfaces/common/Content/LikeContentCreateDto"; +import { Request, Response } from "express"; +import { message, statusCode } from "../constants"; +import { success, fail } from "../constants/response"; +import { contentService } from "../service"; + +//* 콘텐츠 정보 조회 +const getContent = async (req: Request, res: Response) => { + const { contentId } = req.params; + const { episode } = req.query; + + let type: "episode" | null; + if (episode) { + type = "episode"; + } else { + type = null; + } + + if (!contentId) { + return res + .status(statusCode.BAD_REQUEST) + .send(fail(statusCode.BAD_REQUEST, message.BAD_REQUEST)); + } + switch (type) { + case "episode": { + const data = await contentService.getContent( + +contentId, + +(episode as string) + ); + + return res + .status(statusCode.OK) + .send(success(statusCode.OK, message.READ_CONTENT_SUCCESS, data)); + } + case null: { + const data = await contentService.getAllContentsInfo(+contentId); + return res + .status(statusCode.OK) + .send(success(statusCode.OK, message.READ_CONTENT_SUCCESS, data)); + } + } +}; + +//* 찜한 콘텐츠 생성 +const createLikeContent = async (req: Request, res: Response) => { + const { userId } = req.params; + const likeContentCreateDto: LikeContentCreateDto = req.body; + + if (!likeContentCreateDto) { + return res + .status(statusCode.BAD_REQUEST) + .send(fail(statusCode.BAD_REQUEST, message.BAD_REQUEST)); + } + + const data = await contentService.createLikeContent( + +userId, + likeContentCreateDto + ); + return res + .status(statusCode.OK) + .send(success(statusCode.OK, message.CREATE_LIKE_CONTENT_SUCCESS, data)); +}; + +//* 찝한 콘텐츠 조회 +const getLikeContents = async (req: Request, res: Response) => { + const { userId } = req.params; + + if (!userId) { + return res + .status(statusCode.BAD_REQUEST) + .send(fail(statusCode.BAD_REQUEST, message.BAD_REQUEST)); + } + + const data = await contentService.getLikeContents(+userId); + + return res + .status(statusCode.OK) + .send(success(statusCode.OK, message.READ_LIKE_CONTENT_SUCCESS, data)); +}; + +//* 찜한 콘텐츠 삭제 +const deleteLikeContent = async (req: Request, res: Response) => { + const { likeContentId } = req.params; + + await contentService.deleteLikeContent(+likeContentId); + return res + .status(statusCode.OK) + .send(success(statusCode.OK, message.DELETE_LIKE_CONTENT_SUCCESS)); +}; + +const contentController = { + getContent, + createLikeContent, + getLikeContents, + deleteLikeContent, +}; + +export default contentController; diff --git a/seminar6/advanced/src/controller/index.ts b/seminar6/advanced/src/controller/index.ts new file mode 100644 index 0000000..6ccc158 --- /dev/null +++ b/seminar6/advanced/src/controller/index.ts @@ -0,0 +1,4 @@ +import userController from "./userController"; +import contentController from "./contentController"; + +export { userController, contentController }; diff --git a/seminar6/advanced/src/controller/userController.ts b/seminar6/advanced/src/controller/userController.ts new file mode 100644 index 0000000..2b7cd7c --- /dev/null +++ b/seminar6/advanced/src/controller/userController.ts @@ -0,0 +1,105 @@ +import { UserUpdateDTO } from "../interfaces/common/User/UserUpdateDto"; +import { Request, Response } from "express"; +import { validationResult } from "express-validator"; +import { message, statusCode } from "../constants"; +import { success, fail } from "../constants/response"; +import { UserCreateDTO } from "../interfaces/common/User/UserCreateDto"; +import userService from "../service/userService"; +import jwtHandler from "../modules/jwtHandler"; +import { UserSignInDTO } from "../interfaces/common/User/UserSignInDto"; + +//* 유저 생성 +const createUser = async (req: Request, res: Response) => { + //? validation의 결과를 바탕으로 분기 처리 + const error = validationResult(req); + if (!error.isEmpty()) { + return res + .status(statusCode.BAD_REQUEST) + .send(fail(statusCode.BAD_REQUEST, message.BAD_REQUEST)); + } + //? 기존 비구조화 할당 방식 -> DTO의 형태 + const userCreateDto: UserCreateDTO = req.body; + const data = await userService.createUser(userCreateDto); + + if (!data) { + return res + .status(statusCode.BAD_REQUEST) + .send(fail(statusCode.BAD_REQUEST, message.SIGNUP_FAIL)); + } + + const accessToken = jwtHandler.sign(data.id); + + const result = { + id: data.id, + name: data.userName, + accessToken, + }; + + return res + .status(statusCode.CREATED) + .send(success(statusCode.CREATED, message.SIGNUP_SUCCESS, result)); +}; + +//* 유저 로그인 +const signInUser = async (req: Request, res: Response) => { + const error = validationResult(req); + if (!error.isEmpty()) { + return res + .status(statusCode.BAD_REQUEST) + .send(fail(statusCode.BAD_REQUEST, message.BAD_REQUEST)); + } + + const userSignInDto: UserSignInDTO = req.body; + + try { + const userId = await userService.signInUser(userSignInDto); + + if (!userId) + return res + .status(statusCode.NOT_FOUND) + .send(fail(statusCode.NOT_FOUND, message.NOT_FOUND)); + else if (userId === statusCode.UNAUTHORIZED) + return res + .status(statusCode.UNAUTHORIZED) + .send(fail(statusCode.UNAUTHORIZED, message.INVALID_PASSWORD)); + + const accessToken = jwtHandler.sign(userId); + + const result = { + id: userId, + accessToken, + }; + + res + .status(statusCode.OK) + .send(success(statusCode.OK, message.SIGNIN_SUCCESS, result)); + } catch (e) { + console.log(error); + //? 서버 내부에서 오류 발생 + res + .status(statusCode.INTERNAL_SERVER_ERROR) + .send( + fail(statusCode.INTERNAL_SERVER_ERROR, message.INTERNAL_SERVER_ERROR) + ); + } +}; + +//* 유저 정보(이름) 수정 +const updateUser = async (req: Request, res: Response) => { + const userUpdateDto: UserUpdateDTO = req.body; + const { userId } = req.params; + + if (!userUpdateDto) { + return res + .status(statusCode.BAD_REQUEST) + .send(fail(statusCode.BAD_REQUEST, message.BAD_REQUEST)); + } + const updatedUser = await userService.updateUser(+userId, userUpdateDto); + return res + .status(statusCode.OK) + .send(success(statusCode.OK, message.UPDATE_USER_SUCCESS, updatedUser)); +}; + +const userController = { createUser, signInUser, updateUser }; + +export default userController; diff --git a/seminar6/advanced/src/index.ts b/seminar6/advanced/src/index.ts new file mode 100644 index 0000000..e7a0809 --- /dev/null +++ b/seminar6/advanced/src/index.ts @@ -0,0 +1,24 @@ +import express, { NextFunction, Request, Response } from "express"; +import router from "./router"; + +const app = express(); // express 객체 받아옴 +const PORT = 3000; // 사용할 port를 3000번으로 설정 + +app.use(express.json()); // express 에서 request body를 json 으로 받아오겠다. + +app.use("/api", router); // use -> 모든 요청 +// localhost:8000/api -> api 폴더 +// localhost:8000/api/user -> user.ts + +//* HTTP method - GET +app.get("/", (req: Request, res: Response, next: NextFunction) => { + res.send("마! 이게 서버다!!!!!!!!!!!!!!!!!!!!"); +}); + +app.listen(PORT, () => { + console.log(` + ############################################# + 🛡️ Server listening on port: ${PORT} 🛡️ + ############################################# + `); +}); // 8000 번 포트에서 서버를 실행하겠다! diff --git a/seminar6/advanced/src/interfaces/common/Content/ContentResponseDto.ts b/seminar6/advanced/src/interfaces/common/Content/ContentResponseDto.ts new file mode 100644 index 0000000..9f7e968 --- /dev/null +++ b/seminar6/advanced/src/interfaces/common/Content/ContentResponseDto.ts @@ -0,0 +1,9 @@ +import { EpisodeInfo } from "./EpisodeInfo"; + +export interface ContentResponseDto { + id: number; + contentName: string; + genre: string; + ageLimit: number; + episode: EpisodeInfo[]; +} diff --git a/seminar6/advanced/src/interfaces/common/Content/EpisodeInfo.ts b/seminar6/advanced/src/interfaces/common/Content/EpisodeInfo.ts new file mode 100644 index 0000000..7cdab58 --- /dev/null +++ b/seminar6/advanced/src/interfaces/common/Content/EpisodeInfo.ts @@ -0,0 +1,7 @@ +export interface EpisodeInfo { + id: number; + contentId: number; + episodeNumber: number; + runningTime: number; + description: string; +} diff --git a/seminar6/advanced/src/interfaces/common/Content/LikeContentCreateDto.ts b/seminar6/advanced/src/interfaces/common/Content/LikeContentCreateDto.ts new file mode 100644 index 0000000..8e22ff1 --- /dev/null +++ b/seminar6/advanced/src/interfaces/common/Content/LikeContentCreateDto.ts @@ -0,0 +1,3 @@ +export interface LikeContentCreateDto { + contentId: number; +} diff --git a/seminar6/advanced/src/interfaces/common/User/UserCreateDto.ts b/seminar6/advanced/src/interfaces/common/User/UserCreateDto.ts new file mode 100644 index 0000000..17d77c9 --- /dev/null +++ b/seminar6/advanced/src/interfaces/common/User/UserCreateDto.ts @@ -0,0 +1,6 @@ +export interface UserCreateDTO { + name: string; + age: number; + email: string; + password: string; +} diff --git a/seminar6/advanced/src/interfaces/common/User/UserResponseDto.ts b/seminar6/advanced/src/interfaces/common/User/UserResponseDto.ts new file mode 100644 index 0000000..b8988d7 --- /dev/null +++ b/seminar6/advanced/src/interfaces/common/User/UserResponseDto.ts @@ -0,0 +1,7 @@ +export interface UserResponseDTO { + id: number; + userName: string; + age: number | null; + email: string; + password: string; +} diff --git a/seminar6/advanced/src/interfaces/common/User/UserSignInDto.ts b/seminar6/advanced/src/interfaces/common/User/UserSignInDto.ts new file mode 100644 index 0000000..47fde5a --- /dev/null +++ b/seminar6/advanced/src/interfaces/common/User/UserSignInDto.ts @@ -0,0 +1,4 @@ +export interface UserSignInDTO { + email: string; + password: string; +} diff --git a/seminar6/advanced/src/interfaces/common/User/UserUpdateDto.ts b/seminar6/advanced/src/interfaces/common/User/UserUpdateDto.ts new file mode 100644 index 0000000..417ed54 --- /dev/null +++ b/seminar6/advanced/src/interfaces/common/User/UserUpdateDto.ts @@ -0,0 +1,3 @@ +export interface UserUpdateDTO { + userName: string; +} diff --git a/seminar6/advanced/src/middleware/auth.ts b/seminar6/advanced/src/middleware/auth.ts new file mode 100644 index 0000000..623c722 --- /dev/null +++ b/seminar6/advanced/src/middleware/auth.ts @@ -0,0 +1,44 @@ +import { NextFunction, Request, Response } from "express"; +import { JwtPayload } from "jsonwebtoken"; +import { message, statusCode, tokenType } from "../constants"; +import { fail } from "../constants/response"; +import jwtHandler from "../modules/jwtHandler"; + +export default async (req: Request, res: Response, next: NextFunction) => { + const token = req.headers.authorization?.split(" ").reverse()[0]; //? Bearer ~~ 에서 토큰만 파싱 + if (!token) + return res + .status(statusCode.UNAUTHORIZED) + .send(fail(statusCode.UNAUTHORIZED, message.EMPTY_TOKEN)); + try { + const decoded = jwtHandler.verify(token); // jwtHandler에서 만들어둔 토큰 검사 + + //? 토큰 에러 분기 처리 + if (decoded === tokenType.TOKEN_EXPIRED) + return res + .status(statusCode.UNAUTHORIZED) + .send(fail(statusCode.UNAUTHORIZED, message.EXPIRED_TOKEN)); + if (decoded === tokenType.TOKEN_INVALID) + return res + .status(statusCode.UNAUTHORIZED) + .send(fail(statusCode.UNAUTHORIZED, message.INVALID_TOKEN)); + + //? decode한 후 담겨있는 userId를 꺼내옴 + const userId: number = (decoded as JwtPayload).userId; + if (!userId) + return res + .status(statusCode.UNAUTHORIZED) + .send(fail(statusCode.UNAUTHORIZED, message.INVALID_TOKEN)); + + //? 얻어낸 userId를 Request Body 내 userId 필드에 담고, 다음 미들웨어로 넘김 ( next() ) + req.body.userId = userId; + next(); + } catch (error) { + console.log(error); + res + .status(statusCode.INTERNAL_SERVER_ERROR) + .send( + fail(statusCode.INTERNAL_SERVER_ERROR, message.INTERNAL_SERVER_ERROR) + ); + } +}; diff --git a/seminar6/advanced/src/middleware/index.ts b/seminar6/advanced/src/middleware/index.ts new file mode 100644 index 0000000..0d3b686 --- /dev/null +++ b/seminar6/advanced/src/middleware/index.ts @@ -0,0 +1 @@ +export { default as auth } from "./auth"; diff --git a/seminar6/advanced/src/modules/jwtHandler.ts b/seminar6/advanced/src/modules/jwtHandler.ts new file mode 100644 index 0000000..e4a0114 --- /dev/null +++ b/seminar6/advanced/src/modules/jwtHandler.ts @@ -0,0 +1,37 @@ +import jwt from "jsonwebtoken"; +import { tokenType } from "../constants"; + +const sign = (userId: number) => { + const payload = { + userId, + }; + + const accessToken = jwt.sign(payload, process.env.JWT_SECRET as string, { + expiresIn: "2h", + }); + return accessToken; +}; + +//* token 검사 +const verify = (token: string) => { + let decoded: string | jwt.JwtPayload; + + try { + decoded = jwt.verify(token, process.env.JWT_SECRET as string); + } catch (error: any) { + if (error.message === "jwt expired") { + return tokenType.TOKEN_EXPIRED; + } else if (error.message === "invalid token") { + return tokenType.TOKEN_INVALID; + } else { + return tokenType.TOKEN_INVALID; + } + } + + return decoded; +}; + +export default { + sign, + verify, +}; diff --git a/seminar6/advanced/src/router/contentRouter.ts b/seminar6/advanced/src/router/contentRouter.ts new file mode 100644 index 0000000..9e26704 --- /dev/null +++ b/seminar6/advanced/src/router/contentRouter.ts @@ -0,0 +1,20 @@ +import { Router } from "express"; +import { contentController } from "../controller"; +import auth from "../middleware/auth"; + +const router: Router = Router(); + +//* 콘텐츠 내용 조회 GET api/contents/:contentId +router.get("/:contentId", contentController.getContent); +//* 찜한 콘텐츠 생성 POST api/contents/like/:userId +router.post("/like/:userId", auth, contentController.createLikeContent); +//* 찜한 콘텐츠 조회 GET api/contents/like/:userId +router.get("/like/:userId", auth, contentController.getLikeContents); +//* 찜한 콘텐츠 삭제 DELETE api/contents/like/:likeContentId +router.delete( + "/like/:likeContentId", + auth, + contentController.deleteLikeContent +); + +export default router; diff --git a/seminar6/advanced/src/router/index.ts b/seminar6/advanced/src/router/index.ts new file mode 100644 index 0000000..de979d6 --- /dev/null +++ b/seminar6/advanced/src/router/index.ts @@ -0,0 +1,10 @@ +import { Router } from "express"; +import contentRouter from "./contentRouter"; +import userRouter from "./userRouter"; + +const router: Router = Router(); + +router.use("/users", userRouter); +router.use("/contents", contentRouter); + +export default router; diff --git a/seminar6/advanced/src/router/userRouter.ts b/seminar6/advanced/src/router/userRouter.ts new file mode 100644 index 0000000..620c89f --- /dev/null +++ b/seminar6/advanced/src/router/userRouter.ts @@ -0,0 +1,32 @@ +import { Router } from "express"; +import { body } from "express-validator"; +import { userController } from "../controller"; + +const router: Router = Router(); + +//* 유저 생성 - POST api/users +router.post( + "/", + [ + body("name").notEmpty(), + body("email").notEmpty(), + body("password").isLength({ min: 6 }), + ], + userController.createUser +); +//* 유저 로그인 - POST api/users/signin +router.post( + "/signin", + [ + body("email").notEmpty(), + body("email").isEmail(), + body("password").notEmpty(), + body("password").isLength({ min: 6 }), + ], + userController.signInUser +); + +//* 유저 이름 수정 PATCH /api/users/:userId +router.patch("/:userId", userController.updateUser); + +export default router; diff --git a/seminar6/advanced/src/service/contentService.ts b/seminar6/advanced/src/service/contentService.ts new file mode 100644 index 0000000..ef7c165 --- /dev/null +++ b/seminar6/advanced/src/service/contentService.ts @@ -0,0 +1,102 @@ +import { LikeContentCreateDto } from "./../interfaces/common/Content/LikeContentCreateDto"; +import { ContentResponseDto } from "./../interfaces/common/Content/ContentResponseDto"; +const { PrismaClient } = require("@prisma/client"); +const prisma = new PrismaClient(); + +const getContent = async ( + contentId: number, + episode: number +): Promise => { + const data = await prisma.Content.findMany({ + where: { + id: contentId, + }, + include: { + episode: { + where: { + episodeNumber: episode, + }, + }, + }, + }); + return data; +}; + +const getAllContentsInfo = async ( + contentId: number +): Promise => { + const data = await prisma.Content.findMany({ + where: { + id: contentId, + }, + include: { + episode: true, + }, + }); + return data; +}; + +const createLikeContent = async ( + userId: number, + likeContentCreateDto: LikeContentCreateDto +) => { + const data = await prisma.LikeContent.create({ + data: { + userId, + contentId: likeContentCreateDto.contentId, + }, + }); + return data; +}; + +const getLikeContents = async ( + userId: number +): Promise => { + const likeContents = await prisma.LikeContent.findMany({ + where: { + userId, + }, + select: { + content: { + select: { + id: true, + contentName: true, + genre: true, + ageLimit: true, + episode: true, + }, + }, + }, + }); + const data = await Promise.all( + likeContents.map((likeContent: any) => { + const result = { + id: likeContent.content.id, + contentId: likeContent.content.contentId, + contentName: likeContent.content.contentName, + genre: likeContent.content.genre, + ageLimit: likeContent.content.ageLimit, + episode: likeContent.content.episode, + }; + return result; + }) + ); + return data; +}; + +const deleteLikeContent = async (likeContentId: number) => { + const data = await prisma.LikeContent.delete({ + where: { id: likeContentId }, + }); + return data; +}; + +const contentService = { + getContent, + getAllContentsInfo, + createLikeContent, + getLikeContents, + deleteLikeContent, +}; + +export default contentService; diff --git a/seminar6/advanced/src/service/index.ts b/seminar6/advanced/src/service/index.ts new file mode 100644 index 0000000..e8f10fe --- /dev/null +++ b/seminar6/advanced/src/service/index.ts @@ -0,0 +1,4 @@ +import userService from "./userService"; +import contentService from "./contentService"; + +export { userService, contentService }; diff --git a/seminar6/advanced/src/service/userService.ts b/seminar6/advanced/src/service/userService.ts new file mode 100644 index 0000000..ea8f251 --- /dev/null +++ b/seminar6/advanced/src/service/userService.ts @@ -0,0 +1,66 @@ +import bcrypt from "bcryptjs"; +import { statusCode } from "../constants"; +import { UserSignInDTO } from "../interfaces/common/User/UserSignInDto"; + +import { UserCreateDTO } from "../interfaces/common/User/UserCreateDto"; +import { UserUpdateDTO } from "../interfaces/common/User/UserUpdateDto"; +const { PrismaClient } = require("@prisma/client"); +const prisma = new PrismaClient(); + +const createUser = async (userCreateDto: UserCreateDTO) => { + const salt = await bcrypt.genSalt(10); + const password = await bcrypt.hash(userCreateDto.password, salt); //^ 위에서 랜덤 생성한 salt를 이용해 초기화 + + const data = await prisma.user.create({ + data: { + userName: userCreateDto?.name, + age: userCreateDto?.age, + email: userCreateDto.email, + password, + }, + }); + + return data; +}; + +const signInUser = async (userSignInDto: UserSignInDTO) => { + try { + const user = await prisma.user.findFirst({ + where: { + email: userSignInDto.email, + }, + }); + if (!user) return null; + + //? bcrypt가 DB에 저장된 기존 password와 넘겨 받은 password를 대조하고, + //? match false시 401을 리턴 + const isMatch = await bcrypt.compare(userSignInDto.password, user.password); + if (!isMatch) return statusCode.UNAUTHORIZED; + + return user.id; + } catch (error) { + console.log(error); + throw error; + } +}; + +//* 유저의 정보(이름) 수정 +const updateUser = async (userId: number, userUpdateDTO: UserUpdateDTO) => { + const data = await prisma.user.update({ + where: { + id: userId, + }, + data: { + userName: userUpdateDTO.userName, + }, + }); + return data; +}; + +const userService = { + createUser, + signInUser, + updateUser, +}; + +export default userService; diff --git a/seminar6/advanced/tsconfig.json b/seminar6/advanced/tsconfig.json new file mode 100644 index 0000000..8075b2f --- /dev/null +++ b/seminar6/advanced/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es6", //? 어떤 버전으로 컴파일 + "allowSyntheticDefaultImports": true, //? default export가 없는 모듈에서 default imports를 허용 + "experimentalDecorators": true, //? decorator 실험적 허용 + "emitDecoratorMetadata": true, //? 데코레이터가 있는 선언에 대해 특정 타입의 메타 데이터를 내보내는 실험적인 지원 + "skipLibCheck": true, //? 정의 파일 타입 체크 여부 + "moduleResolution": "node", //? commonJS -> node 에서 동작 + "module": "commonjs", //? import 문법 + "strict": true, //? 타입 검사 엄격하게 + "pretty": true, //? error 메시지 예쁘게 + "sourceMap": true, //? 소스맵 파일 생성 -> .ts가 .js 파일로 트랜스 시 .js.map 생성 + "outDir": "./dist", //? 트랜스 파일 (.js) 저장 경로 + "allowJs": true, //? js 파일 ts에서 import 허용 + "esModuleInterop": true, //? ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져올 수 있게 허용 + "typeRoots": [ + "./src/types/express.d.ts", //? 타입(*.d.ts)파일을 가져올 디렉토리 설정 + "./node_modules/@types" //? 설정 안할시 기본적으로 ./node_modules/@types + ] + }, + "include": [ + "./src/**/*" //? build 시 포함 + ], + "exclude": [ + "node_modules", //? build 시 제외 + "tests" + ] +} diff --git a/seminar6/advanced/yarn.lock b/seminar6/advanced/yarn.lock new file mode 100644 index 0000000..286b505 --- /dev/null +++ b/seminar6/advanced/yarn.lock @@ -0,0 +1,826 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@prisma/client@^4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.5.0.tgz#f708549bee3da396d5741d846b4e4306b120210c" + integrity sha512-B2cV0OPI1smhdYUxsJoLYQLoMlLH06MUxgFUWQnHodGMX98VRVXKmQE/9OcrTNkqtke5RC+YU24Szxd04tZA2g== + dependencies: + "@prisma/engines-version" "4.5.0-43.0362da9eebca54d94c8ef5edd3b2e90af99ba452" + +"@prisma/engines-version@4.5.0-43.0362da9eebca54d94c8ef5edd3b2e90af99ba452": + version "4.5.0-43.0362da9eebca54d94c8ef5edd3b2e90af99ba452" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.5.0-43.0362da9eebca54d94c8ef5edd3b2e90af99ba452.tgz#5b7fae294ee9bd9790d0e7b7a0b0912e4222ac08" + integrity sha512-o7LyVx8PPJBLrEzLl6lpxxk2D5VnlM4Fwmrbq0NoT6pr5aa1OuHD9ZG+WJY6TlR/iD9bhmo2LNcxddCMr5Rv2A== + +"@prisma/engines@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.5.0.tgz#82df347a893a5ae2a67707d44772ba181f4b9328" + integrity sha512-4t9ir2SbQQr/wMCNU4YpHWp5hU14J2m3wHUZnGJPpmBF8YtkisxyVyQsKd1e6FyLTaGq8LOLhm6VLYHKqKNm+g== + +"@types/bcryptjs@^2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@types/bcryptjs/-/bcryptjs-2.4.2.tgz#e3530eac9dd136bfdfb0e43df2c4c5ce1f77dfae" + integrity sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ== + +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^4.17.18": + version "4.17.31" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz#a1139efeab4e7323834bb0226e62ac019f474b2f" + integrity sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express-validator@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/express-validator/-/express-validator-3.0.0.tgz#e6f3cab842e5ae376a1cfa11295202ec380b68d1" + integrity sha512-LusnB0YhTXpBT25PXyGPQlK7leE1e41Vezq1hHEUwjfkopM1Pkv2X2Ppxqh9c+w/HZ6Udzki8AJotKNjDTGdkQ== + dependencies: + express-validator "*" + +"@types/express@^4.17.14": + version "4.17.14" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" + integrity sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/jsonwebtoken@^8.5.9": + version "8.5.9" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz#2c064ecb0b3128d837d2764aa0b117b0ff6e4586" + integrity sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg== + dependencies: + "@types/node" "*" + +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + +"@types/node@*", "@types/node@^18.11.9": + version "18.11.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" + integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== + +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/serve-static@*": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" + integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== + dependencies: + "@types/mime" "*" + "@types/node" "*" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +bcryptjs@^2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" + integrity sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== + dependencies: + bytes "3.1.2" + content-type "~1.0.4" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +chokidar@^3.5.2: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +express-validator@*, express-validator@^6.14.2: + version "6.14.2" + resolved "https://registry.yarnpkg.com/express-validator/-/express-validator-6.14.2.tgz#6147893f7bec0e14162c3a88b3653121afc4678f" + integrity sha512-8XfAUrQ6Y7dIIuy9KcUPCfG/uCbvREctrxf5EeeME+ulanJ4iiW71lWmm9r4YcKKYOCBMan0WpVg7FtHu4Z4Wg== + dependencies: + lodash "^4.17.21" + validator "^13.7.0" + +express@^4.18.2: + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.1" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-intrinsic@^1.0.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.3, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +nodemon@^2.0.20: + version "2.0.20" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.20.tgz#e3537de768a492e8d74da5c5813cb0c7486fc701" + integrity sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw== + dependencies: + chokidar "^3.5.2" + debug "^3.2.7" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^5.7.1" + simple-update-notifier "^1.0.7" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== + dependencies: + abbrev "1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +prisma@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.5.0.tgz#361ae3f4476d0821b97645e5da42975a7c2943bb" + integrity sha512-9Aeg4qiKlv9Wsjz4NO8k2CzRzlvS3A4FYVJ5+28sBBZ0eEwbiVOE/Jj7v6rZC1tFW2s4GSICQOAyuOjc6WsNew== + dependencies: + "@prisma/engines" "4.5.0" + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +safe-buffer@5.2.1, safe-buffer@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^5.6.0, semver@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@~7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +simple-update-notifier@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz#7edf75c5bdd04f88828d632f762b2bc32996a9cc" + integrity sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew== + dependencies: + semver "~7.0.0" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +validator@^13.7.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" + integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== diff --git a/seminar6/basic/nodemon.json b/seminar6/basic/nodemon.json new file mode 100644 index 0000000..bd415b2 --- /dev/null +++ b/seminar6/basic/nodemon.json @@ -0,0 +1,6 @@ +{ + "watch": ["src", ".env"], + "ext": "js,ts,json", + "ignore": ["src/**/*.spec.ts"], + "exec": "ts-node --transpile-only ./src/index.ts" +} diff --git a/seminar6/basic/package.json b/seminar6/basic/package.json new file mode 100644 index 0000000..83772f5 --- /dev/null +++ b/seminar6/basic/package.json @@ -0,0 +1,29 @@ +{ + "name": "seminar4", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "dev": "nodemon", + "build": "tsc && node dist", + "db:pull": "npx prisma db pull", + "db:push": "npx prisma db push", + "generate": "npx prisma generate" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.2", + "@types/express": "^4.17.14", + "@types/express-validator": "^3.0.0", + "@types/jsonwebtoken": "^8.5.9", + "@types/node": "^18.11.9", + "nodemon": "^2.0.20" + }, + "dependencies": { + "@prisma/client": "^4.5.0", + "bcryptjs": "^2.4.3", + "express": "^4.18.2", + "express-validator": "^6.14.2", + "jsonwebtoken": "^8.5.1", + "prisma": "^4.5.0" + } +} diff --git a/seminar6/basic/prisma/schema.prisma b/seminar6/basic/prisma/schema.prisma new file mode 100644 index 0000000..d6c6ecb --- /dev/null +++ b/seminar6/basic/prisma/schema.prisma @@ -0,0 +1,16 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id Int @id(map: "user_pk") @unique(map: "user_id_uindex") @default(autoincrement()) + userName String @db.VarChar(100) + age Int? + email String @db.VarChar(400) + password String @db.VarChar(400) +} diff --git a/seminar6/basic/src/constants/index.ts b/seminar6/basic/src/constants/index.ts new file mode 100644 index 0000000..eaeae62 --- /dev/null +++ b/seminar6/basic/src/constants/index.ts @@ -0,0 +1,3 @@ +export { default as message } from "./responseMessage"; +export { default as statusCode } from "./statusCode"; +export { default as tokenType } from "./tokenType"; diff --git a/seminar6/basic/src/constants/response.ts b/seminar6/basic/src/constants/response.ts new file mode 100644 index 0000000..1441aab --- /dev/null +++ b/seminar6/basic/src/constants/response.ts @@ -0,0 +1,16 @@ +export const success = (status: number, message: string, data?: any) => { + return { + status, + success: true, + message, + data, + }; +}; + +export const fail = (status: number, message: string) => { + return { + status, + success: false, + message, + }; +}; diff --git a/seminar6/basic/src/constants/responseMessage.ts b/seminar6/basic/src/constants/responseMessage.ts new file mode 100644 index 0000000..e2b661c --- /dev/null +++ b/seminar6/basic/src/constants/responseMessage.ts @@ -0,0 +1,36 @@ +export default { + NULL_VALUE: "필요한 값이 없습니다.", + OUT_OF_VALUE: "파라미터 값이 잘못되었습니다.", + NOT_FOUND: "잘못된 경로입니다.", + BAD_REQUEST: "잘못된 요청입니다.", + + // 회원가입 및 로그인 + SIGNUP_SUCCESS: "회원 가입 성공", + SIGNUP_FAIL: "회원 가입 실패", + SIGNIN_SUCCESS: "로그인 성공", + SIGNIN_FAIL: "로그인 실패", + ALREADY_NICKNAME: "이미 사용중인 닉네임입니다.", + INVALID_PASSWORD: "잘못된 비밀번호입니다.", + + // 유저 + READ_USER_SUCCESS: "유저 조회 성공", + READ_USER_FAIL: "유저 조회 실패", + READ_ALL_USERS_SUCCESS: "모든 유저 조회 성공", + READ_ALL_USERS_FAIL: "모든 유저 조회 실패", + UPDATE_USER_SUCCESS: "유저 수정 성공", + UPDATE_USER_FAIL: "유저 수정 실패", + DELETE_USER_SUCCESS: "유저 탈퇴 성공", + DELETE_USER_FAIL: "유저 탈퇴 실패", + NO_USER: "탈퇴했거나 가입하지 않은 유저입니다.", + + // 토큰 + CREATE_TOKEN_SUCCESS: "토큰 재발급 성공", + EXPIRED_TOKEN: "토큰이 만료되었습니다.", + EXPIRED_ALL_TOKEN: "모든 토큰이 만료되었습니다.", + INVALID_TOKEN: "유효하지 않은 토큰입니다.", + VALID_TOKEN: "유효한 토큰입니다.", + EMPTY_TOKEN: "토큰 값이 없습니다.", + + // 서버 내 오류 + INTERNAL_SERVER_ERROR: "서버 내 오류", +}; diff --git a/seminar6/basic/src/constants/statusCode.ts b/seminar6/basic/src/constants/statusCode.ts new file mode 100644 index 0000000..c9f04ce --- /dev/null +++ b/seminar6/basic/src/constants/statusCode.ts @@ -0,0 +1,15 @@ +export default { + OK: 200, // 목록, 상세, 수정 성공 + CREATED: 201, // POST나 PUT으로 데이터 등록할 경우 사용 + // 어떠한 생성 작업을 요청받아 생성 작업을 성공 + ACCEPTED: 202, // 요청은 받았지만, 아직 동작을 수행하지 않은 상태로 요청이 적절함을 의미한다. + NO_CONTENT: 204, // 요청이 성공은 했지만 응답할 콘텐츠가 없을 경우를 뜻한다. + BAD_REQUEST: 400, // 클라이언트가 올바르지 못한 요청을 보내고 있음을 의미 + UNAUTHORIZED: 401, // 로그인을 하지 않아 권한이 없음. 권한 인증 요구 + FORBIDDEN: 403, // 요청이 서버에 의해 거부 되었음을 의미. 금지된 페이지 + NOT_FOUND: 404, // 요청한 URL을 찾을 수 없음을 의미 + CONFLICT: 409, // 클라이언트 요청에 대해 서버에서 충돌 요소가 발생 할수 있음을 의미 + INTERNAL_SERVER_ERROR: 500, // 서버에 오류가 발생하여 응답 할 수 없음을 의미 + SERVICE_UNAVAILABLE: 503, // 현재 서버가 유지보수 등의 이유로 일시적인 사용 불가함을 의미 + DB_ERROR: 600, +}; diff --git a/seminar6/basic/src/constants/tokenType.ts b/seminar6/basic/src/constants/tokenType.ts new file mode 100644 index 0000000..23cde90 --- /dev/null +++ b/seminar6/basic/src/constants/tokenType.ts @@ -0,0 +1,4 @@ +export default { + TOKEN_EXPIRED: -3, + TOKEN_INVALID: -2, +}; diff --git a/seminar6/basic/src/controller/index.ts b/seminar6/basic/src/controller/index.ts new file mode 100644 index 0000000..d2ddc5f --- /dev/null +++ b/seminar6/basic/src/controller/index.ts @@ -0,0 +1 @@ +export { default as userController } from "./userController"; diff --git a/seminar6/basic/src/controller/userController.ts b/seminar6/basic/src/controller/userController.ts new file mode 100644 index 0000000..96f43ba --- /dev/null +++ b/seminar6/basic/src/controller/userController.ts @@ -0,0 +1,146 @@ +import { Request, Response } from "express"; +import { message, statusCode } from "../constants"; +import { success, fail } from "../constants/response"; + +import { userService } from "../service"; +import { UserUpdateDTO } from "./../interfaces/common/UserUpdateDto"; +import { UserSignInDTO } from "../interfaces/common/UserSignInDto"; +import { UserCreateDTO } from "../interfaces/common/UserCreateDto"; + +import { validationResult } from "express-validator"; +import jwtHandler from "../modules/jwtHandler"; + +//* 유저 생성 +const createUser = async (req: Request, res: Response) => { + //? validation의 결과를 바탕으로 분기 처리 + const error = validationResult(req); + if (!error.isEmpty()) { + return res + .status(statusCode.BAD_REQUEST) + .send(fail(statusCode.BAD_REQUEST, message.BAD_REQUEST)); + } + //? 기존 비구조화 할당 방식 -> DTO의 형태 + const userCreateDto: UserCreateDTO = req.body; + const data = await userService.createUser(userCreateDto); + + if (!data) { + return res + .status(statusCode.BAD_REQUEST) + .send(fail(statusCode.BAD_REQUEST, message.SIGNUP_FAIL)); + } + const accessToken = jwtHandler.sign(data.id); + + const result = { + id: data.id, + name: data.userName, + accessToken, + }; + + return res + .status(statusCode.CREATED) + .send(success(statusCode.CREATED, message.SIGNUP_SUCCESS, result)); +}; + +//* 로그인 +const signInUser = async (req: Request, res: Response) => { + const error = validationResult(req); + if (!error.isEmpty()) { + return res + .status(statusCode.BAD_REQUEST) + .send(fail(statusCode.BAD_REQUEST, message.BAD_REQUEST)); + } + + const userSignInDto: UserSignInDTO = req.body; + + try { + const userId = await userService.signIn(userSignInDto); + + if (!userId) + return res + .status(statusCode.NOT_FOUND) + .send(fail(statusCode.NOT_FOUND, message.NOT_FOUND)); + else if (userId === statusCode.UNAUTHORIZED) + return res + .status(statusCode.UNAUTHORIZED) + .send(fail(statusCode.UNAUTHORIZED, message.INVALID_PASSWORD)); + + const accessToken = jwtHandler.sign(userId); + + const result = { + id: userId, + accessToken, + }; + + res + .status(statusCode.OK) + .send(success(statusCode.OK, message.SIGNIN_SUCCESS, result)); + } catch (e) { + console.log(error); + //? 서버 내부에서 오류 발생 + res + .status(statusCode.INTERNAL_SERVER_ERROR) + .send( + fail(statusCode.INTERNAL_SERVER_ERROR, message.INTERNAL_SERVER_ERROR) + ); + } +}; + +//* 유저 전체 조회 +const getAllUser = async (req: Request, res: Response) => { + const data = await userService.getAllUser(); + return res + .status(statusCode.OK) + .send(success(statusCode.OK, message.READ_ALL_USERS_SUCCESS, data)); +}; + +//* 유저 정보 업데이트 +const updateUser = async (req: Request, res: Response) => { + const userUpdateDto: UserUpdateDTO = req.body; + const { userId } = req.params; + + if (!userUpdateDto) { + return res + .status(statusCode.BAD_REQUEST) + .send(fail(statusCode.BAD_REQUEST, message.UPDATE_USER_FAIL)); + } + const updatedUser = await userService.updateUser(+userId, userUpdateDto); + return res + .status(statusCode.OK) + .send(success(statusCode.OK, message.UPDATE_USER_SUCCESS, updatedUser)); +}; + +//* 유저 삭제 +const deleteUser = async (req: Request, res: Response) => { + const { userId } = req.params; + + await userService.deleteUser(+userId); + return res + .status(statusCode.OK) + .send(success(statusCode.OK, message.DELETE_USER_SUCCESS)); +}; + +const getUserById = async (req: Request, res: Response) => { + const { userId } = req.params; + + const data = await userService.getUserById(+userId); + + if (!data) { + return res + .status(statusCode.NOT_FOUND) + .send(fail(statusCode.NOT_FOUND, message.READ_USER_FAIL)); + } + return res + .status(statusCode.OK) + .send(success(statusCode.OK, message.READ_USER_SUCCESS, data)); +}; + +const userController = { + createUser, + signInUser, + getAllUser, + updateUser, + deleteUser, + getUserById, +}; + +export default userController; diff --git a/seminar6/basic/src/index.ts b/seminar6/basic/src/index.ts new file mode 100644 index 0000000..e7a0809 --- /dev/null +++ b/seminar6/basic/src/index.ts @@ -0,0 +1,24 @@ +import express, { NextFunction, Request, Response } from "express"; +import router from "./router"; + +const app = express(); // express 객체 받아옴 +const PORT = 3000; // 사용할 port를 3000번으로 설정 + +app.use(express.json()); // express 에서 request body를 json 으로 받아오겠다. + +app.use("/api", router); // use -> 모든 요청 +// localhost:8000/api -> api 폴더 +// localhost:8000/api/user -> user.ts + +//* HTTP method - GET +app.get("/", (req: Request, res: Response, next: NextFunction) => { + res.send("마! 이게 서버다!!!!!!!!!!!!!!!!!!!!"); +}); + +app.listen(PORT, () => { + console.log(` + ############################################# + 🛡️ Server listening on port: ${PORT} 🛡️ + ############################################# + `); +}); // 8000 번 포트에서 서버를 실행하겠다! diff --git a/seminar6/basic/src/interfaces/common/UserCreateDto.ts b/seminar6/basic/src/interfaces/common/UserCreateDto.ts new file mode 100644 index 0000000..17d77c9 --- /dev/null +++ b/seminar6/basic/src/interfaces/common/UserCreateDto.ts @@ -0,0 +1,6 @@ +export interface UserCreateDTO { + name: string; + age: number; + email: string; + password: string; +} diff --git a/seminar6/basic/src/interfaces/common/UserResponseDto.ts b/seminar6/basic/src/interfaces/common/UserResponseDto.ts new file mode 100644 index 0000000..b8988d7 --- /dev/null +++ b/seminar6/basic/src/interfaces/common/UserResponseDto.ts @@ -0,0 +1,7 @@ +export interface UserResponseDTO { + id: number; + userName: string; + age: number | null; + email: string; + password: string; +} diff --git a/seminar6/basic/src/interfaces/common/UserSignInDto.ts b/seminar6/basic/src/interfaces/common/UserSignInDto.ts new file mode 100644 index 0000000..47fde5a --- /dev/null +++ b/seminar6/basic/src/interfaces/common/UserSignInDto.ts @@ -0,0 +1,4 @@ +export interface UserSignInDTO { + email: string; + password: string; +} diff --git a/seminar6/basic/src/interfaces/common/UserUpdateDto.ts b/seminar6/basic/src/interfaces/common/UserUpdateDto.ts new file mode 100644 index 0000000..a41c657 --- /dev/null +++ b/seminar6/basic/src/interfaces/common/UserUpdateDto.ts @@ -0,0 +1,6 @@ +export interface UserUpdateDTO { + userName?: string; + age?: number; + email?: string; + password?: string; +} diff --git a/seminar6/basic/src/middlewares/auth.ts b/seminar6/basic/src/middlewares/auth.ts new file mode 100644 index 0000000..380dfd1 --- /dev/null +++ b/seminar6/basic/src/middlewares/auth.ts @@ -0,0 +1,46 @@ +import { NextFunction, Request, Response } from "express"; +import { JwtPayload } from "jsonwebtoken"; +import { message, statusCode } from "../constants"; +import { fail } from "../constants/response"; +import tokenType from "../constants/tokenType"; +import jwtHandler from "../modules/jwtHandler"; + +export default async (req: Request, res: Response, next: NextFunction) => { + const token = req.headers.authorization?.split(" ").reverse()[0]; //? Bearer ~~ 에서 토큰만 파싱 + if (!token) + return res + .status(statusCode.UNAUTHORIZED) + .send(fail(statusCode.UNAUTHORIZED, message.EMPTY_TOKEN)); + + try { + const decoded = jwtHandler.verify(token); //? jwtHandler에서 만들어둔 verify로 토큰 검사 + + //? 토큰 에러 분기 처리 + if (decoded === tokenType.TOKEN_EXPIRED) + return res + .status(statusCode.UNAUTHORIZED) + .send(fail(statusCode.UNAUTHORIZED, message.EXPIRED_TOKEN)); + if (decoded === tokenType.TOKEN_INVALID) + return res + .status(statusCode.UNAUTHORIZED) + .send(fail(statusCode.UNAUTHORIZED, message.INVALID_TOKEN)); + + //? decode한 후 담겨있는 userId를 꺼내옴 + const userId: number = (decoded as JwtPayload).userId; + if (!userId) + return res + .status(statusCode.UNAUTHORIZED) + .send(fail(statusCode.UNAUTHORIZED, message.INVALID_TOKEN)); + + //? 얻어낸 userId 를 Request Body 내 userId 필드에 담고, 다음 미들웨어로 넘김( next() ) + req.body.userId = userId; + next(); + } catch (error) { + console.log(error); + res + .status(statusCode.INTERNAL_SERVER_ERROR) + .send( + fail(statusCode.INTERNAL_SERVER_ERROR, message.INTERNAL_SERVER_ERROR) + ); + } +}; diff --git a/seminar6/basic/src/middlewares/index.ts b/seminar6/basic/src/middlewares/index.ts new file mode 100644 index 0000000..0d3b686 --- /dev/null +++ b/seminar6/basic/src/middlewares/index.ts @@ -0,0 +1 @@ +export { default as auth } from "./auth"; diff --git a/seminar6/basic/src/modules/jwtHandler.ts b/seminar6/basic/src/modules/jwtHandler.ts new file mode 100644 index 0000000..339f6fa --- /dev/null +++ b/seminar6/basic/src/modules/jwtHandler.ts @@ -0,0 +1,38 @@ +import jwt from "jsonwebtoken"; +import { tokenType } from "../constants"; + +//* 받아온 userId를 담는 access token 생성 +const sign = (userId: number) => { + const payload = { + userId, + }; + + const accessToken = jwt.sign(payload, process.env.JWT_SECRET as string, { + expiresIn: "2h", + }); + return accessToken; +}; + +//* token 검사! +const verify = (token: string) => { + let decoded: string | jwt.JwtPayload; + + try { + decoded = jwt.verify(token, process.env.JWT_SECRET as string); + } catch (error: any) { + if (error.message === "jwt expired") { + return tokenType.TOKEN_EXPIRED; + } else if (error.message === "invalid token") { + return tokenType.TOKEN_INVALID; + } else { + return tokenType.TOKEN_INVALID; + } + } + + return decoded; +}; + +export default { + sign, + verify, +}; diff --git a/seminar6/basic/src/router/index.ts b/seminar6/basic/src/router/index.ts new file mode 100644 index 0000000..9974b16 --- /dev/null +++ b/seminar6/basic/src/router/index.ts @@ -0,0 +1,8 @@ +import { Router } from "express"; +import userRouter from "./userRouter"; + +const router: Router = Router(); + +router.use("/user", userRouter); + +export default router; diff --git a/seminar6/basic/src/router/userRouter.ts b/seminar6/basic/src/router/userRouter.ts new file mode 100644 index 0000000..06c0925 --- /dev/null +++ b/seminar6/basic/src/router/userRouter.ts @@ -0,0 +1,43 @@ +import { Router } from "express"; +import { body } from "express-validator"; +import { userController } from "../controller"; +import { auth } from "../middlewares"; + +const router: Router = Router(); + +router.get("/:userId", auth, userController.getUserById); + +//* 회원가입 - POST api/user +router.post( + "/", + [ + body("name").notEmpty(), + body("email").notEmpty(), + body("password").isLength({ min: 6 }), + ], + auth, + userController.createUser +); + +//* 로그인 - POST api/user/signin +router.post( + "/signin", + [ + body("email").notEmpty(), + body("email").isEmail(), + body("password").notEmpty(), + body("password").isLength({ min: 6 }), + ], + userController.signInUser +); + +//* 전체 유저 조회 - GET api/user +router.get("/", userController.getAllUser); + +//* 유저 정보 업데이트 - PATCH api/user/:userId +router.patch("/:userId", auth, userController.updateUser); + +//* 유저 삭제 - DELETE api/user/:userId +router.delete("/:userId", auth, userController.deleteUser); + +export default router; diff --git a/seminar6/basic/src/service/index.ts b/seminar6/basic/src/service/index.ts new file mode 100644 index 0000000..67d995e --- /dev/null +++ b/seminar6/basic/src/service/index.ts @@ -0,0 +1 @@ +export { default as userService } from "./userService"; diff --git a/seminar6/basic/src/service/userService.ts b/seminar6/basic/src/service/userService.ts new file mode 100644 index 0000000..052954a --- /dev/null +++ b/seminar6/basic/src/service/userService.ts @@ -0,0 +1,102 @@ +import bcrypt from "bcryptjs"; + +import { PrismaClient } from "@prisma/client"; +import { statusCode } from "../constants"; + +import { UserCreateDTO } from "../interfaces/common/UserCreateDto"; +import { UserUpdateDTO } from "./../interfaces/common/UserUpdateDto"; +import { UserResponseDTO } from "./../interfaces/common/UserResponseDto"; +import { UserSignInDTO } from "../interfaces/common/UserSignInDto"; + +const prisma = new PrismaClient(); + +//* 유저 생성 +const createUser = async (userCreateDto: UserCreateDTO) => { + //? 넘겨받은 password를 bcrypt의 도움을 받아 암호화 + const salt = await bcrypt.genSalt(10); //^ 매우 작은 임의의 랜덤 텍스트 salt + const password = await bcrypt.hash(userCreateDto.password, salt); //^ 위에서 랜덤을 생성한 salt를 이용해 암호화 + + const data = await prisma.user.create({ + data: { + userName: userCreateDto?.name, + age: userCreateDto?.age, + email: userCreateDto.email, + password, + }, + }); + + return data; +}; + +//* 로그인 +const signIn = async (userSignInDto: UserSignInDTO) => { + try { + const user = await prisma.user.findFirst({ + where: { + email: userSignInDto.email, + }, + }); + if (!user) return null; + + //? bcrypt가 DB에 저장된 기존 password와 넘겨 받은 password를 대조하고, + //? match false시 401을 리턴 + const isMatch = await bcrypt.compare(userSignInDto.password, user.password); + if (!isMatch) return statusCode.UNAUTHORIZED; + + return user.id; + } catch (error) { + console.log(error); + throw error; + } +}; + +//* 유저 전체 조회 +const getAllUser = async (): Promise => { + const data = await prisma.user.findMany(); + return data; +}; + +//* 유저 정보 업데이트 +const updateUser = async (userId: number, userUpdateDto: UserUpdateDTO) => { + const data = await prisma.user.update({ + where: { + id: userId, + }, + data: { + ...userUpdateDto, + }, + }); + return data; +}; + +//* 유저 삭제 +const deleteUser = async (userId: number) => { + await prisma.user.delete({ + where: { + id: userId, + }, + }); +}; + +//* userId로 유저 조회 +const getUserById = async (userId: number): Promise => { + const user = await prisma.user.findUnique({ + where: { + id: userId, + }, + }); + if (!user) return null; + + return user; +}; + +const userService = { + createUser, + signIn, + getAllUser, + updateUser, + deleteUser, + getUserById, +}; + +export default userService; diff --git a/seminar6/basic/tsconfig.json b/seminar6/basic/tsconfig.json new file mode 100644 index 0000000..8075b2f --- /dev/null +++ b/seminar6/basic/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es6", //? 어떤 버전으로 컴파일 + "allowSyntheticDefaultImports": true, //? default export가 없는 모듈에서 default imports를 허용 + "experimentalDecorators": true, //? decorator 실험적 허용 + "emitDecoratorMetadata": true, //? 데코레이터가 있는 선언에 대해 특정 타입의 메타 데이터를 내보내는 실험적인 지원 + "skipLibCheck": true, //? 정의 파일 타입 체크 여부 + "moduleResolution": "node", //? commonJS -> node 에서 동작 + "module": "commonjs", //? import 문법 + "strict": true, //? 타입 검사 엄격하게 + "pretty": true, //? error 메시지 예쁘게 + "sourceMap": true, //? 소스맵 파일 생성 -> .ts가 .js 파일로 트랜스 시 .js.map 생성 + "outDir": "./dist", //? 트랜스 파일 (.js) 저장 경로 + "allowJs": true, //? js 파일 ts에서 import 허용 + "esModuleInterop": true, //? ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져올 수 있게 허용 + "typeRoots": [ + "./src/types/express.d.ts", //? 타입(*.d.ts)파일을 가져올 디렉토리 설정 + "./node_modules/@types" //? 설정 안할시 기본적으로 ./node_modules/@types + ] + }, + "include": [ + "./src/**/*" //? build 시 포함 + ], + "exclude": [ + "node_modules", //? build 시 제외 + "tests" + ] +} diff --git a/seminar6/basic/yarn.lock b/seminar6/basic/yarn.lock new file mode 100644 index 0000000..286b505 --- /dev/null +++ b/seminar6/basic/yarn.lock @@ -0,0 +1,826 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@prisma/client@^4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.5.0.tgz#f708549bee3da396d5741d846b4e4306b120210c" + integrity sha512-B2cV0OPI1smhdYUxsJoLYQLoMlLH06MUxgFUWQnHodGMX98VRVXKmQE/9OcrTNkqtke5RC+YU24Szxd04tZA2g== + dependencies: + "@prisma/engines-version" "4.5.0-43.0362da9eebca54d94c8ef5edd3b2e90af99ba452" + +"@prisma/engines-version@4.5.0-43.0362da9eebca54d94c8ef5edd3b2e90af99ba452": + version "4.5.0-43.0362da9eebca54d94c8ef5edd3b2e90af99ba452" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.5.0-43.0362da9eebca54d94c8ef5edd3b2e90af99ba452.tgz#5b7fae294ee9bd9790d0e7b7a0b0912e4222ac08" + integrity sha512-o7LyVx8PPJBLrEzLl6lpxxk2D5VnlM4Fwmrbq0NoT6pr5aa1OuHD9ZG+WJY6TlR/iD9bhmo2LNcxddCMr5Rv2A== + +"@prisma/engines@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.5.0.tgz#82df347a893a5ae2a67707d44772ba181f4b9328" + integrity sha512-4t9ir2SbQQr/wMCNU4YpHWp5hU14J2m3wHUZnGJPpmBF8YtkisxyVyQsKd1e6FyLTaGq8LOLhm6VLYHKqKNm+g== + +"@types/bcryptjs@^2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@types/bcryptjs/-/bcryptjs-2.4.2.tgz#e3530eac9dd136bfdfb0e43df2c4c5ce1f77dfae" + integrity sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ== + +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^4.17.18": + version "4.17.31" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz#a1139efeab4e7323834bb0226e62ac019f474b2f" + integrity sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express-validator@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/express-validator/-/express-validator-3.0.0.tgz#e6f3cab842e5ae376a1cfa11295202ec380b68d1" + integrity sha512-LusnB0YhTXpBT25PXyGPQlK7leE1e41Vezq1hHEUwjfkopM1Pkv2X2Ppxqh9c+w/HZ6Udzki8AJotKNjDTGdkQ== + dependencies: + express-validator "*" + +"@types/express@^4.17.14": + version "4.17.14" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" + integrity sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/jsonwebtoken@^8.5.9": + version "8.5.9" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz#2c064ecb0b3128d837d2764aa0b117b0ff6e4586" + integrity sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg== + dependencies: + "@types/node" "*" + +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + +"@types/node@*", "@types/node@^18.11.9": + version "18.11.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" + integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== + +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/serve-static@*": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" + integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== + dependencies: + "@types/mime" "*" + "@types/node" "*" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +bcryptjs@^2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" + integrity sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== + dependencies: + bytes "3.1.2" + content-type "~1.0.4" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +chokidar@^3.5.2: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +express-validator@*, express-validator@^6.14.2: + version "6.14.2" + resolved "https://registry.yarnpkg.com/express-validator/-/express-validator-6.14.2.tgz#6147893f7bec0e14162c3a88b3653121afc4678f" + integrity sha512-8XfAUrQ6Y7dIIuy9KcUPCfG/uCbvREctrxf5EeeME+ulanJ4iiW71lWmm9r4YcKKYOCBMan0WpVg7FtHu4Z4Wg== + dependencies: + lodash "^4.17.21" + validator "^13.7.0" + +express@^4.18.2: + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.1" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-intrinsic@^1.0.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.3, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +nodemon@^2.0.20: + version "2.0.20" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.20.tgz#e3537de768a492e8d74da5c5813cb0c7486fc701" + integrity sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw== + dependencies: + chokidar "^3.5.2" + debug "^3.2.7" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^5.7.1" + simple-update-notifier "^1.0.7" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== + dependencies: + abbrev "1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +prisma@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.5.0.tgz#361ae3f4476d0821b97645e5da42975a7c2943bb" + integrity sha512-9Aeg4qiKlv9Wsjz4NO8k2CzRzlvS3A4FYVJ5+28sBBZ0eEwbiVOE/Jj7v6rZC1tFW2s4GSICQOAyuOjc6WsNew== + dependencies: + "@prisma/engines" "4.5.0" + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +safe-buffer@5.2.1, safe-buffer@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^5.6.0, semver@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@~7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +simple-update-notifier@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz#7edf75c5bdd04f88828d632f762b2bc32996a9cc" + integrity sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew== + dependencies: + semver "~7.0.0" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +validator@^13.7.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" + integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==