diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..3498abd1 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6ed48a98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +node_modules diff --git a/README.md b/README.md index be09dfce..5072554e 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,7 @@ _위 이미지는 판다마켓의 대표 이미지입니다. 프로젝트가 진 - Express.js를 이용해 더 복잡한 백엔드 기능을 구현하는 미션입니다. 데이터베이스 연동, 인증 및 권한 관리 등 고급 API 설계가 포함됩니다. - **스프린트 미션 6부터 12까지**의 백엔드 내용이 들어 있어요. -> _스프린트 미션 내 프론트엔드 요구사항은 [프론트엔드 레포지토리](https://github.com/codeit-sprint-fullstack/5-Sprint-mission-FE)의 브랜치에서 관리해주세요_ +> _스프린트 미션 내 프론트엔드 요구사항은 [프론트엔드 레포지토리](https://github.com/codeit-sprint-fullstack/6-Sprint-mission-FE)의 브랜치에서 관리해주세요_ ---- 본 프로젝트는 [코드잇](https://www.codeit.kr)의 소유이며, 교육 목적으로만 사용됩니다. © 2024 Codeit. All rights reserved. diff --git a/dist/app.js b/dist/app.js new file mode 100644 index 00000000..c7e3d3e5 --- /dev/null +++ b/dist/app.js @@ -0,0 +1,39 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +var _a; +Object.defineProperty(exports, "__esModule", { value: true }); +require("dotenv/config"); +const express_1 = __importDefault(require("express")); +const cors_1 = __importDefault(require("cors")); +const cookie_parser_1 = __importDefault(require("cookie-parser")); +const passport_1 = __importDefault(require("./config/passport")); +const authController_1 = __importDefault(require("./controllers/authController")); +const userController_1 = __importDefault(require("./controllers/userController")); +const imageController_1 = __importDefault(require("./controllers/imageController")); +const productController_1 = __importDefault(require("./controllers/productController")); +const articleController_1 = __importDefault(require("./controllers/articleController")); +const commentController_1 = __importDefault(require("./controllers/commentController")); +const errorHandler_1 = __importDefault(require("./middlewares/errorHandler")); +const app = (0, express_1.default)(); +app.use((0, cors_1.default)({ + origin: "http://localhost:3001", // 프론트엔드 주소 + credentials: true, +})); +app.use(express_1.default.json()); +app.use((0, cookie_parser_1.default)()); +app.use(passport_1.default.initialize()); +app.use("/auth", authController_1.default); +app.use("/users", userController_1.default); +app.use("/products", productController_1.default); +app.use("/articles", articleController_1.default); +app.use("/uploads", express_1.default.static("uploads")); +app.use("/images", imageController_1.default); +app.use(commentController_1.default); +app.use(errorHandler_1.default); +const port = (_a = process.env.PORT) !== null && _a !== void 0 ? _a : 3000; +app.listen(port, () => { + console.log(`Server is running on port ${port}`); +}); +//# sourceMappingURL=app.js.map \ No newline at end of file diff --git a/dist/app.js.map b/dist/app.js.map new file mode 100644 index 00000000..4235c818 --- /dev/null +++ b/dist/app.js.map @@ -0,0 +1 @@ +{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":";;;;;;AAAA,yBAAuB;AACvB,sDAA4E;AAC5E,gDAAwB;AACxB,kEAAyC;AACzC,iEAAyC;AACzC,kFAA0D;AAC1D,kFAA0D;AAC1D,oFAA4D;AAC5D,wFAAgE;AAChE,wFAAgE;AAChE,wFAAgE;AAChE,8EAAsD;AAEtD,MAAM,GAAG,GAAY,IAAA,iBAAO,GAAE,CAAC;AAE/B,GAAG,CAAC,GAAG,CACL,IAAA,cAAI,EAAC;IACH,MAAM,EAAE,uBAAuB,EAAE,WAAW;IAC5C,WAAW,EAAE,IAAI;CAClB,CAAC,CACH,CAAC;AACF,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,EAAE,CAAC,CAAC;AACxB,GAAG,CAAC,GAAG,CAAC,IAAA,uBAAY,GAAE,CAAC,CAAC;AAExB,GAAG,CAAC,GAAG,CAAC,kBAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;AAE/B,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,wBAAc,CAAC,CAAC;AACjC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,wBAAc,CAAC,CAAC;AAClC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,2BAAiB,CAAC,CAAC;AACxC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,2BAAiB,CAAC,CAAC;AACxC,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,iBAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;AAC/C,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,yBAAe,CAAC,CAAC;AACpC,GAAG,CAAC,GAAG,CAAC,2BAAiB,CAAC,CAAC;AAC3B,GAAG,CAAC,GAAG,CAAC,sBAAY,CAAC,CAAC;AAEtB,MAAM,IAAI,GAAG,MAAA,OAAO,CAAC,GAAG,CAAC,IAAI,mCAAI,IAAI,CAAC;AACtC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IACpB,OAAO,CAAC,GAAG,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/dist/config/passport.js b/dist/config/passport.js new file mode 100644 index 00000000..b866e180 --- /dev/null +++ b/dist/config/passport.js @@ -0,0 +1,11 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const passport_1 = __importDefault(require("passport")); +const jwtStrategy_1 = __importDefault(require("../middlewares/passport/jwtStrategy")); +passport_1.default.use("access-token", jwtStrategy_1.default.accessTokenStrategy); +passport_1.default.use("refresh-token", jwtStrategy_1.default.refreshTokenStrategy); +exports.default = passport_1.default; +//# sourceMappingURL=passport.js.map \ No newline at end of file diff --git a/dist/config/passport.js.map b/dist/config/passport.js.map new file mode 100644 index 00000000..2bf0c5ca --- /dev/null +++ b/dist/config/passport.js.map @@ -0,0 +1 @@ +{"version":3,"file":"passport.js","sourceRoot":"","sources":["../../src/config/passport.ts"],"names":[],"mappings":";;;;;AAAA,wDAAgC;AAChC,sFAAsD;AAEtD,kBAAQ,CAAC,GAAG,CAAC,cAAc,EAAE,qBAAG,CAAC,mBAAmB,CAAC,CAAC;AACtD,kBAAQ,CAAC,GAAG,CAAC,eAAe,EAAE,qBAAG,CAAC,oBAAoB,CAAC,CAAC;AAExD,kBAAe,kBAAQ,CAAC"} \ No newline at end of file diff --git a/dist/config/prisma.js b/dist/config/prisma.js new file mode 100644 index 00000000..cb7a5ecb --- /dev/null +++ b/dist/config/prisma.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const client_1 = require("@prisma/client"); +const prisma = new client_1.PrismaClient(); +exports.default = prisma; +//# sourceMappingURL=prisma.js.map \ No newline at end of file diff --git a/dist/config/prisma.js.map b/dist/config/prisma.js.map new file mode 100644 index 00000000..1ab0d4fe --- /dev/null +++ b/dist/config/prisma.js.map @@ -0,0 +1 @@ +{"version":3,"file":"prisma.js","sourceRoot":"","sources":["../../src/config/prisma.ts"],"names":[],"mappings":";;AAAA,2CAA8C;AAE9C,MAAM,MAAM,GAAG,IAAI,qBAAY,EAAE,CAAC;AAElC,kBAAe,MAAM,CAAC"} \ No newline at end of file diff --git a/dist/controllers/articleController.js b/dist/controllers/articleController.js new file mode 100644 index 00000000..322efc09 --- /dev/null +++ b/dist/controllers/articleController.js @@ -0,0 +1,99 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = __importDefault(require("express")); +const articleService_1 = __importDefault(require("../services/articleService")); +const passport_1 = __importDefault(require("../config/passport")); +const asyncHandler_1 = require("../utils/asyncHandler"); +const CustomError_1 = require("../utils/CustomError"); +const articleController = express_1.default.Router(); +articleController.post("/", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const writerId = req.user.id; + const articleData = Object.assign(Object.assign({}, req.body), { writerId }); + if (!articleData.title || !articleData.content) { + throw new CustomError_1.CustomError(422, "제목과 내용은 필수입니다."); + } + const article = yield articleService_1.default.createArticle(articleData); + res.status(201).json(article); +}))); +articleController.get("/", (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + var _a; + const articles = yield articleService_1.default.getArticles(req.query, (_a = req.user) === null || _a === void 0 ? void 0 : _a.id); + res.json(articles); +}))); +articleController.get("/:articleId", (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + var _a; + const { articleId } = req.params; + const article = yield articleService_1.default.getArticleById(parseInt(articleId, 10), (_a = req.user) === null || _a === void 0 ? void 0 : _a.id); + res.json(article); +}))); +articleController.patch("/:articleId", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const { articleId } = req.params; + const writerId = req.user.id; + const updateData = req.body; + const updatedArticle = yield articleService_1.default.updateArticle(parseInt(articleId, 10), updateData, writerId); + res.json(updatedArticle); +}))); +articleController.delete("/:articleId", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const { articleId } = req.params; + const writerId = req.user.id; + yield articleService_1.default.deleteArticle(parseInt(articleId, 10), writerId); + res.status(204).send(); +}))); +articleController.post("/:articleId/like", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const { articleId } = req.params; + const userId = req.user.id; + const result = yield articleService_1.default.likeArticle(parseInt(articleId, 10), userId); + res + .status(201) + .json({ message: "게시글에 좋아요를 눌렀습니다.", data: result }); +}))); +articleController.delete("/:articleId/like", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const { articleId } = req.params; + const userId = req.user.id; + yield articleService_1.default.unlikeArticle(parseInt(articleId, 10), userId); + res.status(200).json({ message: "게시글 좋아요를 취소했습니다." }); +}))); +exports.default = articleController; +//# sourceMappingURL=articleController.js.map \ No newline at end of file diff --git a/dist/controllers/articleController.js.map b/dist/controllers/articleController.js.map new file mode 100644 index 00000000..c4b9fb72 --- /dev/null +++ b/dist/controllers/articleController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"articleController.js","sourceRoot":"","sources":["../../src/controllers/articleController.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,sDAAqD;AACrD,gFAAwD;AACxD,kEAA0C;AAC1C,wDAAqD;AACrD,sDAAmD;AASnD,MAAM,iBAAiB,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAC;AAE3C,iBAAiB,CAAC,IAAI,CACpB,GAAG,EACH,kBAAQ,CAAC,YAAY,CAAC,cAAc,EAAE;IACpC,OAAO,EAAE,KAAK;IACd,aAAa,EAAE,IAAI;CACpB,CAAC,EACF,IAAA,2BAAY,EAAC,CAAO,GAAY,EAAE,GAAa,EAAE,EAAE;IACjD,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,yBAAW,CACnB,GAAG,EACH,8BAA8B,CAC/B,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;IAC7B,MAAM,WAAW,mCAAQ,GAAG,CAAC,IAAI,KAAE,QAAQ,GAAE,CAAC;IAE9C,IAAI,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;QAC/C,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,wBAAc,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;IAChE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAChC,CAAC,CAAA,CAAC,CACH,CAAC;AAEF,iBAAiB,CAAC,GAAG,CACnB,GAAG,EACH,IAAA,2BAAY,EAAC,CAAO,GAAY,EAAE,GAAa,EAAE,EAAE;;IACjD,MAAM,QAAQ,GAAG,MAAM,wBAAc,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,MAAA,GAAG,CAAC,IAAI,0CAAE,EAAE,CAAC,CAAC;IAC3E,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACrB,CAAC,CAAA,CAAC,CACH,CAAC;AAEF,iBAAiB,CAAC,GAAG,CACnB,aAAa,EACb,IAAA,2BAAY,EAAC,CAAO,GAAY,EAAE,GAAa,EAAE,EAAE;;IACjD,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;IACjC,MAAM,OAAO,GAAG,MAAM,wBAAc,CAAC,cAAc,CACjD,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,EACvB,MAAA,GAAG,CAAC,IAAI,0CAAE,EAAE,CACb,CAAC;IACF,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACpB,CAAC,CAAA,CAAC,CACH,CAAC;AAEF,iBAAiB,CAAC,KAAK,CACrB,aAAa,EACb,kBAAQ,CAAC,YAAY,CAAC,cAAc,EAAE;IACpC,OAAO,EAAE,KAAK;IACd,aAAa,EAAE,IAAI;CACpB,CAAC,EACF,IAAA,2BAAY,EAAC,CAAO,GAAY,EAAE,GAAa,EAAE,EAAE;IACjD,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,yBAAW,CACnB,GAAG,EACH,8BAA8B,CAC/B,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;IACjC,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;IAC7B,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,CAAC;IAE5B,MAAM,cAAc,GAAG,MAAM,wBAAc,CAAC,aAAa,CACvD,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,EACvB,UAAU,EACV,QAAQ,CACT,CAAC;IACF,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AAC3B,CAAC,CAAA,CAAC,CACH,CAAC;AAEF,iBAAiB,CAAC,MAAM,CACtB,aAAa,EACb,kBAAQ,CAAC,YAAY,CAAC,cAAc,EAAE;IACpC,OAAO,EAAE,KAAK;IACd,aAAa,EAAE,IAAI;CACpB,CAAC,EACF,IAAA,2BAAY,EAAC,CAAO,GAAY,EAAE,GAAa,EAAE,EAAE;IACjD,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,yBAAW,CACnB,GAAG,EACH,8BAA8B,CAC/B,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;IACjC,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;IAE7B,MAAM,wBAAc,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;IACtE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC,CAAA,CAAC,CACH,CAAC;AAEF,iBAAiB,CAAC,IAAI,CACpB,kBAAkB,EAClB,kBAAQ,CAAC,YAAY,CAAC,cAAc,EAAE;IACpC,OAAO,EAAE,KAAK;IACd,aAAa,EAAE,IAAI;CACpB,CAAC,EACF,IAAA,2BAAY,EAAC,CAAO,GAAY,EAAE,GAAa,EAAE,EAAE;IACjD,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,yBAAW,CACnB,GAAG,EACH,8BAA8B,CAC/B,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;IACjC,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,MAAM,wBAAc,CAAC,WAAW,CAC7C,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,EACvB,MAAM,CACP,CAAC;IACF,GAAG;SACA,MAAM,CAAC,GAAG,CAAC;SACX,IAAI,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;AACzD,CAAC,CAAA,CAAC,CACH,CAAC;AAEF,iBAAiB,CAAC,MAAM,CACtB,kBAAkB,EAClB,kBAAQ,CAAC,YAAY,CAAC,cAAc,EAAE;IACpC,OAAO,EAAE,KAAK;IACd,aAAa,EAAE,IAAI;CACpB,CAAC,EACF,IAAA,2BAAY,EAAC,CAAO,GAAY,EAAE,GAAa,EAAE,EAAE;IACjD,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,yBAAW,CACnB,GAAG,EACH,8BAA8B,CAC/B,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;IACjC,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;IAC3B,MAAM,wBAAc,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;IACpE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC;AACxD,CAAC,CAAA,CAAC,CACH,CAAC;AAEF,kBAAe,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/controllers/authController.js b/dist/controllers/authController.js new file mode 100644 index 00000000..d796bb14 --- /dev/null +++ b/dist/controllers/authController.js @@ -0,0 +1,60 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = __importDefault(require("express")); +const authService_1 = __importDefault(require("../services/authService")); +const passport_1 = __importDefault(require("../config/passport")); +const asyncHandler_1 = require("../utils/asyncHandler"); +const CustomError_1 = require("../utils/CustomError"); +const authController = express_1.default.Router(); +authController.post("/signup", (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { nickname, email, password, passwordConfirmation } = req.body; + if (!nickname || !email || !password || !passwordConfirmation) { + throw new CustomError_1.CustomError(422, "닉네임, 이메일, 비밀번호, 비밀번호 확인은 필수입니다."); + } + const result = yield authService_1.default.signUpUser({ + nickname, + email, + password, + passwordConfirmation, + }); + res.status(201).json(result); +}))); +authController.post("/signin", (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { email, password } = req.body; + if (!email || !password) { + throw new CustomError_1.CustomError(422, "이메일과 비밀번호를 모두 입력해주세요."); + } + const { accessToken, refreshToken, user } = yield authService_1.default.signInUser(email, password); + res.cookie("refreshToken", refreshToken, { + httpOnly: true, + path: "/", + sameSite: "none", + secure: true, + }); + res.json({ accessToken, user }); +}))); +authController.post("/refresh-token", passport_1.default.authenticate("refresh-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + if (!req.user) { + throw new CustomError_1.CustomError(401, "토큰 갱신을 위한 사용자 정보가 없습니다."); + } + const user = req.user; + const newAccessToken = authService_1.default.generateNewAccessToken(user); + res.json({ accessToken: newAccessToken }); +}))); +exports.default = authController; +//# sourceMappingURL=authController.js.map \ No newline at end of file diff --git a/dist/controllers/authController.js.map b/dist/controllers/authController.js.map new file mode 100644 index 00000000..4a9f155d --- /dev/null +++ b/dist/controllers/authController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"authController.js","sourceRoot":"","sources":["../../src/controllers/authController.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,sDAAqD;AACrD,0EAAwE;AACxE,kEAA0C;AAC1C,wDAAqD;AACrD,sDAAmD;AAQnD,MAAM,cAAc,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAC;AAExC,cAAc,CAAC,IAAI,CACjB,SAAS,EACT,IAAA,2BAAY,EAAC,CAAO,GAAY,EAAE,GAAa,EAAE,EAAE;IACjD,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,oBAAoB,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;IAErE,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC9D,MAAM,IAAI,yBAAW,CACnB,GAAG,EACH,iCAAiC,CAClC,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,qBAAW,CAAC,UAAU,CAAC;QAC1C,QAAQ;QACR,KAAK;QACL,QAAQ;QACR,oBAAoB;KACrB,CAAC,CAAC;IAEH,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC/B,CAAC,CAAA,CAAC,CACH,CAAC;AAEF,cAAc,CAAC,IAAI,CACjB,SAAS,EACT,IAAA,2BAAY,EAAC,CAAO,GAAY,EAAE,GAAa,EAAE,EAAE;IACjD,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;IAErC,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxB,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,uBAAuB,CAAC,CAAC;IACtD,CAAC;IACD,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,IAAI,EAAE,GAAG,MAAM,qBAAW,CAAC,UAAU,CACtE,KAAK,EACL,QAAQ,CACT,CAAC;IAEF,GAAG,CAAC,MAAM,CAAC,cAAc,EAAE,YAAY,EAAE;QACvC,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,MAAM;QAChB,MAAM,EAAE,IAAI;KACb,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;AAClC,CAAC,CAAA,CAAC,CACH,CAAC;AAEF,cAAc,CAAC,IAAI,CACjB,gBAAgB,EAChB,kBAAQ,CAAC,YAAY,CAAC,eAAe,EAAE;IACrC,OAAO,EAAE,KAAK;IACd,aAAa,EAAE,IAAI;CACpB,CAAC,EACF,IAAA,2BAAY,EAAC,CAAO,GAAY,EAAE,GAAa,EAAE,EAAE;IACjD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACd,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,yBAAyB,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;IAEtB,MAAM,cAAc,GAAG,qBAAW,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAChE,GAAG,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAC;AAC5C,CAAC,CAAA,CAAC,CACH,CAAC;AAEF,kBAAe,cAAc,CAAC"} \ No newline at end of file diff --git a/dist/controllers/commentController.js b/dist/controllers/commentController.js new file mode 100644 index 00000000..4426deb5 --- /dev/null +++ b/dist/controllers/commentController.js @@ -0,0 +1,83 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = __importDefault(require("express")); +const commentService_1 = __importDefault(require("../services/commentService")); +const asyncHandler_1 = require("../utils/asyncHandler"); +const passport_1 = __importDefault(require("../config/passport")); +const CustomError_1 = require("../utils/CustomError"); +const commentController = express_1.default.Router(); +commentController.post("/products/:productId/comments", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { productId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const writerId = req.user.id; + const { content } = req.body; + const comment = yield commentService_1.default.createProductComment(productId, writerId, content); + res.status(201).json(comment); +}))); +commentController.get("/products/:productId/comments", (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { productId } = req.params; + const comments = yield commentService_1.default.getProductComments(productId, req.query); + res.json(comments); +}))); +commentController.post("/articles/:articleId/comments", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { articleId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const writerId = req.user.id; + const { content } = req.body; + const comment = yield commentService_1.default.createArticleComment(parseInt(articleId, 10), writerId, content); + res.status(201).json(comment); +}))); +commentController.get("/articles/:articleId/comments", (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { articleId } = req.params; + const comments = yield commentService_1.default.getArticleComments(parseInt(articleId, 10), req.query); + res.json(comments); +}))); +commentController.patch("/comments/:commentId", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { commentId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const writerId = req.user.id; + const { content } = req.body; + const updatedComment = yield commentService_1.default.updateComment(parseInt(commentId, 10), content, writerId); + res.json(updatedComment); +}))); +commentController.delete("/comments/:commentId", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { commentId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const writerId = req.user.id; + yield commentService_1.default.deleteComment(parseInt(commentId, 10), writerId); + res.status(204).send(); +}))); +exports.default = commentController; +//# sourceMappingURL=commentController.js.map \ No newline at end of file diff --git a/dist/controllers/commentController.js.map b/dist/controllers/commentController.js.map new file mode 100644 index 00000000..796d15ae --- /dev/null +++ b/dist/controllers/commentController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"commentController.js","sourceRoot":"","sources":["../../src/controllers/commentController.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/controllers/imageController.js b/dist/controllers/imageController.js new file mode 100644 index 00000000..ea5466f4 --- /dev/null +++ b/dist/controllers/imageController.js @@ -0,0 +1,37 @@ +"use strict"; +// src/controllers/imageController.ts +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = __importDefault(require("express")); +const asyncHandler_1 = require("../utils/asyncHandler"); +const passport_1 = __importDefault(require("../config/passport")); +const CustomError_1 = require("../utils/CustomError"); +const uploadMiddleware_1 = __importDefault(require("../middlewares/uploadMiddleware")); +const imageController = express_1.default.Router(); +imageController.post("/upload", passport_1.default.authenticate("access-token", { session: false }), uploadMiddleware_1.default.array("image", 5), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + const files = req.files; + if (!files || !Array.isArray(files) || files.length === 0) { + throw new CustomError_1.CustomError(400, "업로드할 이미지 파일들이 없습니다."); + } + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증된 사용자만 이미지를 업로드할 수 있습니다."); + } + const uploaderId = req.user.id; + res.status(201).json({ + message: "이미지들이 성공적으로 업로드되었습니다.", + imageUrl: files.map((file) => file.location), + }); +}))); +exports.default = imageController; +//# sourceMappingURL=imageController.js.map \ No newline at end of file diff --git a/dist/controllers/imageController.js.map b/dist/controllers/imageController.js.map new file mode 100644 index 00000000..b9cb1953 --- /dev/null +++ b/dist/controllers/imageController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"imageController.js","sourceRoot":"","sources":["../../src/controllers/imageController.ts"],"names":[],"mappings":";AAAA,qCAAqC;;;;;;;;;;;;;;AAErC,sDAAqD;AACrD,wDAAqD;AACrD,kEAA0C;AAC1C,sDAAmD;AACnD,uFAA+D;AAW/D,MAAM,eAAe,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAC;AAEzC,eAAe,CAAC,IAAI,CAClB,SAAS,EACT,kBAAQ,CAAC,YAAY,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EACzD,0BAAgB,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,EAClC,IAAA,2BAAY,EAAC,CAAO,GAAY,EAAE,GAAa,EAAE,EAAE;IACjD,MAAM,KAAK,GAAG,GAAG,CAAC,KAA2C,CAAC;IAC9D,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,yBAAW,CACnB,GAAG,EACH,4BAA4B,CAC7B,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;IAE/B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;QACnB,OAAO,EAAE,uBAAuB;QAChC,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC;KAC7C,CAAC,CAAC;AACL,CAAC,CAAA,CAAC,CACH,CAAC;AAEF,kBAAe,eAAe,CAAC"} \ No newline at end of file diff --git a/dist/controllers/productController.js b/dist/controllers/productController.js new file mode 100644 index 00000000..2a147979 --- /dev/null +++ b/dist/controllers/productController.js @@ -0,0 +1,101 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = __importDefault(require("express")); +const passport_1 = __importDefault(require("../config/passport")); +const productService_1 = __importDefault(require("../services/productService")); +const asyncHandler_1 = require("../utils/asyncHandler"); +const CustomError_1 = require("../utils/CustomError"); +const productController = express_1.default.Router(); +productController.post("/", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const ownerId = req.user.id; + if (!req.body.name || req.body.price === undefined) { + throw new CustomError_1.CustomError(422, "상품 이름과 가격은 필수입니다."); + } + const createdProduct = yield productService_1.default.createProduct(req.body, ownerId); + res.status(201).json(createdProduct); +}))); +productController.get("/", (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + var _a; + const products = yield productService_1.default.getProducts(req.query, (_a = req.user) === null || _a === void 0 ? void 0 : _a.id); + res.json(products); +}))); +productController.get("/:productId", (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + var _a; + const { productId } = req.params; + const product = yield productService_1.default.getProductById(productId, (_a = req.user) === null || _a === void 0 ? void 0 : _a.id); + res.json(product); +}))); +productController.patch("/:productId", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { productId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const requesterId = req.user.id; + const updateData = req.body; + if (Object.keys(updateData).length === 0) { + throw new CustomError_1.CustomError(400, "수정할 내용이 없습니다."); + } + const updatedProduct = yield productService_1.default.updateProduct(productId, updateData, requesterId); + res.json(updatedProduct); +}))); +productController.delete("/:productId", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { productId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const requesterId = req.user.id; + yield productService_1.default.deleteProduct(productId, requesterId); + res.status(204).send(); +}))); +productController.post("/:productId/favorite", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { productId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const userId = req.user.id; + const result = yield productService_1.default.addFavorite(productId, userId); + res + .status(201) + .json({ message: "상품을 즐겨찾기에 추가했습니다.", data: result }); +}))); +productController.delete("/:productId/favorite", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { productId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const userId = req.user.id; + yield productService_1.default.removeFavorite(productId, userId); + res.status(200).json({ message: "상품 즐겨찾기를 삭제했습니다." }); +}))); +exports.default = productController; +//# sourceMappingURL=productController.js.map \ No newline at end of file diff --git a/dist/controllers/productController.js.map b/dist/controllers/productController.js.map new file mode 100644 index 00000000..5648a342 --- /dev/null +++ b/dist/controllers/productController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"productController.js","sourceRoot":"","sources":["../../src/controllers/productController.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/controllers/userController.js b/dist/controllers/userController.js new file mode 100644 index 00000000..229bb89f --- /dev/null +++ b/dist/controllers/userController.js @@ -0,0 +1,90 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = __importDefault(require("express")); +const userService_1 = __importDefault(require("../services/userService")); +const passport_1 = __importDefault(require("../config/passport")); +const asyncHandler_1 = require("../utils/asyncHandler"); +const CustomError_1 = require("../utils/CustomError"); +const userController = express_1.default.Router(); +// GET /users/me - 현재 로그인된 사용자 정보 조회 +userController.get("/me", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const userId = req.user.id; + const userProfile = yield userService_1.default.getUserProfile(userId); + res.json(userProfile); +}))); +// PATCH /users/me - 현재 로그인된 사용자 정보 수정 (닉네임, 프로필 이미지 등) +userController.patch("/me", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const userId = req.user.id; + const updateData = req.body; + if (Object.keys(updateData).length === 0) { + throw new CustomError_1.CustomError(400, "수정할 내용이 없습니다."); + } + const updatedUser = yield userService_1.default.updateUserProfile(userId, updateData); + res.json(updatedUser); +}))); +// PATCH /users/me/password - 현재 로그인된 사용자 비밀번호 변경 +userController.patch("/me/password", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const userId = req.user.id; + const { currentPassword, newPassword } = req.body; + if (!currentPassword || !newPassword) { + throw new CustomError_1.CustomError(422, "현재 비밀번호와 새 비밀번호를 모두 입력해야 합니다."); + } + yield userService_1.default.updateUserPassword(userId, currentPassword, newPassword); + res.json({ message: "비밀번호가 성공적으로 변경되었습니다." }); +}))); +// GET /users/me/products - 현재 로그인된 사용자가 등록한 상품 목록 조회 +userController.get("/me/products", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const userId = req.user.id; + const products = yield userService_1.default.getUserProducts(userId, req.query); + res.json(products); +}))); +// GET /users/me/favorites - 현재 로그인된 사용자가 즐겨찾기한 상품 및 게시글 목록 조회 +userController.get("/me/favorites", passport_1.default.authenticate("access-token", { + session: false, + failWithError: true, +}), (0, asyncHandler_1.asyncHandler)((req, res) => __awaiter(void 0, void 0, void 0, function* () { + if (!req.user || !req.user.id) { + throw new CustomError_1.CustomError(401, "인증되지 않은 사용자이거나 사용자 ID가 없습니다."); + } + const userId = req.user.id; + const favorites = yield userService_1.default.getUserFavorites(userId, req.query); + res.json(favorites); +}))); +exports.default = userController; +//# sourceMappingURL=userController.js.map \ No newline at end of file diff --git a/dist/controllers/userController.js.map b/dist/controllers/userController.js.map new file mode 100644 index 00000000..c7b5f1c5 --- /dev/null +++ b/dist/controllers/userController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"userController.js","sourceRoot":"","sources":["../../src/controllers/userController.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/middlewares/errorHandler.js b/dist/middlewares/errorHandler.js new file mode 100644 index 00000000..3a8f6c2c --- /dev/null +++ b/dist/middlewares/errorHandler.js @@ -0,0 +1,30 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = errorHandler; +const CustomError_1 = require("../utils/CustomError"); +function errorHandler(error, req, res, next) { + console.error("Error caught by errorHandler:", error); + let statusCode = error.statusCode || error.status || error.code || 500; + let responseMessage = error.message || "서버 내부 오류가 발생했습니다."; + if (error instanceof CustomError_1.CustomError) { + statusCode = error.statusCode; + responseMessage = error.message; + } + else if (error.status === 401 || + (error.name === "AuthenticationError" && error.message === "Unauthorized")) { + statusCode = 401; + responseMessage = + "인증에 실패했습니다. 유효한 토큰이 아니거나 토큰이 만료되었을 수 있습니다."; + } + else if (error.name === "SyntaxError" && error.message.includes("JSON")) { + statusCode = 400; + responseMessage = "잘못된 JSON 형식입니다."; + } + res.status(statusCode).json({ + path: req.path, + method: req.method, + message: responseMessage, + timestamp: new Date().toISOString(), + }); +} +//# sourceMappingURL=errorHandler.js.map \ No newline at end of file diff --git a/dist/middlewares/errorHandler.js.map b/dist/middlewares/errorHandler.js.map new file mode 100644 index 00000000..0ee866fd --- /dev/null +++ b/dist/middlewares/errorHandler.js.map @@ -0,0 +1 @@ +{"version":3,"file":"errorHandler.js","sourceRoot":"","sources":["../../src/middlewares/errorHandler.ts"],"names":[],"mappings":";;AASA,+BAgCC;AAxCD,sDAAmD;AAQnD,SAAwB,YAAY,CAClC,KAAe,EACf,GAAY,EACZ,GAAa,EACb,IAAkB;IAElB,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,KAAK,CAAC,CAAC;IAEtD,IAAI,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,GAAG,CAAC;IACvE,IAAI,eAAe,GAAG,KAAK,CAAC,OAAO,IAAI,mBAAmB,CAAC;IAE3D,IAAI,KAAK,YAAY,yBAAW,EAAE,CAAC;QACjC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;QAC9B,eAAe,GAAG,KAAK,CAAC,OAAO,CAAC;IAClC,CAAC;SAAM,IACL,KAAK,CAAC,MAAM,KAAK,GAAG;QACpB,CAAC,KAAK,CAAC,IAAI,KAAK,qBAAqB,IAAI,KAAK,CAAC,OAAO,KAAK,cAAc,CAAC,EAC1E,CAAC;QACD,UAAU,GAAG,GAAG,CAAC;QACjB,eAAe;YACb,4CAA4C,CAAC;IACjD,CAAC;SAAM,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1E,UAAU,GAAG,GAAG,CAAC;QACjB,eAAe,GAAG,iBAAiB,CAAC;IACtC,CAAC;IAED,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;QAC1B,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,OAAO,EAAE,eAAe;QACxB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC,CAAC;AACL,CAAC"} \ No newline at end of file diff --git a/dist/middlewares/passport/jwtStrategy.js b/dist/middlewares/passport/jwtStrategy.js new file mode 100644 index 00000000..aab1a4ed --- /dev/null +++ b/dist/middlewares/passport/jwtStrategy.js @@ -0,0 +1,58 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const passport_jwt_1 = require("passport-jwt"); +const userService_1 = __importDefault(require("../../services/userService")); +if (!process.env.JWT_SECRET) { + console.error("치명적 오류: JWT_SECRET 환경 변수가 설정되지 않았습니다."); + process.exit(1); +} +const accessTokenOptions = { + jwtFromRequest: passport_jwt_1.ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: process.env.JWT_SECRET, + passReqToCallback: false, +}; +const cookieExtractor = (req) => { + let token = null; + if (req && req.cookies) { + token = req.cookies["refreshToken"]; + } + return token; +}; +const refreshTokenOptions = { + jwtFromRequest: cookieExtractor, + secretOrKey: process.env.JWT_SECRET, + passReqToCallback: false, +}; +function jwtVerify(payload, done) { + return __awaiter(this, void 0, void 0, function* () { + try { + const user = yield userService_1.default.getUserById(payload.userId); + if (!user) { + return done(null, false); + } + return done(null, user); + } + catch (error) { + return done(error, false); + } + }); +} +const accessTokenStrategy = new passport_jwt_1.Strategy(accessTokenOptions, jwtVerify); +const refreshTokenStrategy = new passport_jwt_1.Strategy(refreshTokenOptions, jwtVerify); +exports.default = { + accessTokenStrategy, + refreshTokenStrategy, +}; +//# sourceMappingURL=jwtStrategy.js.map \ No newline at end of file diff --git a/dist/middlewares/passport/jwtStrategy.js.map b/dist/middlewares/passport/jwtStrategy.js.map new file mode 100644 index 00000000..87917620 --- /dev/null +++ b/dist/middlewares/passport/jwtStrategy.js.map @@ -0,0 +1 @@ +{"version":3,"file":"jwtStrategy.js","sourceRoot":"","sources":["../../../src/middlewares/passport/jwtStrategy.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,+CAKsB;AAEtB,6EAAqD;AAOrD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;IAC5B,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;IACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,kBAAkB,GAAoB;IAC1C,cAAc,EAAE,yBAAU,CAAC,2BAA2B,EAAE;IACxD,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,UAAW;IACpC,iBAAiB,EAAE,KAAK;CACzB,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,GAAY,EAAiB,EAAE;IACtD,IAAI,KAAK,GAAkB,IAAI,CAAC;IAChC,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;QACvB,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAoB;IAC3C,cAAc,EAAE,eAAe;IAC/B,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,UAAW;IACpC,iBAAiB,EAAE,KAAK;CACzB,CAAC;AAIF,SAAe,SAAS,CAAC,OAAmB,EAAE,IAAsB;;QAClE,IAAI,CAAC;YACH,MAAM,IAAI,GAAoB,MAAM,qBAAW,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC5E,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC3B,CAAC;YACD,OAAO,IAAI,CAAC,IAAI,EAAE,IAAY,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;CAAA;AAED,MAAM,mBAAmB,GAAG,IAAI,uBAAW,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;AAC3E,MAAM,oBAAoB,GAAG,IAAI,uBAAW,CAAC,mBAAmB,EAAE,SAAS,CAAC,CAAC;AAE7E,kBAAe;IACb,mBAAmB;IACnB,oBAAoB;CACrB,CAAC"} \ No newline at end of file diff --git a/dist/middlewares/uploadMiddleware.js b/dist/middlewares/uploadMiddleware.js new file mode 100644 index 00000000..a91ea11b --- /dev/null +++ b/dist/middlewares/uploadMiddleware.js @@ -0,0 +1,53 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const path_1 = __importDefault(require("path")); +const client_s3_1 = require("@aws-sdk/client-s3"); +const multer_s3_1 = __importDefault(require("multer-s3")); +const multer_1 = __importDefault(require("multer")); +const CustomError_1 = require("../utils/CustomError"); +const { AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_BUCKET_NAME, } = process.env; +if (!AWS_ACCESS_KEY_ID || + !AWS_SECRET_ACCESS_KEY || + !AWS_REGION || + !AWS_BUCKET_NAME) { + throw new Error("AWS S3 환경 변수가 설정되지 않았습니다. .env 파일을 확인해주세요. (필수: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_BUCKET_NAME)"); +} +const s3 = new client_s3_1.S3Client({ + credentials: { + accessKeyId: AWS_ACCESS_KEY_ID, + secretAccessKey: AWS_SECRET_ACCESS_KEY, + }, + region: AWS_REGION, +}); +const storage = (0, multer_s3_1.default)({ + s3: s3, + bucket: AWS_BUCKET_NAME, + contentType: multer_s3_1.default.AUTO_CONTENT_TYPE, + key: (req, file, cb) => { + const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9); + const extension = path_1.default.extname(file.originalname); + cb(null, `public/images/${uniqueSuffix}${extension}`); + }, +}); +const fileFilter = (req, file, cb) => { + const allowedMimeTypes = ["image/jpeg", "image/png", "image/gif"]; + if (allowedMimeTypes.includes(file.mimetype)) { + cb(null, true); // 파일 허용 + } + else { + const error = new CustomError_1.CustomError(400, "지원하지 않는 파일 형식입니다. (jpeg, png, gif만 허용됩니다.)"); + cb(error); + } +}; +const uploadMiddleware = (0, multer_1.default)({ + storage: storage, + fileFilter: fileFilter, + limits: { + fileSize: 5 * 1024 * 1024, + }, +}); +exports.default = uploadMiddleware; +//# sourceMappingURL=uploadMiddleware.js.map \ No newline at end of file diff --git a/dist/middlewares/uploadMiddleware.js.map b/dist/middlewares/uploadMiddleware.js.map new file mode 100644 index 00000000..49142c05 --- /dev/null +++ b/dist/middlewares/uploadMiddleware.js.map @@ -0,0 +1 @@ +{"version":3,"file":"uploadMiddleware.js","sourceRoot":"","sources":["../../src/middlewares/uploadMiddleware.ts"],"names":[],"mappings":";;;;;AAAA,gDAAwB;AACxB,kDAA8C;AAC9C,0DAAiC;AACjC,oDAAoD;AAEpD,sDAAmD;AAEnD,MAAM,EACJ,iBAAiB,EACjB,qBAAqB,EACrB,UAAU,EACV,eAAe,GAChB,GAAG,OAAO,CAAC,GAAG,CAAC;AAEhB,IACE,CAAC,iBAAiB;IAClB,CAAC,qBAAqB;IACtB,CAAC,UAAU;IACX,CAAC,eAAe,EAChB,CAAC;IACD,MAAM,IAAI,KAAK,CACb,wHAAwH,CACzH,CAAC;AACJ,CAAC;AAED,MAAM,EAAE,GAAG,IAAI,oBAAQ,CAAC;IACtB,WAAW,EAAE;QACX,WAAW,EAAE,iBAAiB;QAC9B,eAAe,EAAE,qBAAqB;KACvC;IACD,MAAM,EAAE,UAAU;CACnB,CAAC,CAAC;AAEH,MAAM,OAAO,GAAG,IAAA,mBAAQ,EAAC;IACvB,EAAE,EAAE,EAAE;IACN,MAAM,EAAE,eAAe;IACvB,WAAW,EAAE,mBAAQ,CAAC,iBAAiB;IACvC,GAAG,EAAE,CACH,GAAY,EACZ,IAAyB,EACzB,EAAsC,EACtC,EAAE;QACF,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;QACxE,MAAM,SAAS,GAAG,cAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAClD,EAAE,CAAC,IAAI,EAAE,iBAAiB,YAAY,GAAG,SAAS,EAAE,CAAC,CAAC;IACxD,CAAC;CACF,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG,CACjB,GAAY,EACZ,IAAyB,EACzB,EAAsB,EACtB,EAAE;IACF,MAAM,gBAAgB,GAAG,CAAC,YAAY,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;IAClE,IAAI,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7C,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ;IAC1B,CAAC;SAAM,CAAC;QACN,MAAM,KAAK,GAAG,IAAI,yBAAW,CAC3B,GAAG,EACH,4CAA4C,CAC7C,CAAC;QACF,EAAE,CAAC,KAAK,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,IAAA,gBAAM,EAAC;IAC9B,OAAO,EAAE,OAAO;IAChB,UAAU,EAAE,UAAU;IACtB,MAAM,EAAE;QACN,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;KAC1B;CACF,CAAC,CAAC;AAEH,kBAAe,gBAAgB,CAAC"} \ No newline at end of file diff --git a/dist/repositories/articleRepository.js b/dist/repositories/articleRepository.js new file mode 100644 index 00000000..17370899 --- /dev/null +++ b/dist/repositories/articleRepository.js @@ -0,0 +1,148 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const prisma_1 = __importDefault(require("../config/prisma")); +const _writerSelection = { + select: { + id: true, + nickname: true, + image: true, + }, +}; +const _countSelection = { + select: { likedBy: true, comments: true }, +}; +function create(data) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.article.create({ + data, + include: { + writer: _writerSelection, + _count: _countSelection, + }, + }); + }); +} +function findAll() { + return __awaiter(this, arguments, void 0, function* (options = {}, userId) { + const { skip, take = 10, where, orderBy = { createdAt: "desc" } } = options; + const articles = yield prisma_1.default.article.findMany({ + where, + include: { + writer: _writerSelection, + _count: _countSelection, + }, + orderBy, + skip: skip !== undefined ? parseInt(String(skip), 10) : 0, + take: take !== undefined ? parseInt(String(take), 10) : 10, + }); + const totalCount = yield prisma_1.default.article.count({ where }); + const listWithIsLiked = articles.map((article) => { + const likedByArray = article.likedBy; + const isLiked = userId && Array.isArray(likedByArray) + ? likedByArray.some((like) => like.userId === userId) + : false; + return Object.assign(Object.assign({}, article), { isLiked }); + }); + return { list: listWithIsLiked, totalCount }; + }); +} +function findById(id, userId) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.article.findUnique({ + where: { id }, + include: { + writer: _writerSelection, + comments: { + orderBy: { createdAt: "desc" }, + take: 5, + include: { + writer: _writerSelection, + }, + }, + likedBy: userId + ? { + where: { userId }, + select: { userId: true }, + } + : false, + _count: _countSelection, + }, + }); + }); +} +function update(id, data) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.article.update({ + where: { id }, + data, + include: { + writer: _writerSelection, + _count: _countSelection, + }, + }); + }); +} +function deleteById(id) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.article.delete({ + where: { id }, + }); + }); +} +function findLike(articleId, userId) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.articleLike.findUnique({ + where: { + userId_articleId: { + userId, + articleId, + }, + }, + }); + }); +} +function createLike(articleId, userId) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.articleLike.create({ + data: { + articleId, + userId, + }, + }); + }); +} +function deleteLike(articleId, userId) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.articleLike.delete({ + where: { + userId_articleId: { + userId, + articleId, + }, + }, + }); + }); +} +exports.default = { + create, + findAll, + findById, + update, + deleteById, + findLike, + createLike, + deleteLike, +}; +//# sourceMappingURL=articleRepository.js.map \ No newline at end of file diff --git a/dist/repositories/articleRepository.js.map b/dist/repositories/articleRepository.js.map new file mode 100644 index 00000000..e2c6a3ae --- /dev/null +++ b/dist/repositories/articleRepository.js.map @@ -0,0 +1 @@ +{"version":3,"file":"articleRepository.js","sourceRoot":"","sources":["../../src/repositories/articleRepository.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/repositories/commentRepository.js b/dist/repositories/commentRepository.js new file mode 100644 index 00000000..d3f0a7ed --- /dev/null +++ b/dist/repositories/commentRepository.js @@ -0,0 +1,105 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const prisma_1 = __importDefault(require("../config/prisma")); +const _writerSelection = { + select: { + id: true, + nickname: true, + image: true, + }, +}; +function create(data) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.comment.create({ + data, + include: { + writer: _writerSelection, + }, + }); + }); +} +function findByProductId(productId_1) { + return __awaiter(this, arguments, void 0, function* (productId, options = {}) { + const { skip: rawSkip, take: rawTake = 10 } = options; + const skip = rawSkip !== undefined ? parseInt(String(rawSkip), 10) : undefined; + const take = parseInt(String(rawTake), 10); + return prisma_1.default.comment.findMany({ + where: { productId }, + include: { + writer: _writerSelection, + }, + orderBy: { + createdAt: "desc", + }, + skip, + take, + }); + }); +} +function findByArticleId(articleId_1) { + return __awaiter(this, arguments, void 0, function* (articleId, options = {}) { + const { skip: rawSkip, take: rawTake = 10 } = options; + const skip = rawSkip !== undefined ? parseInt(String(rawSkip), 10) : undefined; + const take = parseInt(String(rawTake), 10); + return prisma_1.default.comment.findMany({ + where: { articleId }, + include: { + writer: _writerSelection, + }, + orderBy: { + createdAt: "desc", + }, + skip, + take, + }); + }); +} +function findById(id) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.comment.findUnique({ + where: { id }, + include: { + writer: _writerSelection, + }, + }); + }); +} +function update(id, data) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.comment.update({ + where: { id }, + data, + include: { + writer: _writerSelection, + }, + }); + }); +} +function deleteById(id) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.comment.delete({ + where: { id }, + }); + }); +} +exports.default = { + create, + findByProductId, + findByArticleId, + findById, + update, + deleteById, +}; +//# sourceMappingURL=commentRepository.js.map \ No newline at end of file diff --git a/dist/repositories/commentRepository.js.map b/dist/repositories/commentRepository.js.map new file mode 100644 index 00000000..438a9f6d --- /dev/null +++ b/dist/repositories/commentRepository.js.map @@ -0,0 +1 @@ +{"version":3,"file":"commentRepository.js","sourceRoot":"","sources":["../../src/repositories/commentRepository.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,8DAAsC;AAGtC,MAAM,gBAAgB,GAAG;IACvB,MAAM,EAAE;QACN,EAAE,EAAE,IAAI;QACR,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,IAAI;KACZ;CACO,CAAC;AAWX,SAAe,MAAM,CACnB,IAA+B;;QAE/B,OAAO,gBAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YAC3B,IAAI;YACJ,OAAO,EAAE;gBACP,MAAM,EAAE,gBAAgB;aACzB;SACF,CAAC,CAAC;IACL,CAAC;CAAA;AAED,SAAe,eAAe;yDAC5B,SAAiB,EACjB,UAA8C,EAAE;QAEhD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;QAEtD,MAAM,IAAI,GACR,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACpE,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QAE3C,OAAO,gBAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;YAC7B,KAAK,EAAE,EAAE,SAAS,EAAE;YACpB,OAAO,EAAE;gBACP,MAAM,EAAE,gBAAgB;aACzB;YACD,OAAO,EAAE;gBACP,SAAS,EAAE,MAAM;aAClB;YACD,IAAI;YACJ,IAAI;SACL,CAAC,CAAC;IACL,CAAC;CAAA;AAED,SAAe,eAAe;yDAC5B,SAAiB,EACjB,UAA8C,EAAE;QAEhD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;QAEtD,MAAM,IAAI,GACR,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACpE,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QAE3C,OAAO,gBAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;YAC7B,KAAK,EAAE,EAAE,SAAS,EAAE;YACpB,OAAO,EAAE;gBACP,MAAM,EAAE,gBAAgB;aACzB;YACD,OAAO,EAAE;gBACP,SAAS,EAAE,MAAM;aAClB;YACD,IAAI;YACJ,IAAI;SACL,CAAC,CAAC;IACL,CAAC;CAAA;AAED,SAAe,QAAQ,CAAC,EAAU;;QAChC,OAAO,gBAAM,CAAC,OAAO,CAAC,UAAU,CAAC;YAC/B,KAAK,EAAE,EAAE,EAAE,EAAE;YACb,OAAO,EAAE;gBACP,MAAM,EAAE,gBAAgB;aACzB;SACF,CAAC,CAAC;IACL,CAAC;CAAA;AAED,SAAe,MAAM,CACnB,EAAU,EACV,IAA+B;;QAE/B,OAAO,gBAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YAC3B,KAAK,EAAE,EAAE,EAAE,EAAE;YACb,IAAI;YACJ,OAAO,EAAE;gBACP,MAAM,EAAE,gBAAgB;aACzB;SACF,CAAC,CAAC;IACL,CAAC;CAAA;AAED,SAAe,UAAU,CAAC,EAAU;;QAClC,OAAO,gBAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YAC3B,KAAK,EAAE,EAAE,EAAE,EAAE;SACd,CAAC,CAAC;IACL,CAAC;CAAA;AAED,kBAAe;IACb,MAAM;IACN,eAAe;IACf,eAAe;IACf,QAAQ;IACR,MAAM;IACN,UAAU;CACX,CAAC"} \ No newline at end of file diff --git a/dist/repositories/productRepository.js b/dist/repositories/productRepository.js new file mode 100644 index 00000000..02133486 --- /dev/null +++ b/dist/repositories/productRepository.js @@ -0,0 +1,161 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const prisma_1 = __importDefault(require("../config/prisma")); +// Helper for consistent selections +const _productOwnerSelection = { + select: { id: true, nickname: true }, +}; +const _productCountSelection = { select: { likedBy: true } }; +function findById(id, userId) { + return __awaiter(this, void 0, void 0, function* () { + const product = yield prisma_1.default.product.findUnique({ + where: { + id: parseInt(id, 10), + }, + include: { + owner: _productOwnerSelection, + likedBy: userId + ? { + where: { userId }, + select: { userId: true }, + } + : false, + _count: _productCountSelection, + }, + }); + // Prisma's return type with conditional includes can be tricky. + // If likedBy is false, the property won't exist. + // Casting might be necessary if GetPayload cannot fully represent this. + return product; + }); +} +function create(productData, ownerId) { + return __awaiter(this, void 0, void 0, function* () { + const { name, description, price, images, tags } = productData; + return prisma_1.default.product.create({ + data: { + name, + description, // Now 'string', matching non-nullable schema expectation + price: typeof price === "string" ? parseInt(price, 10) : price, + images: images || [], + owner: { + connect: { id: ownerId }, + }, + tags: tags || [], + }, + include: { + owner: _productOwnerSelection, + _count: _productCountSelection, + }, + }); + }); +} +function findAll() { + return __awaiter(this, arguments, void 0, function* (options = {}, userId) { + const { skip, take = 10, where, orderBy = { createdAt: "desc" } } = options; + const products = yield prisma_1.default.product.findMany({ + where, + include: { + owner: _productOwnerSelection, + likedBy: userId ? { where: { userId }, select: { userId: true } } : false, + _count: _productCountSelection, + }, + orderBy, + skip: skip !== undefined ? parseInt(String(skip), 10) : 0, + take: parseInt(String(take), 10), + }); + const totalCount = yield prisma_1.default.product.count({ where }); + return { list: products, totalCount }; + }); +} +function update(id, data) { + return __awaiter(this, void 0, void 0, function* () { + const { name, description, price, images, tags } = data, otherData = __rest(data, ["name", "description", "price", "images", "tags"]); + const finalUpdatePayload = Object.assign({}, otherData); + if (name !== undefined) + finalUpdatePayload.name = name; + // description is now 'string | undefined'. If undefined, it's skipped. + // If string, it's assigned. This is compatible with a non-nullable schema field. + if (description !== undefined) + finalUpdatePayload.description = description; + if (price !== undefined && price !== null) { + // price can be 0 + finalUpdatePayload.price = + typeof price === "string" ? parseInt(price, 10) : price; + } + if (images !== undefined) + finalUpdatePayload.images = images; + if (tags !== undefined) + finalUpdatePayload.tags = tags; + return prisma_1.default.product.update({ + where: { id: parseInt(id, 10) }, + data: finalUpdatePayload, + include: { + owner: _productOwnerSelection, + _count: _productCountSelection, + }, + }); + }); +} +function deleteById(id) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.product.delete({ + where: { id: parseInt(id, 10) }, + }); + }); +} +function findLike(productId, userId) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.productLike.findUnique({ + where: { userId_productId: { userId, productId: parseInt(productId, 10) } }, + }); + }); +} +function createLike(productId, userId) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.productLike.create({ + data: { userId, productId: parseInt(productId, 10) }, + }); + }); +} +function deleteLike(productId, userId) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.productLike.delete({ + where: { userId_productId: { userId, productId: parseInt(productId, 10) } }, + }); + }); +} +exports.default = { + findById, + create, + findAll, + update, + deleteById, + findLike, + createLike, + deleteLike, +}; +//# sourceMappingURL=productRepository.js.map \ No newline at end of file diff --git a/dist/repositories/productRepository.js.map b/dist/repositories/productRepository.js.map new file mode 100644 index 00000000..d6d9b05a --- /dev/null +++ b/dist/repositories/productRepository.js.map @@ -0,0 +1 @@ +{"version":3,"file":"productRepository.js","sourceRoot":"","sources":["../../src/repositories/productRepository.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/repositories/userRepository.js b/dist/repositories/userRepository.js new file mode 100644 index 00000000..ef08524d --- /dev/null +++ b/dist/repositories/userRepository.js @@ -0,0 +1,142 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const prisma_1 = __importDefault(require("../config/prisma")); +function create(data) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.user.create({ + data, + select: { + id: true, + email: true, + nickname: true, + image: true, + createdAt: true, + updatedAt: true, + }, + }); + }); +} +function findById(id) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.user.findUnique({ + where: { id }, + select: { + id: true, + email: true, + nickname: true, + image: true, + createdAt: true, + updatedAt: true, + }, + }); + }); +} +function findByIdWithPassword(id) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.user.findUnique({ + where: { id }, + }); + }); +} +function findByEmail(email) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.user.findUnique({ + where: { email }, + }); + }); +} +function findByNickname(nickname) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.user.findUnique({ + where: { nickname }, + select: { + id: true, + nickname: true, + }, + }); + }); +} +function update(id, data) { + return __awaiter(this, void 0, void 0, function* () { + return prisma_1.default.user.update({ + where: { id }, + data, + select: { + id: true, + email: true, + nickname: true, + image: true, + createdAt: true, + updatedAt: true, + }, + }); + }); +} +function findUserFavoriteProducts(userId_1) { + return __awaiter(this, arguments, void 0, function* (userId, options = {}) { + const { skip, take } = options; + return prisma_1.default.productLike.findMany({ + where: { userId }, + include: { + product: { + select: { + id: true, + name: true, + price: true, + images: true, + }, + }, + }, + skip, + take, + orderBy: { + createdAt: "desc", + }, + }); + }); +} +function findUserFavoriteArticles(userId_1) { + return __awaiter(this, arguments, void 0, function* (userId, options = {}) { + const { skip, take } = options; + return prisma_1.default.articleLike.findMany({ + where: { userId }, + include: { + article: { + select: { + id: true, + title: true, + image: true, + }, + }, + }, + skip, + take, + orderBy: { + createdAt: "desc", + }, + }); + }); +} +exports.default = { + create, + findById, + findByIdWithPassword, + findByEmail, + findByNickname, + update, + findUserFavoriteProducts, + findUserFavoriteArticles, +}; +//# sourceMappingURL=userRepository.js.map \ No newline at end of file diff --git a/dist/repositories/userRepository.js.map b/dist/repositories/userRepository.js.map new file mode 100644 index 00000000..9cb6a654 --- /dev/null +++ b/dist/repositories/userRepository.js.map @@ -0,0 +1 @@ +{"version":3,"file":"userRepository.js","sourceRoot":"","sources":["../../src/repositories/userRepository.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/services/articleService.js b/dist/services/articleService.js new file mode 100644 index 00000000..92827911 --- /dev/null +++ b/dist/services/articleService.js @@ -0,0 +1,223 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const articleRepository_1 = __importDefault(require("../repositories/articleRepository")); +const userRepository_1 = __importDefault(require("../repositories/userRepository")); +const CustomError_1 = require("../utils/CustomError"); +function formatArticleForList(article) { + var _a, _b; + if (!article) + return null; + return { + id: article.id, + title: article.title, + content: article.content, + image: article.image, + writer: { + id: article.writer.id, + nickname: article.writer.nickname, + }, + likeCount: ((_a = article._count) === null || _a === void 0 ? void 0 : _a.likedBy) || 0, + commentCount: ((_b = article._count) === null || _b === void 0 ? void 0 : _b.comments) || 0, + createdAt: article.createdAt.toISOString(), + updatedAt: article.updatedAt.toISOString(), + }; +} +function formatArticleForDetail(article, userId) { + var _a, _b, _c; + if (!article) + return null; + return { + id: article.id, + title: article.title, + content: article.content, + image: article.image, + writer: { + id: article.writer.id, + nickname: article.writer.nickname, + image: article.writer.image, + }, + comments: ((_a = article.comments) === null || _a === void 0 ? void 0 : _a.map((comment) => ({ + id: comment.id, + content: comment.content, + createdAt: comment.createdAt, + updatedAt: comment.updatedAt, + writer: { + id: comment.writer.id, + nickname: comment.writer.nickname, + image: comment.writer.image, + }, + }))) || [], + likeCount: ((_b = article._count) === null || _b === void 0 ? void 0 : _b.likedBy) || 0, + commentCount: ((_c = article._count) === null || _c === void 0 ? void 0 : _c.comments) || 0, + isLiked: userId && Array.isArray(article.likedBy) + ? article.likedBy.some((like) => like.userId === userId) + : false, + createdAt: article.createdAt.toISOString(), + updatedAt: article.updatedAt.toISOString(), + }; +} +function createArticle(articleData) { + return __awaiter(this, void 0, void 0, function* () { + const { title, content, writerId, image } = articleData; + if (!title || title.trim() === "") { + throw new CustomError_1.CustomError(422, "게시글 제목을 입력해주세요."); + } + if (!content || content.trim() === "") { + throw new CustomError_1.CustomError(422, "게시글 내용을 입력해주세요."); + } + if (!writerId) { + throw new CustomError_1.CustomError(400, "작성자 정보가 누락되었습니다."); + } + const writer = yield userRepository_1.default.findById(writerId); + if (!writer) { + throw new CustomError_1.CustomError(404, "게시글을 작성할 사용자를 찾을 수 없습니다."); + } + const createdArticle = yield articleRepository_1.default.create({ + title, + content, + image, + writer: { + connect: { id: writerId }, + }, + }); + const detailedArticle = yield articleRepository_1.default.findById(createdArticle.id, writerId); + return formatArticleForDetail(detailedArticle, writerId); + }); +} +function getArticles(queryParams, userId) { + return __awaiter(this, void 0, void 0, function* () { + const { page = "1", pageSize = "10", orderBy = "recent", keyword, } = queryParams; + const skip = (parseInt(String(page), 10) - 1) * parseInt(String(pageSize), 10); + const take = parseInt(String(pageSize), 10); + const where = {}; + if (keyword) { + where.OR = [ + { title: { contains: keyword, mode: "insensitive" } }, + { content: { contains: keyword, mode: "insensitive" } }, + ]; + } + let prismaOrderBy = {}; + if (orderBy === "like") { + prismaOrderBy = { likedBy: { _count: "desc" } }; + } + else { + prismaOrderBy = { createdAt: "desc" }; + } + const { list, totalCount } = yield articleRepository_1.default.findAll({ skip, take, where, orderBy: prismaOrderBy }, userId); + return { + totalCount, + list: list.map((article) => formatArticleForList(article)), + }; + }); +} +function getArticleById(articleId, userId) { + return __awaiter(this, void 0, void 0, function* () { + const article = yield articleRepository_1.default.findById(articleId, userId); + if (!article) { + throw new CustomError_1.CustomError(404, "게시글을 찾을 수 없습니다."); + } + return formatArticleForDetail(article, userId); + }); +} +function updateArticle(articleId, updateData, writerId) { + return __awaiter(this, void 0, void 0, function* () { + const article = yield articleRepository_1.default.findById(articleId, writerId); + if (!article) { + throw new CustomError_1.CustomError(404, "수정할 게시글을 찾을 수 없습니다."); + } + if (article.writer.id !== writerId) { + throw new CustomError_1.CustomError(403, "게시글을 수정할 권한이 없습니다."); + } + const { title, content, image } = updateData; + const dataToUpdate = {}; + if (title !== undefined) { + const trimmedTitle = title.trim(); + if (!trimmedTitle) + throw new CustomError_1.CustomError(422, "제목은 공백일 수 없습니다."); + dataToUpdate.title = trimmedTitle; + } + if (content !== undefined) { + const trimmedContent = content.trim(); + if (!trimmedContent) + throw new CustomError_1.CustomError(422, "내용은 공백일 수 없습니다."); + dataToUpdate.content = trimmedContent; + } + if (image !== undefined) { + dataToUpdate.image = image; + } + if (Object.keys(dataToUpdate).length === 0) { + throw new CustomError_1.CustomError(422, "수정할 내용이 없습니다."); + } + yield articleRepository_1.default.update(articleId, dataToUpdate); + const updatedArticle = yield articleRepository_1.default.findById(articleId, writerId); + return formatArticleForDetail(updatedArticle, writerId); + }); +} +function deleteArticle(articleId, writerId) { + return __awaiter(this, void 0, void 0, function* () { + const article = yield articleRepository_1.default.findById(articleId, writerId); + if (!article) { + throw new CustomError_1.CustomError(404, "삭제할 게시글을 찾을 수 없습니다."); + } + if (article.writer.id !== writerId) { + throw new CustomError_1.CustomError(403, "게시글을 삭제할 권한이 없습니다."); + } + yield articleRepository_1.default.deleteById(articleId); + }); +} +function likeArticle(articleId, userId) { + return __awaiter(this, void 0, void 0, function* () { + const article = yield articleRepository_1.default.findById(articleId, userId); + if (!article) { + throw new CustomError_1.CustomError(404, "게시글을 찾을 수 없습니다."); + } + const existingLike = yield articleRepository_1.default.findLike(articleId, userId); + if (existingLike) { + return formatArticleForDetail(article, userId); + } + yield articleRepository_1.default.createLike(articleId, userId); + const updatedArticle = yield articleRepository_1.default.findById(articleId, userId); + if (!updatedArticle) + throw new CustomError_1.CustomError(404, "업데이트된 게시글을 찾을 수 없습니다."); + return formatArticleForDetail(updatedArticle, userId); + }); +} +function unlikeArticle(articleId, userId) { + return __awaiter(this, void 0, void 0, function* () { + const article = yield articleRepository_1.default.findById(articleId, userId); + if (!article) { + throw new CustomError_1.CustomError(404, "게시글을 찾을 수 없습니다."); + } + const existingLike = yield articleRepository_1.default.findLike(articleId, userId); + if (!existingLike) { + return formatArticleForDetail(article, userId); + } + yield articleRepository_1.default.deleteLike(articleId, userId); + const updatedArticle = yield articleRepository_1.default.findById(articleId, userId); + if (!updatedArticle) + throw new CustomError_1.CustomError(404, "업데이트된 게시글을 찾을 수 없습니다."); + return formatArticleForDetail(updatedArticle, userId); + }); +} +exports.default = { + createArticle, + getArticles, + getArticleById, + updateArticle, + deleteArticle, + likeArticle, + unlikeArticle, +}; +//# sourceMappingURL=articleService.js.map \ No newline at end of file diff --git a/dist/services/articleService.js.map b/dist/services/articleService.js.map new file mode 100644 index 00000000..8c7837f0 --- /dev/null +++ b/dist/services/articleService.js.map @@ -0,0 +1 @@ +{"version":3,"file":"articleService.js","sourceRoot":"","sources":["../../src/services/articleService.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/services/authService.js b/dist/services/authService.js new file mode 100644 index 00000000..a42be980 --- /dev/null +++ b/dist/services/authService.js @@ -0,0 +1,105 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const userRepository_1 = __importDefault(require("../repositories/userRepository")); +const bcrypt_1 = __importDefault(require("bcrypt")); +const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); +const CustomError_1 = require("../utils/CustomError"); +const JWT_SECRET_ENV = process.env.JWT_SECRET; +if (!JWT_SECRET_ENV) { + throw new Error("JWT_SECRET is not defined in environment variables. Please set it."); +} +const JWT_SECRET = JWT_SECRET_ENV; +const ACCESS_TOKEN_EXPIRES_IN = process.env.ACCESS_TOKEN_EXPIRES_IN || "15m"; +const REFRESH_TOKEN_EXPIRES_IN = process.env.REFRESH_TOKEN_EXPIRES_IN || "7d"; +function signUpUser(_a) { + return __awaiter(this, arguments, void 0, function* ({ nickname, email, password, passwordConfirmation, }) { + if (password !== passwordConfirmation) { + throw new CustomError_1.CustomError(422, "비밀번호가 일치하지 않습니다."); + } + const existingUserByEmail = yield userRepository_1.default.findByEmail(email); + if (existingUserByEmail) { + throw new CustomError_1.CustomError(409, "이미 사용중인 이메일입니다."); + } + const existingUserByNickname = yield userRepository_1.default.findByNickname(nickname); + if (existingUserByNickname) { + throw new CustomError_1.CustomError(409, "이미 사용중인 닉네임입니다."); + } + const hashedPassword = yield bcrypt_1.default.hash(password, 10); + const newUser = yield userRepository_1.default.create({ + nickname, + email, + password: hashedPassword, + }); + return newUser; + }); +} +function signInUser(email, passwordInput) { + return __awaiter(this, void 0, void 0, function* () { + const user = yield userRepository_1.default.findByEmail(email); + if (!user) { + throw new CustomError_1.CustomError(401, "이메일 또는 비밀번호가 일치하지 않습니다."); + } + const isPasswordValid = yield bcrypt_1.default.compare(passwordInput, user.password); + if (!isPasswordValid) { + throw new CustomError_1.CustomError(401, "이메일 또는 비밀번호가 일치하지 않습니다."); + } + const payload = { + userId: user.id, + email: user.email, + nickname: user.nickname, + }; + const accessTokenOptions = { + expiresIn: ACCESS_TOKEN_EXPIRES_IN, + }; + const refreshTokenOptions = { + expiresIn: REFRESH_TOKEN_EXPIRES_IN, + }; + const accessToken = jsonwebtoken_1.default.sign(payload, JWT_SECRET, accessTokenOptions); + const refreshToken = jsonwebtoken_1.default.sign(payload, JWT_SECRET, refreshTokenOptions); + const { password: _ } = user, userWithoutPassword = __rest(user, ["password"]); + return { accessToken, refreshToken, user: userWithoutPassword }; + }); +} +function generateNewAccessToken(user) { + if (!user || !user.id || !user.email || !user.nickname) { + throw new CustomError_1.CustomError(400, "새로운 액세스 토큰을 생성하기 위한 사용자 정보가 유효하지 않습니다."); + } + const payload = { + userId: user.id, + email: user.email, + nickname: user.nickname, + }; + const signOptions = { + expiresIn: ACCESS_TOKEN_EXPIRES_IN, + }; + return jsonwebtoken_1.default.sign(payload, JWT_SECRET, signOptions); +} +exports.default = { + signUpUser, + signInUser, + generateNewAccessToken, +}; +//# sourceMappingURL=authService.js.map \ No newline at end of file diff --git a/dist/services/authService.js.map b/dist/services/authService.js.map new file mode 100644 index 00000000..7d1e9ada --- /dev/null +++ b/dist/services/authService.js.map @@ -0,0 +1 @@ +{"version":3,"file":"authService.js","sourceRoot":"","sources":["../../src/services/authService.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/services/commentService.js b/dist/services/commentService.js new file mode 100644 index 00000000..6abd4a93 --- /dev/null +++ b/dist/services/commentService.js @@ -0,0 +1,123 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const commentRepository_1 = __importDefault(require("../repositories/commentRepository")); +const productRepository_1 = __importDefault(require("../repositories/productRepository")); +const articleRepository_1 = __importDefault(require("../repositories/articleRepository")); +const CustomError_1 = require("../utils/CustomError"); +function createProductComment(productId, writerId, content) { + return __awaiter(this, void 0, void 0, function* () { + if (!content || content.trim() === "") { + throw new CustomError_1.CustomError(422, "댓글 내용을 입력해주세요."); + } + const product = yield productRepository_1.default.findById(productId); + if (!product) { + throw new CustomError_1.CustomError(404, "댓글을 작성할 상품을 찾을 수 없습니다."); + } + return commentRepository_1.default.create({ + content, + writer: { connect: { id: writerId } }, + product: { connect: { id: parseInt(productId, 10) } }, + }); + }); +} +function getProductComments(productId, queryParams) { + return __awaiter(this, void 0, void 0, function* () { + const product = yield productRepository_1.default.findById(productId); + if (!product) { + throw new CustomError_1.CustomError(404, "상품을 찾을 수 없습니다."); + } + const page = queryParams.page ? parseInt(String(queryParams.page), 10) : 1; + const pageSize = queryParams.pageSize + ? parseInt(String(queryParams.pageSize), 10) + : 10; + const skip = (page - 1) * pageSize; + const take = pageSize; + const commentList = yield commentRepository_1.default.findByProductId(parseInt(productId, 10), { skip, take }); + // NOTE: If commentRepository.findByProductId performs pagination, + // commentList.length will be the count for the current page, not the total. + return { list: commentList, totalCount: commentList.length }; + }); +} +function createArticleComment(articleId, writerId, content) { + return __awaiter(this, void 0, void 0, function* () { + if (!content || content.trim() === "") { + throw new CustomError_1.CustomError(422, "댓글 내용을 입력해주세요."); + } + const article = yield articleRepository_1.default.findById(articleId); + if (!article) { + throw new CustomError_1.CustomError(404, "댓글을 작성할 게시글을 찾을 수 없습니다."); + } + return commentRepository_1.default.create({ + content, + writer: { connect: { id: writerId } }, + article: { connect: { id: articleId } }, + }); + }); +} +function getArticleComments(articleId, queryParams) { + return __awaiter(this, void 0, void 0, function* () { + const article = yield articleRepository_1.default.findById(articleId); + if (!article) { + throw new CustomError_1.CustomError(404, "게시글을 찾을 수 없습니다."); + } + const page = queryParams.page ? parseInt(String(queryParams.page), 10) : 1; + const pageSize = queryParams.pageSize + ? parseInt(String(queryParams.pageSize), 10) + : 10; + const skip = (page - 1) * pageSize; + const take = pageSize; + const commentList = yield commentRepository_1.default.findByArticleId(articleId, { + skip, + take, + }); + return { list: commentList, totalCount: commentList.length }; + }); +} +function updateComment(commentId, content, writerId) { + return __awaiter(this, void 0, void 0, function* () { + if (!content || content.trim() === "") { + throw new CustomError_1.CustomError(422, "댓글 내용을 입력해주세요."); + } + const comment = yield commentRepository_1.default.findById(commentId); + if (!comment) { + throw new CustomError_1.CustomError(404, "수정할 댓글을 찾을 수 없습니다."); + } + if (comment.writerId !== writerId) { + throw new CustomError_1.CustomError(403, "댓글을 수정할 권한이 없습니다."); + } + return commentRepository_1.default.update(commentId, { content }); + }); +} +function deleteComment(commentId, writerId) { + return __awaiter(this, void 0, void 0, function* () { + const comment = yield commentRepository_1.default.findById(commentId); + if (!comment) { + throw new CustomError_1.CustomError(404, "삭제할 댓글을 찾을 수 없습니다."); + } + if (comment.writerId !== writerId) { + throw new CustomError_1.CustomError(403, "댓글을 삭제할 권한이 없습니다."); + } + yield commentRepository_1.default.deleteById(commentId); + }); +} +exports.default = { + createProductComment, + getProductComments, + createArticleComment, + getArticleComments, + updateComment, + deleteComment, +}; +//# sourceMappingURL=commentService.js.map \ No newline at end of file diff --git a/dist/services/commentService.js.map b/dist/services/commentService.js.map new file mode 100644 index 00000000..31fc4e71 --- /dev/null +++ b/dist/services/commentService.js.map @@ -0,0 +1 @@ +{"version":3,"file":"commentService.js","sourceRoot":"","sources":["../../src/services/commentService.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,0FAAkE;AAClE,0FAAkE;AAClE,0FAAkE;AAClE,sDAAmD;AAQnD,SAAe,oBAAoB,CACjC,SAAiB,EACjB,QAAgB,EAChB,OAAe;;QAEf,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACtC,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,2BAAiB,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,wBAAwB,CAAC,CAAC;QACvD,CAAC;QAED,OAAO,2BAAiB,CAAC,MAAM,CAAC;YAC9B,OAAO;YACP,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE;YACrC,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE;SACtD,CAAC,CAAC;IACL,CAAC;CAAA;AAED,SAAe,kBAAkB,CAC/B,SAAiB,EACjB,WAA+B;;QAE/B,MAAM,OAAO,GAAG,MAAM,2BAAiB,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3E,MAAM,QAAQ,GAAG,WAAW,CAAC,QAAQ;YACnC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YAC5C,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;QACnC,MAAM,IAAI,GAAG,QAAQ,CAAC;QAEtB,MAAM,WAAW,GAAG,MAAM,2BAAiB,CAAC,eAAe,CACzD,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,EACvB,EAAE,IAAI,EAAE,IAAI,EAAE,CACf,CAAC;QACF,kEAAkE;QAClE,4EAA4E;QAC5E,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC;IAC/D,CAAC;CAAA;AAED,SAAe,oBAAoB,CACjC,SAAiB,EACjB,QAAgB,EAChB,OAAe;;QAEf,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACtC,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,2BAAiB,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,yBAAyB,CAAC,CAAC;QACxD,CAAC;QAED,OAAO,2BAAiB,CAAC,MAAM,CAAC;YAC9B,OAAO;YACP,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE;YACrC,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE;SACxC,CAAC,CAAC;IACL,CAAC;CAAA;AAED,SAAe,kBAAkB,CAC/B,SAAiB,EACjB,WAA+B;;QAE/B,MAAM,OAAO,GAAG,MAAM,2BAAiB,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;QAChD,CAAC;QAED,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3E,MAAM,QAAQ,GAAG,WAAW,CAAC,QAAQ;YACnC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YAC5C,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;QACnC,MAAM,IAAI,GAAG,QAAQ,CAAC;QAEtB,MAAM,WAAW,GAAG,MAAM,2BAAiB,CAAC,eAAe,CAAC,SAAS,EAAE;YACrE,IAAI;YACJ,IAAI;SACL,CAAC,CAAC;QAEH,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC;IAC/D,CAAC;CAAA;AAED,SAAe,aAAa,CAC1B,SAAiB,EACjB,OAAe,EACf,QAAgB;;QAEhB,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACtC,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,2BAAiB,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC;QACnD,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC;QAClD,CAAC;QAED,OAAO,2BAAiB,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1D,CAAC;CAAA;AAED,SAAe,aAAa,CAC1B,SAAiB,EACjB,QAAgB;;QAEhB,MAAM,OAAO,GAAG,MAAM,2BAAiB,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC;QACnD,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,MAAM,IAAI,yBAAW,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC;QAClD,CAAC;QAED,MAAM,2BAAiB,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAChD,CAAC;CAAA;AAED,kBAAe;IACb,oBAAoB;IACpB,kBAAkB;IAClB,oBAAoB;IACpB,kBAAkB;IAClB,aAAa;IACb,aAAa;CACd,CAAC"} \ No newline at end of file diff --git a/dist/services/productService.js b/dist/services/productService.js new file mode 100644 index 00000000..db65a16c --- /dev/null +++ b/dist/services/productService.js @@ -0,0 +1,186 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const productRepository_1 = __importDefault(require("../repositories/productRepository")); +const CustomError_1 = require("../utils/CustomError"); +function formatProductResponse(product, userId) { + var _a; + if (!product) + return null; + return { + id: product.id, + name: product.name, + description: product.description, + price: product.price, + images: product.images || [], + tags: product.tags || [], + ownerId: product.owner.id, + ownerNickname: product.owner.nickname, + favoriteCount: ((_a = product._count) === null || _a === void 0 ? void 0 : _a.likedBy) || 0, + createdAt: product.createdAt, + isFavorite: userId && Array.isArray(product.likedBy) + ? product.likedBy.some((like) => like.userId === userId) + : false, + }; +} +function getProductById(productId, userId) { + return __awaiter(this, void 0, void 0, function* () { + const product = yield productRepository_1.default.findById(productId, userId); + if (!product) { + throw new CustomError_1.CustomError(404, "상품을 찾을 수 없습니다."); + } + return formatProductResponse(product, userId); + }); +} +function createProduct(productData, ownerId) { + return __awaiter(this, void 0, void 0, function* () { + if (!productData.name || productData.price === undefined) { + throw new CustomError_1.CustomError(422, "상품 이름과 가격은 필수입니다."); + } + if ((typeof productData.price !== "number" && + typeof productData.price !== "string") || + (typeof productData.price === "string" && + isNaN(parseInt(productData.price, 10))) || + (typeof productData.price === "number" && isNaN(productData.price)) || + Number(productData.price) < 0) { + throw new CustomError_1.CustomError(422, "가격은 0 이상의 숫자여야 합니다."); + } + if (productData.tags && !Array.isArray(productData.tags)) { + throw new CustomError_1.CustomError(422, "태그는 배열이어야 합니다."); + } + if (productData.images && !Array.isArray(productData.images)) { + throw new CustomError_1.CustomError(422, "이미지는 URL 배열이어야 합니다."); + } + const createdProduct = yield productRepository_1.default.create({ + name: productData.name, + price: productData.price, + description: productData.description, + tags: productData.tags, + images: productData.images, + }, ownerId); + return formatProductResponse(createdProduct, ownerId); + }); +} +function getProducts(queryParams, userId) { + return __awaiter(this, void 0, void 0, function* () { + const { page = 1, pageSize = 10, orderBy = "recent", keyword } = queryParams; + const skip = (parseInt(String(page), 10) - 1) * parseInt(String(pageSize), 10); + const take = parseInt(String(pageSize), 10); + const where = {}; + if (keyword) { + where.OR = [ + { name: { contains: keyword, mode: "insensitive" } }, + { description: { contains: keyword, mode: "insensitive" } }, + { tags: { has: keyword } }, + ]; + } + let prismaOrderBy = {}; + if (orderBy === "favorite") { + prismaOrderBy = { likedBy: { _count: "desc" } }; + } + else { + prismaOrderBy = { createdAt: "desc" }; + } + const { list, totalCount } = yield productRepository_1.default.findAll({ skip, take, where, orderBy: prismaOrderBy }, userId); + return { + totalCount, + list: list.map((product) => formatProductResponse(product, userId)), + }; + }); +} +function updateProduct(productId, updateData, requesterId) { + return __awaiter(this, void 0, void 0, function* () { + const productToUpdate = yield productRepository_1.default.findById(productId, requesterId); + if (!productToUpdate) { + throw new CustomError_1.CustomError(404, "수정할 상품을 찾을 수 없습니다."); + } + if (productToUpdate.owner.id !== requesterId) { + throw new CustomError_1.CustomError(403, "상품을 수정할 권한이 없습니다."); + } + if (Object.keys(updateData).length === 0) { + throw new CustomError_1.CustomError(400, "수정할 내용이 없습니다."); + } + if (updateData.price !== undefined && + ((typeof updateData.price !== "number" && + typeof updateData.price !== "string") || + (typeof updateData.price === "string" && + isNaN(parseInt(updateData.price, 10))) || + (typeof updateData.price === "number" && isNaN(updateData.price)) || + Number(updateData.price) < 0)) { + throw new CustomError_1.CustomError(422, "가격은 0 이상의 숫자여야 합니다."); + } + if (updateData.tags && !Array.isArray(updateData.tags)) { + throw new CustomError_1.CustomError(422, "태그는 배열이어야 합니다."); + } + if (updateData.images && !Array.isArray(updateData.images)) { + throw new CustomError_1.CustomError(422, "이미지는 URL 배열이어야 합니다."); + } + const updatedProductRaw = yield productRepository_1.default.update(productId, updateData); + const finalProduct = yield productRepository_1.default.findById(productId, requesterId); + return formatProductResponse(finalProduct, requesterId); + }); +} +function deleteProduct(productId, requesterId) { + return __awaiter(this, void 0, void 0, function* () { + const product = yield productRepository_1.default.findById(productId); + if (!product) { + throw new CustomError_1.CustomError(404, "삭제할 상품을 찾을 수 없습니다."); + } + if (product.owner.id !== requesterId) { + throw new CustomError_1.CustomError(403, "상품을 삭제할 권한이 없습니다."); + } + yield productRepository_1.default.deleteById(productId); + return { id: parseInt(productId, 10) }; + }); +} +function addFavorite(productId, userId) { + return __awaiter(this, void 0, void 0, function* () { + const product = yield productRepository_1.default.findById(productId, userId); + if (!product) { + throw new CustomError_1.CustomError(404, "즐겨찾기할 상품을 찾을 수 없습니다."); + } + const existingLike = yield productRepository_1.default.findLike(productId, userId); + if (existingLike) { + return formatProductResponse(product, userId); + } + yield productRepository_1.default.createLike(productId, userId); + const updatedProduct = yield productRepository_1.default.findById(productId, userId); + return formatProductResponse(updatedProduct, userId); + }); +} +function removeFavorite(productId, userId) { + return __awaiter(this, void 0, void 0, function* () { + const product = yield productRepository_1.default.findById(productId, userId); + if (!product) { + throw new CustomError_1.CustomError(404, "즐겨찾기에서 삭제할 상품을 찾을 수 없습니다."); + } + const existingLike = yield productRepository_1.default.findLike(productId, userId); + if (!existingLike) { + return formatProductResponse(product, userId); + } + yield productRepository_1.default.deleteLike(productId, userId); + const updatedProduct = yield productRepository_1.default.findById(productId, userId); + return formatProductResponse(updatedProduct, userId); + }); +} +exports.default = { + getProductById, + createProduct, + getProducts, + updateProduct, + deleteProduct, + addFavorite, + removeFavorite, +}; +//# sourceMappingURL=productService.js.map \ No newline at end of file diff --git a/dist/services/productService.js.map b/dist/services/productService.js.map new file mode 100644 index 00000000..65283a23 --- /dev/null +++ b/dist/services/productService.js.map @@ -0,0 +1 @@ +{"version":3,"file":"productService.js","sourceRoot":"","sources":["../../src/services/productService.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/services/userService.js b/dist/services/userService.js new file mode 100644 index 00000000..14b196d5 --- /dev/null +++ b/dist/services/userService.js @@ -0,0 +1,115 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const userRepository_1 = __importDefault(require("../repositories/userRepository")); +const productRepository_1 = __importDefault(require("../repositories/productRepository")); +const bcrypt_1 = __importDefault(require("bcrypt")); +const CustomError_1 = require("../utils/CustomError"); +function getUserProfile(userId) { + return __awaiter(this, void 0, void 0, function* () { + const user = yield userRepository_1.default.findById(userId); + if (!user) { + throw new CustomError_1.CustomError(404, "사용자를 찾을 수 없습니다."); + } + return user; + }); +} +function updateUserProfile(userId, updateData) { + return __awaiter(this, void 0, void 0, function* () { + const allowedUpdates = ["nickname", "image"]; + const filteredUpdateData = {}; + for (const key of allowedUpdates) { + if (updateData[key] !== undefined) { + const value = updateData[key]; + const valueToAssign = value === null ? undefined : value; + filteredUpdateData[key] = valueToAssign; + } + } + if (Object.keys(filteredUpdateData).length === 0) { + throw new CustomError_1.CustomError(400, "수정할 유효한 정보가 없습니다."); + } + if (typeof filteredUpdateData.nickname === "string" && + filteredUpdateData.nickname) { + const existingUserByNickname = yield userRepository_1.default.findByNickname(filteredUpdateData.nickname); + if (existingUserByNickname && existingUserByNickname.id !== userId) { + throw new CustomError_1.CustomError(409, "이미 사용중인 닉네임입니다."); + } + } + const updatedUser = yield userRepository_1.default.update(userId, filteredUpdateData); + return updatedUser; + }); +} +function updateUserPassword(userId, currentPassword, newPassword) { + return __awaiter(this, void 0, void 0, function* () { + const user = yield userRepository_1.default.findByIdWithPassword(userId); + if (!user) { + throw new CustomError_1.CustomError(404, "사용자를 찾을 수 없습니다."); + } + const isPasswordValid = yield bcrypt_1.default.compare(currentPassword, user.password); + if (!isPasswordValid) { + throw new CustomError_1.CustomError(401, "현재 비밀번호가 일치하지 않습니다."); + } + if (newPassword.length < 8) { + throw new CustomError_1.CustomError(422, "새 비밀번호는 최소 8자 이상이어야 합니다."); + } + if (currentPassword === newPassword) { + throw new CustomError_1.CustomError(422, "새 비밀번호는 현재 비밀번호와 달라야 합니다."); + } + const hashedNewPassword = yield bcrypt_1.default.hash(newPassword, 10); + yield userRepository_1.default.update(userId, { + password: hashedNewPassword, + }); + }); +} +function getUserProducts(userId, queryParams) { + return __awaiter(this, void 0, void 0, function* () { + const prismaQueryParams = { + where: { ownerId: userId }, + }; + if (queryParams.page && queryParams.pageSize) { + prismaQueryParams.skip = + (Number(queryParams.page) - 1) * Number(queryParams.pageSize); + prismaQueryParams.take = Number(queryParams.pageSize); + } + return productRepository_1.default.findAll(prismaQueryParams, userId); + }); +} +function getUserFavorites(userId, queryParams) { + return __awaiter(this, void 0, void 0, function* () { + const options = { + skip: queryParams.page && queryParams.pageSize + ? (Number(queryParams.page) - 1) * Number(queryParams.pageSize) + : undefined, + take: queryParams.pageSize ? Number(queryParams.pageSize) : undefined, + }; + const favoriteProducts = yield userRepository_1.default.findUserFavoriteProducts(userId, options); + const favoriteArticles = yield userRepository_1.default.findUserFavoriteArticles(userId, options); + return { products: favoriteProducts, articles: favoriteArticles }; + }); +} +function getUserById(id) { + return __awaiter(this, void 0, void 0, function* () { + const user = yield userRepository_1.default.findById(id); + return user; + }); +} +exports.default = { + getUserProfile, + updateUserProfile, + updateUserPassword, + getUserProducts, + getUserFavorites, + getUserById, +}; +//# sourceMappingURL=userService.js.map \ No newline at end of file diff --git a/dist/services/userService.js.map b/dist/services/userService.js.map new file mode 100644 index 00000000..582a8f07 --- /dev/null +++ b/dist/services/userService.js.map @@ -0,0 +1 @@ +{"version":3,"file":"userService.js","sourceRoot":"","sources":["../../src/services/userService.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/utils/CustomError.js b/dist/utils/CustomError.js new file mode 100644 index 00000000..58942731 --- /dev/null +++ b/dist/utils/CustomError.js @@ -0,0 +1,18 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CustomError = void 0; +class CustomError extends Error { + constructor(statusCode, message) { + super(message); + Object.defineProperty(this, "statusCode", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + this.statusCode = statusCode; + this.name = this.constructor.name; + } +} +exports.CustomError = CustomError; +//# sourceMappingURL=CustomError.js.map \ No newline at end of file diff --git a/dist/utils/CustomError.js.map b/dist/utils/CustomError.js.map new file mode 100644 index 00000000..2da818ae --- /dev/null +++ b/dist/utils/CustomError.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CustomError.js","sourceRoot":"","sources":["../../src/utils/CustomError.ts"],"names":[],"mappings":";;;AAAA,MAAM,WAAY,SAAQ,KAAK;IAE7B,YAAY,UAAkB,EAAE,OAAe;QAC7C,KAAK,CAAC,OAAO,CAAC,CAAC;QAFV;;;;;WAAmB;QAGxB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;IACpC,CAAC;CACF;AACQ,kCAAW"} \ No newline at end of file diff --git a/dist/utils/asyncHandler.js b/dist/utils/asyncHandler.js new file mode 100644 index 00000000..be7bd5f2 --- /dev/null +++ b/dist/utils/asyncHandler.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.asyncHandler = void 0; +const asyncHandler = (fn) => (req, res, next) => { + Promise.resolve(fn(req, res, next)).catch(next); +}; +exports.asyncHandler = asyncHandler; +//# sourceMappingURL=asyncHandler.js.map \ No newline at end of file diff --git a/dist/utils/asyncHandler.js.map b/dist/utils/asyncHandler.js.map new file mode 100644 index 00000000..d4b3eda1 --- /dev/null +++ b/dist/utils/asyncHandler.js.map @@ -0,0 +1 @@ +{"version":3,"file":"asyncHandler.js","sourceRoot":"","sources":["../../src/utils/asyncHandler.ts"],"names":[],"mappings":";;;AAQO,MAAM,YAAY,GACvB,CAAU,EAAoB,EAAE,EAAE,CAClC,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;IACxD,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAClD,CAAC,CAAC;AAJS,QAAA,YAAY,gBAIrB"} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..f59e49fc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4635 @@ +{ + "name": "express-user-system", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "express-user-system", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@aws-sdk/client-s3": "^3.837.0", + "@aws-sdk/s3-request-presigner": "^3.837.0", + "@prisma/client": "^5.17.0", + "bcrypt": "^5.1.1", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.19.2", + "express-jwt": "^8.5.1", + "express-session": "^1.18.1", + "jsonwebtoken": "^9.0.2", + "multer": "^1.4.5-lts.2", + "multer-s3": "^3.0.1", + "passport": "^0.7.0", + "passport-google-oauth20": "^2.0.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", + "prisma": "^5.17.0" + }, + "devDependencies": { + "@types/bcrypt": "^5.0.2", + "@types/cookie-parser": "^1.4.9", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.3", + "@types/express-session": "^1.18.2", + "@types/jsonwebtoken": "^9.0.9", + "@types/multer": "^1.4.13", + "@types/multer-s3": "^3.0.3", + "@types/node": "^24.0.1", + "@types/passport": "^1.0.17", + "@types/passport-google-oauth20": "^2.0.16", + "@types/passport-jwt": "^4.0.1", + "@types/passport-local": "^1.0.38", + "nodemon": "^3.1.0", + "ts-node": "^10.9.2", + "ts-node-dev": "^2.0.0", + "typescript": "^5.8.3" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.837.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.837.0.tgz", + "integrity": "sha512-sBjPPG30HIfNwpzWuajCDf7agb4YAxPFFpsp3kwgptJF8PEi0HzQg64bskquMzjqLC2tXsn5rKtDVpQOvs29MQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/credential-provider-node": "3.835.0", + "@aws-sdk/middleware-bucket-endpoint": "3.830.0", + "@aws-sdk/middleware-expect-continue": "3.821.0", + "@aws-sdk/middleware-flexible-checksums": "3.835.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-location-constraint": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-sdk-s3": "3.835.0", + "@aws-sdk/middleware-ssec": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.835.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/signature-v4-multi-region": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.835.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/eventstream-serde-browser": "^4.0.4", + "@smithy/eventstream-serde-config-resolver": "^4.1.2", + "@smithy/eventstream-serde-node": "^4.0.4", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-blob-browser": "^4.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/hash-stream-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/md5-js": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/middleware-retry": "^4.1.13", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.20", + "@smithy/util-defaults-mode-node": "^4.0.20", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.5", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.835.0.tgz", + "integrity": "sha512-4J19IcBKU5vL8yw/YWEvbwEGcmCli0rpRyxG53v0K5/3weVPxVBbKfkWcjWVQ4qdxNz2uInfbTde4BRBFxWllQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.835.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.835.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/middleware-retry": "^4.1.13", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.20", + "@smithy/util-defaults-mode-node": "^4.0.20", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.835.0.tgz", + "integrity": "sha512-7mnf4xbaLI8rkDa+w6fUU48dG6yDuOgLXEPe4Ut3SbMp1ceJBPMozNHbCwkiyHk3HpxZYf8eVy0wXhJMrxZq5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.5.3", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.835.0.tgz", + "integrity": "sha512-U9LFWe7+ephNyekpUbzT7o6SmJTmn6xkrPkE0D7pbLojnPVi/8SZKyjtgQGIsAv+2kFkOCqMOIYUKd/0pE7uew==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.835.0.tgz", + "integrity": "sha512-jCdNEsQklil7frDm/BuVKl4ubVoQHRbV6fnkOjmxAJz0/v7cR8JP0jBGlqKKzh3ROh5/vo1/5VUZbCTLpc9dSg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.4", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.835.0.tgz", + "integrity": "sha512-nqF6rYRAnJedmvDfrfKygzyeADcduDvtvn7GlbQQbXKeR2l7KnCdhuxHa0FALLvspkHiBx7NtInmvnd5IMuWsw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/credential-provider-env": "3.835.0", + "@aws-sdk/credential-provider-http": "3.835.0", + "@aws-sdk/credential-provider-process": "3.835.0", + "@aws-sdk/credential-provider-sso": "3.835.0", + "@aws-sdk/credential-provider-web-identity": "3.835.0", + "@aws-sdk/nested-clients": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.835.0.tgz", + "integrity": "sha512-77B8elyZlaEd7vDYyCnYtVLuagIBwuJ0AQ98/36JMGrYX7TT8UVAhiDAfVe0NdUOMORvDNFfzL06VBm7wittYw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.835.0", + "@aws-sdk/credential-provider-http": "3.835.0", + "@aws-sdk/credential-provider-ini": "3.835.0", + "@aws-sdk/credential-provider-process": "3.835.0", + "@aws-sdk/credential-provider-sso": "3.835.0", + "@aws-sdk/credential-provider-web-identity": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.835.0.tgz", + "integrity": "sha512-qXkTt5pAhSi2Mp9GdgceZZFo/cFYrA735efqi/Re/nf0lpqBp8mRM8xv+iAaPHV4Q10q0DlkbEidT1DhxdT/+w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.835.0.tgz", + "integrity": "sha512-jAiEMryaPFXayYGszrc7NcgZA/zrrE3QvvvUBh/Udasg+9Qp5ZELdJCm/p98twNyY9n5i6Ex6VgvdxZ7+iEheQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.835.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/token-providers": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.835.0.tgz", + "integrity": "sha512-zfleEFXDLlcJ7cyfS4xSyCRpd8SVlYZfH3rp0pg2vPYKbnmXVE0r+gPIYXl4L+Yz4A2tizYl63nKCNdtbxadog==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/nested-clients": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/lib-storage": { + "version": "3.837.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.837.0.tgz", + "integrity": "sha512-V7NkOw8bX1HdRTWSy+pMCpHQgSaUh/l1fQIx63anu4TORe18pkLkos0x5YnPJ+o2ksbCVtokDuOG1jGdVM0NPg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/smithy-client": "^4.4.4", + "buffer": "5.6.0", + "events": "3.3.0", + "stream-browserify": "3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-s3": "^3.837.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.830.0.tgz", + "integrity": "sha512-ElVeCReZSH5Ds+/pkL5ebneJjuo8f49e9JXV1cYizuH0OAOQfYaBU9+M+7+rn61pTttOFE8W//qKzrXBBJhfMg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.821.0.tgz", + "integrity": "sha512-zAOoSZKe1njOrtynvK6ZORU57YGv5I7KP4+rwOvUN3ZhJbQ7QPf8gKtFUCYAPRMegaXCKF/ADPtDZBAmM+zZ9g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.835.0.tgz", + "integrity": "sha512-9ezorQYlr5cQY28zWAReFhNKUTaXsi3TMvXIagMRrSeWtQ7R6TCYnt91xzHRCmFR2kp3zLI+dfoeH+wF3iCKUw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", + "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.821.0.tgz", + "integrity": "sha512-sKrm80k0t3R0on8aA/WhWFoMaAl4yvdk+riotmMElLUpcMcRXAd1+600uFVrxJqZdbrKQ0mjX0PjT68DlkYXLg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", + "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", + "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.835.0.tgz", + "integrity": "sha512-oPebxpVf9smInHhevHh3APFZagGU+4RPwXEWv9YtYapFvsMq+8QXFvOfxfVZ/mwpe0JVG7EiJzL9/9Kobmts8Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/core": "^3.5.3", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.4", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.821.0.tgz", + "integrity": "sha512-YYi1Hhr2AYiU/24cQc8HIB+SWbQo6FBkMYojVuz/zgrtkFmALxENGF/21OPg7f/QWd+eadZJRxCjmRwh5F2Cxg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.835.0.tgz", + "integrity": "sha512-2gmAYygeE/gzhyF2XlkcbMLYFTbNfV61n+iCFa/ZofJHXYE+RxSyl5g4kujLEs7bVZHmjQZJXhprVSkGccq3/w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@smithy/core": "^3.5.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.835.0.tgz", + "integrity": "sha512-UtmOO0U5QkicjCEv+B32qqRAnS7o2ZkZhC+i3ccH1h3fsfaBshpuuNBwOYAzRCRBeKW5fw3ANFrV/+2FTp4jWg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.835.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.835.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/middleware-retry": "^4.1.13", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.20", + "@smithy/util-defaults-mode-node": "^4.0.20", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", + "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner": { + "version": "3.837.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.837.0.tgz", + "integrity": "sha512-h/D/cqeciBPGFSHIHRQm0q/CDvToV4rUoPef3tWzYtfoKzqfYaqRO175FnDv/4XgOYpdoqv6q36bx8KueVQ62w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/signature-v4-multi-region": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-format-url": "3.821.0", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.835.0.tgz", + "integrity": "sha512-rEtJH4dIwJYlXXe5rIH+uTCQmd2VIjuaoHlDY3Dr4nxF6po6U7vKsLfybIU2tgflGVqoqYQnXsfW/kj/Rh+/ow==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.835.0.tgz", + "integrity": "sha512-zN1P3BE+Rv7w7q/CDA8VCQox6SE9QTn0vDtQ47AHA3eXZQQgYzBqgoLgJxR9rKKBIRGZqInJa/VRskLL95VliQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/nested-clients": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.821.0.tgz", + "integrity": "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz", + "integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.828.0.tgz", + "integrity": "sha512-RvKch111SblqdkPzg3oCIdlGxlQs+k+P7Etory9FmxPHyPDvsP1j1c74PmgYqtzzMWmoXTjd+c9naUHh9xG8xg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.821.0.tgz", + "integrity": "sha512-h+xqmPToxDrZ0a7rxE1a8Oh4zpWfZe9oiQUphGtfiGFA6j75UiURH5J3MmGHa/G4t15I3iLLbYtUXxvb1i7evg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.804.0.tgz", + "integrity": "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", + "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.835.0.tgz", + "integrity": "sha512-gY63QZ4W5w9JYHYuqvUxiVGpn7IbCt1ODPQB0ZZwGGr3WRmK+yyZxCtFjbYhEQDQLgTWpf8YgVxgQLv2ps0PJg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", + "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@prisma/client": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.17.0.tgz", + "integrity": "sha512-N2tnyKayT0Zf7mHjwEyE8iG7FwTmXDHFZ1GnNhQp0pJUObsuel4ZZ1XwfuAYkq5mRIiC/Kot0kt0tGCfLJ70Jw==", + "hasInstallScript": true, + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.17.0.tgz", + "integrity": "sha512-l7+AteR3P8FXiYyo496zkuoiJ5r9jLQEdUuxIxNCN1ud8rdbH3GTxm+f+dCyaSv9l9WY+29L9czaVRXz9mULfg==" + }, + "node_modules/@prisma/engines": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.17.0.tgz", + "integrity": "sha512-+r+Nf+JP210Jur+/X8SIPLtz+uW9YA4QO5IXA+KcSOBe/shT47bCcRMTYCbOESw3FFYFTwe7vU6KTWHKPiwvtg==", + "hasInstallScript": true, + "dependencies": { + "@prisma/debug": "5.17.0", + "@prisma/engines-version": "5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053", + "@prisma/fetch-engine": "5.17.0", + "@prisma/get-platform": "5.17.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053.tgz", + "integrity": "sha512-tUuxZZysZDcrk5oaNOdrBnnkoTtmNQPkzINFDjz7eG6vcs9AVDmA/F6K5Plsb2aQc/l5M2EnFqn3htng9FA4hg==" + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.17.0.tgz", + "integrity": "sha512-ESxiOaHuC488ilLPnrv/tM2KrPhQB5TRris/IeIV4ZvUuKeaicCl4Xj/JCQeG9IlxqOgf1cCg5h5vAzlewN91Q==", + "dependencies": { + "@prisma/debug": "5.17.0", + "@prisma/engines-version": "5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053", + "@prisma/get-platform": "5.17.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.17.0.tgz", + "integrity": "sha512-UlDgbRozCP1rfJ5Tlkf3Cnftb6srGrEQ4Nm3og+1Se2gWmCZ0hmPIi+tQikGDUVLlvOWx3Gyi9LzgRP+HTXV9w==", + "dependencies": { + "@prisma/debug": "5.17.0" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz", + "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz", + "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.6.0.tgz", + "integrity": "sha512-Pgvfb+TQ4wUNLyHzvgCP4aYZMh16y7GcfF59oirRHcgGgkH1e/s9C0nv/v3WP+Quymyr5je71HeFQCwh+44XLg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.4.tgz", + "integrity": "sha512-7XoWfZqWb/QoR/rAU4VSi0mWnO2vu9/ltS6JZ5ZSZv0eovLVfDfu0/AX4ub33RsJTOth3TiFWSHS5YdztvFnig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.4.tgz", + "integrity": "sha512-3fb/9SYaYqbpy/z/H3yIi0bYKyAa89y6xPmIqwr2vQiUT2St+avRt8UKwsWt9fEdEasc5d/V+QjrviRaX1JRFA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.2.tgz", + "integrity": "sha512-JGtambizrWP50xHgbzZI04IWU7LdI0nh/wGbqH3sJesYToMi2j/DcoElqyOcqEIG/D4tNyxgRuaqBXWE3zOFhQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.4.tgz", + "integrity": "sha512-RD6UwNZ5zISpOWPuhVgRz60GkSIp0dy1fuZmj4RYmqLVRtejFqQ16WmfYDdoSoAjlp1LX+FnZo+/hkdmyyGZ1w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.4.tgz", + "integrity": "sha512-UeJpOmLGhq1SLox79QWw/0n2PFX+oPRE1ZyRMxPIaFEfCqWaqpB7BU9C8kpPOGEhLF7AwEqfFbtwNxGy4ReENA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.4.tgz", + "integrity": "sha512-WszRiACJiQV3QG6XMV44i5YWlkrlsM5Yxgz4jvsksuu7LDXA6wAtypfPajtNTadzpJy3KyJPoWehYpmZGKUFIQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.0.0", + "@smithy/chunked-blob-reader-native": "^4.0.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.4.tgz", + "integrity": "sha512-wHo0d8GXyVmpmMh/qOR0R7Y46/G1y6OR8U+bSTB4ppEzRxd1xVAQ9xOE9hOc0bSjhz0ujCPAbfNLkLrpa6cevg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.4.tgz", + "integrity": "sha512-uGLBVqcOwrLvGh/v/jw423yWHq/ofUGK1W31M2TNspLQbUV1Va0F5kTxtirkoHawODAZcjXTSGi7JwbnPcDPJg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.13.tgz", + "integrity": "sha512-xg3EHV/Q5ZdAO5b0UiIMj3RIOCobuS40pBBODguUDVdko6YK6QIzCVRrHTogVuEKglBWqWenRnZ71iZnLL3ZAQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.6.0", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.14.tgz", + "integrity": "sha512-eoXaLlDGpKvdmvt+YBfRXE7HmIEtFF+DJCbTPwuLunP0YUnrydl+C4tS+vEM0+nyxXrX3PSUFqC+lP1+EHB1Tw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.6", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.6.tgz", + "integrity": "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", + "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.5.tgz", + "integrity": "sha512-+lynZjGuUFJaMdDYSTMnP/uPBBXXukVfrJlP+1U/Dp5SFTEI++w6NMga8DjOENxecOF71V9Z2DllaVDYRnGlkg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.6.0", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.21", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.21.tgz", + "integrity": "sha512-wM0jhTytgXu3wzJoIqpbBAG5U6BwiubZ6QKzSbP7/VbmF1v96xlAbX2Am/mz0Zep0NLvLh84JT0tuZnk3wmYQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.21", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.21.tgz", + "integrity": "sha512-/F34zkoU0GzpUgLJydHY8Rxu9lBn8xQC/s/0M0U9lLBkYbA1htaAFjWYJzpzsbXPuri5D1H8gjp2jBum05qBrA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.6.tgz", + "integrity": "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.0.6", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.6.tgz", + "integrity": "sha512-slcr1wdRbX7NFphXZOxtxRNA7hXAAtJAXJDE/wdoMAos27SIquVCKiSqfB6/28YzQ8FCsB5NKkhdM5gMADbqxg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookie-parser": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.9.tgz", + "integrity": "sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express-session": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", + "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" + }, + "node_modules/@types/multer": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", + "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/multer-s3": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/multer-s3/-/multer-s3-3.0.3.tgz", + "integrity": "sha512-VgWygI9UwyS7loLithUUi0qAMIDWdNrERS2Sb06UuPYiLzKuIFn2NgL7satyl4v8sh/LLoU7DiPanvbQaRg9Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@aws-sdk/client-s3": "^3.0.0", + "@types/multer": "*", + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "24.0.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.1.tgz", + "integrity": "sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/oauth": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.6.tgz", + "integrity": "sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/passport": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", + "integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-google-oauth20": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.16.tgz", + "integrity": "sha512-ayXK2CJ7uVieqhYOc6k/pIr5pcQxOLB6kBev+QUGS7oEZeTgIs1odDobXRqgfBPvXzl0wXCQHftV5220czZCPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-oauth2": "*" + } + }, + "node_modules/@types/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-local": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.38.tgz", + "integrity": "sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-oauth2": { + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.17.tgz", + "integrity": "sha512-ODiAHvso6JcWJ6ZkHHroVp05EHGhqQN533PtFNBkg8Fy5mERDqsr030AX81M0D69ZcaMvhF92SRckEk2B0HYYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/oauth": "*", + "@types/passport": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", + "integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "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.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.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" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express-jwt": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-8.5.1.tgz", + "integrity": "sha512-Dv6QjDLpR2jmdb8M6XQXiCcpEom7mK8TOqnr0/TngDKsG2DHVkO8+XnVxkJVN7BuS1I3OrGw6N8j5DaaGgkDRQ==", + "dependencies": { + "@types/jsonwebtoken": "^9", + "express-unless": "^2.1.3", + "jsonwebtoken": "^9.0.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/express-session": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + }, + "node_modules/express-unless": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-2.1.3.tgz", + "integrity": "sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ==" + }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "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": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "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" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/multer-s3": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/multer-s3/-/multer-s3-3.0.1.tgz", + "integrity": "sha512-BFwSO80a5EW4GJRBdUuSHblz2jhVSAze33ZbnGpcfEicoT0iRolx4kWR+AJV07THFRCQ78g+kelKFdjkCCaXeQ==", + "license": "MIT", + "dependencies": { + "@aws-sdk/lib-storage": "^3.46.0", + "file-type": "^3.3.0", + "html-comment-regex": "^1.1.2", + "run-parallel": "^1.1.6" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-s3": "^3.0.0" + } + }, + "node_modules/multer/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nodemon": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/oauth": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz", + "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==", + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-google-oauth20": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", + "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", + "license": "MIT", + "dependencies": { + "passport-oauth2": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-oauth2": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", + "license": "MIT", + "dependencies": { + "base64url": "3.x.x", + "oauth": "0.10.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prisma": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.17.0.tgz", + "integrity": "sha512-m4UWkN5lBE6yevqeOxEvmepnL5cNPEjzMw2IqDB59AcEV6w7D8vGljDLd1gPFH+W6gUxw9x7/RmN5dCS/WTPxA==", + "hasInstallScript": true, + "dependencies": { + "@prisma/engines": "5.17.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node-dev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", + "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^10.4.0", + "tsconfig": "^7.0.0" + }, + "bin": { + "ts-node-dev": "lib/bin.js", + "tsnd": "lib/bin.js" + }, + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "node-notifier": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/ts-node-dev/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==", + "license": "MIT" + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..0a40b027 --- /dev/null +++ b/package.json @@ -0,0 +1,54 @@ +{ + "name": "express-user-system", + "version": "1.0.0", + "description": "", + "license": "ISC", + "author": "", + "main": "src/index.js", + "scripts": { + "build": "tsc", + "start": "node dist/app.js", + "dev": "ts-node-dev --respawn src/app.ts", + "migrate": "prisma migrate dev", + "studio": "prisma studio" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.837.0", + "@aws-sdk/s3-request-presigner": "^3.837.0", + "@prisma/client": "^5.17.0", + "bcrypt": "^5.1.1", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.19.2", + "express-jwt": "^8.5.1", + "express-session": "^1.18.1", + "jsonwebtoken": "^9.0.2", + "multer": "^1.4.5-lts.2", + "multer-s3": "^3.0.1", + "passport": "^0.7.0", + "passport-google-oauth20": "^2.0.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", + "prisma": "^5.17.0" + }, + "devDependencies": { + "@types/bcrypt": "^5.0.2", + "@types/cookie-parser": "^1.4.9", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.3", + "@types/express-session": "^1.18.2", + "@types/jsonwebtoken": "^9.0.9", + "@types/multer": "^1.4.13", + "@types/multer-s3": "^3.0.3", + "@types/node": "^24.0.1", + "@types/passport": "^1.0.17", + "@types/passport-google-oauth20": "^2.0.16", + "@types/passport-jwt": "^4.0.1", + "@types/passport-local": "^1.0.38", + "nodemon": "^3.1.0", + "ts-node": "^10.9.2", + "ts-node-dev": "^2.0.0", + "typescript": "^5.8.3" + } +} diff --git a/prisma/migrations/20250612012336_init/migration.sql b/prisma/migrations/20250612012336_init/migration.sql new file mode 100644 index 00000000..e0494d89 --- /dev/null +++ b/prisma/migrations/20250612012336_init/migration.sql @@ -0,0 +1,104 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "nickname" VARCHAR(20) NOT NULL, + "email" TEXT NOT NULL, + "password" TEXT NOT NULL, + "image" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Product" ( + "id" SERIAL NOT NULL, + "name" VARCHAR(30) NOT NULL, + "description" TEXT NOT NULL, + "price" INTEGER NOT NULL, + "tags" TEXT[], + "images" TEXT[], + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "ownerId" INTEGER NOT NULL, + + CONSTRAINT "Product_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Article" ( + "id" SERIAL NOT NULL, + "title" VARCHAR(50) NOT NULL, + "content" TEXT NOT NULL, + "image" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "writerId" INTEGER NOT NULL, + + CONSTRAINT "Article_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Comment" ( + "id" SERIAL NOT NULL, + "content" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "writerId" INTEGER NOT NULL, + "productId" INTEGER, + "articleId" INTEGER, + + CONSTRAINT "Comment_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ProductLike" ( + "userId" INTEGER NOT NULL, + "productId" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "ProductLike_pkey" PRIMARY KEY ("userId","productId") +); + +-- CreateTable +CREATE TABLE "ArticleLike" ( + "userId" INTEGER NOT NULL, + "articleId" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "ArticleLike_pkey" PRIMARY KEY ("userId","articleId") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_nickname_key" ON "User"("nickname"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- AddForeignKey +ALTER TABLE "Product" ADD CONSTRAINT "Product_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Article" ADD CONSTRAINT "Article_writerId_fkey" FOREIGN KEY ("writerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Comment" ADD CONSTRAINT "Comment_writerId_fkey" FOREIGN KEY ("writerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Comment" ADD CONSTRAINT "Comment_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Comment" ADD CONSTRAINT "Comment_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProductLike" ADD CONSTRAINT "ProductLike_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProductLike" ADD CONSTRAINT "ProductLike_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ArticleLike" ADD CONSTRAINT "ArticleLike_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ArticleLike" ADD CONSTRAINT "ArticleLike_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..fbffa92c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 00000000..17086ca1 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,104 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id Int @id @default(autoincrement()) + nickname String @unique @db.VarChar(20) + email String @unique + password String // 해시된 비밀번호 저장 + image String? // 사용자 이미지 URL, null 가능 + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // 관계 + products Product[] // 이 사용자가 생성한 상품 + articles Article[] // 이 사용자가 작성한 게시글 + comments Comment[] // 이 사용자가 작성한 댓글 + likedProducts ProductLike[] // 이 사용자가 좋아요한 상품 + likedArticles ArticleLike[] // 이 사용자가 좋아요한 게시글 +} + +model Product { + id Int @id @default(autoincrement()) + name String @db.VarChar(30) + description String + price Int // `minimum: 0`은 애플리케이션 레벨 유효성 검사 + tags String[] // 태그 문자열 배열 + images String[] // 이미지 URL 배열. 배열의 `minLength: 1`은 앱 레벨 유효성 검사. + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // 사용자와의 관계 (소유자) + ownerId Int + owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade) + + // 관계 + comments Comment[] // 이 상품에 대한 댓글 + likedBy ProductLike[] // 사용자와의 다대다 관계 (좋아요)를 위한 조인 테이블 +} + +model Article { + id Int @id @default(autoincrement()) + title String @db.VarChar(50) + content String // `minLength: 1`은 애플리케이션 레벨 유효성 검사 + image String? // 게시글 이미지 URL, null 가능 + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // 사용자와의 관계 (작성자) + writerId Int + writer User @relation(fields: [writerId], references: [id], onDelete: Cascade) + + // 관계 + comments Comment[] // 이 게시글에 대한 댓글 + likedBy ArticleLike[] // 사용자와의 다대다 관계 (좋아요)를 위한 조인 테이블 +} + +model Comment { + id Int @id @default(autoincrement()) + content String // `minLength: 1`은 애플리케이션 레벨 유효성 검사 + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // 사용자와의 관계 (작성자) + writerId Int + writer User @relation(fields: [writerId], references: [id], onDelete: Cascade) + + // 상품과의 관계 (선택 사항) + product Product? @relation(fields: [productId], references: [id], onDelete: Cascade) + productId Int? + + // 게시글과의 관계 (선택 사항) + article Article? @relation(fields: [articleId], references: [id], onDelete: Cascade) + articleId Int? +} + +// 사용자-상품 좋아요를 위한 조인 테이블 (다대다) (변경) +model ProductLike { + userId Int + productId Int + createdAt DateTime @default(now()) + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + product Product @relation(fields: [productId], references: [id], onDelete: Cascade) + + @@id([userId, productId]) // 복합 기본 키 +} + +// 사용자-게시글 좋아요를 위한 조인 테이블 (다대다) +model ArticleLike { + userId Int + articleId Int + createdAt DateTime @default(now()) + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + article Article @relation(fields: [articleId], references: [id], onDelete: Cascade) + + @@id([userId, articleId]) // 복합 기본 키 +} diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 00000000..dafe1f5c Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 00000000..a08fc87f --- /dev/null +++ b/src/app.ts @@ -0,0 +1,39 @@ +import "dotenv/config"; +import express, { Express, Request, Response, NextFunction } from "express"; +import cors from "cors"; +import cookieParser from "cookie-parser"; +import passport from "./config/passport"; +import authController from "./controllers/authController"; +import userController from "./controllers/userController"; +import imageController from "./controllers/imageController"; +import productController from "./controllers/productController"; +import articleController from "./controllers/articleController"; +import commentController from "./controllers/commentController"; +import errorHandler from "./middlewares/errorHandler"; + +const app: Express = express(); + +app.use( + cors({ + origin: "https://6-sprint-mission-fe-pxcf.vercel.app", // 프론트엔드 주소 + credentials: true, + }) +); +app.use(express.json()); +app.use(cookieParser()); + +app.use(passport.initialize()); + +app.use("/auth", authController); +app.use("/users", userController); +app.use("/products", productController); +app.use("/articles", articleController); +app.use("/uploads", express.static("uploads")); +app.use("/images", imageController); +app.use(commentController); +app.use(errorHandler); + +const port = process.env.PORT ?? 3000; +app.listen(port, () => { + console.log(`Server is running on port ${port}`); +}); diff --git a/src/config/passport.ts b/src/config/passport.ts new file mode 100644 index 00000000..195fb771 --- /dev/null +++ b/src/config/passport.ts @@ -0,0 +1,7 @@ +import passport from "passport"; +import jwt from "../middlewares/passport/jwtStrategy"; + +passport.use("access-token", jwt.accessTokenStrategy); +passport.use("refresh-token", jwt.refreshTokenStrategy); + +export default passport; diff --git a/src/config/prisma.ts b/src/config/prisma.ts new file mode 100644 index 00000000..4e54f7a7 --- /dev/null +++ b/src/config/prisma.ts @@ -0,0 +1,5 @@ +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +export default prisma; diff --git a/src/controllers/articleController.ts b/src/controllers/articleController.ts new file mode 100644 index 00000000..1f3b2150 --- /dev/null +++ b/src/controllers/articleController.ts @@ -0,0 +1,153 @@ +import express, { Request, Response } from "express"; +import articleService from "../services/articleService"; +import passport from "../config/passport"; +import { asyncHandler } from "../utils/asyncHandler"; +import { CustomError } from "../utils/CustomError"; +import { TokenUserPayload } from "../services/authService"; + +declare global { + namespace Express { + interface User extends TokenUserPayload {} + } +} + +const articleController = express.Router(); + +articleController.post( + "/", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const writerId = req.user.id; + const articleData = { ...req.body, writerId }; + + if (!articleData.title || !articleData.content) { + throw new CustomError(422, "제목과 내용은 필수입니다."); + } + + const article = await articleService.createArticle(articleData); + res.status(201).json(article); + }) +); + +articleController.get( + "/", + asyncHandler(async (req: Request, res: Response) => { + const articles = await articleService.getArticles(req.query, req.user?.id); + res.json(articles); + }) +); + +articleController.get( + "/:articleId", + asyncHandler(async (req: Request, res: Response) => { + const { articleId } = req.params; + const article = await articleService.getArticleById( + parseInt(articleId, 10), + req.user?.id + ); + res.json(article); + }) +); + +articleController.patch( + "/:articleId", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const { articleId } = req.params; + const writerId = req.user.id; + const updateData = req.body; + + const updatedArticle = await articleService.updateArticle( + parseInt(articleId, 10), + updateData, + writerId + ); + res.json(updatedArticle); + }) +); + +articleController.delete( + "/:articleId", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const { articleId } = req.params; + const writerId = req.user.id; + + await articleService.deleteArticle(parseInt(articleId, 10), writerId); + res.status(204).send(); + }) +); + +articleController.post( + "/:articleId/like", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const { articleId } = req.params; + const userId = req.user.id; + const result = await articleService.likeArticle( + parseInt(articleId, 10), + userId + ); + res + .status(201) + .json({ message: "게시글에 좋아요를 눌렀습니다.", data: result }); + }) +); + +articleController.delete( + "/:articleId/like", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const { articleId } = req.params; + const userId = req.user.id; + await articleService.unlikeArticle(parseInt(articleId, 10), userId); + res.status(200).json({ message: "게시글 좋아요를 취소했습니다." }); + }) +); + +export default articleController; diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts new file mode 100644 index 00000000..b2d397f4 --- /dev/null +++ b/src/controllers/authController.ts @@ -0,0 +1,79 @@ +import express, { Request, Response } from "express"; +import authService, { TokenUserPayload } from "../services/authService"; +import passport from "../config/passport"; +import { asyncHandler } from "../utils/asyncHandler"; +import { CustomError } from "../utils/CustomError"; + +declare global { + namespace Express { + interface User extends TokenUserPayload {} + } +} + +const authController = express.Router(); + +authController.post( + "/signup", + asyncHandler(async (req: Request, res: Response) => { + const { nickname, email, password, passwordConfirmation } = req.body; + + if (!nickname || !email || !password || !passwordConfirmation) { + throw new CustomError( + 422, + "닉네임, 이메일, 비밀번호, 비밀번호 확인은 필수입니다." + ); + } + const result = await authService.signUpUser({ + nickname, + email, + password, + passwordConfirmation, + }); + + res.status(201).json(result); + }) +); + +authController.post( + "/signin", + asyncHandler(async (req: Request, res: Response) => { + const { email, password } = req.body; + + if (!email || !password) { + throw new CustomError(422, "이메일과 비밀번호를 모두 입력해주세요."); + } + const { accessToken, refreshToken, user } = await authService.signInUser( + email, + password + ); + + res.cookie("refreshToken", refreshToken, { + httpOnly: true, + path: "/", + sameSite: "none", + secure: true, + }); + + res.json({ accessToken, user }); + }) +); + +authController.post( + "/refresh-token", + passport.authenticate("refresh-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + if (!req.user) { + throw new CustomError(401, "토큰 갱신을 위한 사용자 정보가 없습니다."); + } + + const user = req.user; + + const newAccessToken = authService.generateNewAccessToken(user); + res.json({ accessToken: newAccessToken }); + }) +); + +export default authController; diff --git a/src/controllers/commentController.ts b/src/controllers/commentController.ts new file mode 100644 index 00000000..7d8f0362 --- /dev/null +++ b/src/controllers/commentController.ts @@ -0,0 +1,136 @@ +import express, { Request, Response } from "express"; +import commentService from "../services/commentService"; +import { asyncHandler } from "../utils/asyncHandler"; +import passport from "../config/passport"; +import { CustomError } from "../utils/CustomError"; +import { TokenUserPayload } from "../services/authService"; + +declare global { + namespace Express { + interface User extends TokenUserPayload {} + } +} + +const commentController = express.Router(); + +commentController.post( + "/products/:productId/comments", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + const { productId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const writerId = req.user.id; + const { content } = req.body; + const comment = await commentService.createProductComment( + productId, + writerId, + content + ); + res.status(201).json(comment); + }) +); + +commentController.get( + "/products/:productId/comments", + asyncHandler(async (req: Request, res: Response) => { + const { productId } = req.params; + const comments = await commentService.getProductComments( + productId, + req.query + ); + res.json(comments); + }) +); + +commentController.post( + "/articles/:articleId/comments", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + const { articleId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const writerId = req.user.id; + const { content } = req.body; + const comment = await commentService.createArticleComment( + parseInt(articleId, 10), + writerId, + content + ); + res.status(201).json(comment); + }) +); + +commentController.get( + "/articles/:articleId/comments", + asyncHandler(async (req: Request, res: Response) => { + const { articleId } = req.params; + const comments = await commentService.getArticleComments( + parseInt(articleId, 10), + req.query + ); + res.json(comments); + }) +); + +commentController.patch( + "/comments/:commentId", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + const { commentId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const writerId = req.user.id; + const { content } = req.body; + const updatedComment = await commentService.updateComment( + parseInt(commentId, 10), + content, + writerId + ); + res.json(updatedComment); + }) +); + +commentController.delete( + "/comments/:commentId", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + const { commentId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const writerId = req.user.id; + + await commentService.deleteComment(parseInt(commentId, 10), writerId); + res.status(204).send(); + }) +); + +export default commentController; diff --git a/src/controllers/imageController.ts b/src/controllers/imageController.ts new file mode 100644 index 00000000..95e0c83e --- /dev/null +++ b/src/controllers/imageController.ts @@ -0,0 +1,46 @@ +// src/controllers/imageController.ts + +import express, { Request, Response } from "express"; +import { asyncHandler } from "../utils/asyncHandler"; +import passport from "../config/passport"; +import { CustomError } from "../utils/CustomError"; +import uploadMiddleware from "../middlewares/uploadMiddleware"; +import { TokenUserPayload } from "../services/authService"; + +declare global { + namespace Express { + interface User extends TokenUserPayload { + id: number; + } + } +} + +const imageController = express.Router(); + +imageController.post( + "/upload", + passport.authenticate("access-token", { session: false }), + uploadMiddleware.array("image", 5), + asyncHandler(async (req: Request, res: Response) => { + const files = req.files as { location: string }[] | undefined; + if (!files || !Array.isArray(files) || files.length === 0) { + throw new CustomError(400, "업로드할 이미지 파일들이 없습니다."); + } + + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증된 사용자만 이미지를 업로드할 수 있습니다." + ); + } + + const uploaderId = req.user.id; + + res.status(201).json({ + message: "이미지들이 성공적으로 업로드되었습니다.", + imageUrl: files.map((file) => file.location), + }); + }) +); + +export default imageController; diff --git a/src/controllers/productController.ts b/src/controllers/productController.ts new file mode 100644 index 00000000..29be893f --- /dev/null +++ b/src/controllers/productController.ts @@ -0,0 +1,154 @@ +import express, { Request, Response } from "express"; +import passport from "../config/passport"; +import productService from "../services/productService"; +import { asyncHandler } from "../utils/asyncHandler"; +import { CustomError } from "../utils/CustomError"; +import { TokenUserPayload } from "../services/authService"; + +declare global { + namespace Express { + interface User extends TokenUserPayload {} + } +} + +const productController = express.Router(); + +productController.post( + "/", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const ownerId = req.user.id; + if (!req.body.name || req.body.price === undefined) { + throw new CustomError(422, "상품 이름과 가격은 필수입니다."); + } + const createdProduct = await productService.createProduct( + req.body, + ownerId + ); + res.status(201).json(createdProduct); + }) +); + +productController.get( + "/", + asyncHandler(async (req: Request, res: Response) => { + const products = await productService.getProducts(req.query, req.user?.id); + res.json(products); + }) +); + +productController.get( + "/:productId", + asyncHandler(async (req: Request, res: Response) => { + const { productId } = req.params; + const product = await productService.getProductById( + productId, + req.user?.id + ); + res.json(product); + }) +); + +productController.patch( + "/:productId", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + const { productId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const requesterId = req.user.id; + const updateData = req.body; + + if (Object.keys(updateData).length === 0) { + throw new CustomError(400, "수정할 내용이 없습니다."); + } + + const updatedProduct = await productService.updateProduct( + productId, + updateData, + requesterId + ); + res.json(updatedProduct); + }) +); + +productController.delete( + "/:productId", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + const { productId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const requesterId = req.user.id; + + await productService.deleteProduct(productId, requesterId); + res.status(204).send(); + }) +); + +productController.post( + "/:productId/favorite", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + const { productId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const userId = req.user.id; + const result = await productService.addFavorite(productId, userId); + res + .status(201) + .json({ message: "상품을 즐겨찾기에 추가했습니다.", data: result }); + }) +); + +productController.delete( + "/:productId/favorite", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + const { productId } = req.params; + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const userId = req.user.id; + await productService.removeFavorite(productId, userId); + res.status(200).json({ message: "상품 즐겨찾기를 삭제했습니다." }); + }) +); + +export default productController; diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts new file mode 100644 index 00000000..d00472c1 --- /dev/null +++ b/src/controllers/userController.ts @@ -0,0 +1,129 @@ +import express, { Request, Response } from "express"; +import userService from "../services/userService"; +import passport from "../config/passport"; +import { asyncHandler } from "../utils/asyncHandler"; +import { CustomError } from "../utils/CustomError"; +import { TokenUserPayload } from "../services/authService"; + +declare global { + namespace Express { + interface User extends TokenUserPayload {} + } +} + +const userController = express.Router(); + +// GET /users/me - 현재 로그인된 사용자 정보 조회 +userController.get( + "/me", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const userId = req.user.id; + + const userProfile = await userService.getUserProfile(userId); + res.json(userProfile); + }) +); + +// PATCH /users/me - 현재 로그인된 사용자 정보 수정 (닉네임, 프로필 이미지 등) +userController.patch( + "/me", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const userId = req.user.id; + const updateData = req.body; + if (Object.keys(updateData).length === 0) { + throw new CustomError(400, "수정할 내용이 없습니다."); + } + const updatedUser = await userService.updateUserProfile(userId, updateData); + res.json(updatedUser); + }) +); + +// PATCH /users/me/password - 현재 로그인된 사용자 비밀번호 변경 +userController.patch( + "/me/password", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const userId = req.user.id; + const { currentPassword, newPassword } = req.body; + + if (!currentPassword || !newPassword) { + throw new CustomError( + 422, + "현재 비밀번호와 새 비밀번호를 모두 입력해야 합니다." + ); + } + await userService.updateUserPassword(userId, currentPassword, newPassword); + res.json({ message: "비밀번호가 성공적으로 변경되었습니다." }); + }) +); + +// GET /users/me/products - 현재 로그인된 사용자가 등록한 상품 목록 조회 +userController.get( + "/me/products", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const userId = req.user.id; + const products = await userService.getUserProducts(userId, req.query); + res.json(products); + }) +); + +// GET /users/me/favorites - 현재 로그인된 사용자가 즐겨찾기한 상품 및 게시글 목록 조회 +userController.get( + "/me/favorites", + passport.authenticate("access-token", { + session: false, + failWithError: true, + }), + asyncHandler(async (req: Request, res: Response) => { + if (!req.user || !req.user.id) { + throw new CustomError( + 401, + "인증되지 않은 사용자이거나 사용자 ID가 없습니다." + ); + } + const userId = req.user.id; + const favorites = await userService.getUserFavorites(userId, req.query); + res.json(favorites); + }) +); + +export default userController; diff --git a/src/middlewares/errorHandler.ts b/src/middlewares/errorHandler.ts new file mode 100644 index 00000000..cca453eb --- /dev/null +++ b/src/middlewares/errorHandler.ts @@ -0,0 +1,42 @@ +import { Request, Response, NextFunction } from "express"; +import { CustomError } from "../utils/CustomError"; + +interface AppError extends Error { + statusCode?: number; + status?: number; + code?: number; +} + +export default function errorHandler( + error: AppError, + req: Request, + res: Response, + next: NextFunction +): void { + console.error("Error caught by errorHandler:", error); + + let statusCode = error.statusCode || error.status || error.code || 500; + let responseMessage = error.message || "서버 내부 오류가 발생했습니다."; + + if (error instanceof CustomError) { + statusCode = error.statusCode; + responseMessage = error.message; + } else if ( + error.status === 401 || + (error.name === "AuthenticationError" && error.message === "Unauthorized") + ) { + statusCode = 401; + responseMessage = + "인증에 실패했습니다. 유효한 토큰이 아니거나 토큰이 만료되었을 수 있습니다."; + } else if (error.name === "SyntaxError" && error.message.includes("JSON")) { + statusCode = 400; + responseMessage = "잘못된 JSON 형식입니다."; + } + + res.status(statusCode).json({ + path: req.path, + method: req.method, + message: responseMessage, + timestamp: new Date().toISOString(), + }); +} diff --git a/src/middlewares/passport/jwtStrategy.ts b/src/middlewares/passport/jwtStrategy.ts new file mode 100644 index 00000000..b8f8cac3 --- /dev/null +++ b/src/middlewares/passport/jwtStrategy.ts @@ -0,0 +1,60 @@ +import { + Strategy as JwtStrategy, + ExtractJwt, + StrategyOptions, + VerifiedCallback, +} from "passport-jwt"; +import { Request } from "express"; +import userService from "../../services/userService"; +import { User } from "@prisma/client"; + +interface JwtPayload { + userId: User["id"]; +} + +if (!process.env.JWT_SECRET) { + console.error("치명적 오류: JWT_SECRET 환경 변수가 설정되지 않았습니다."); + process.exit(1); +} + +const accessTokenOptions: StrategyOptions = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: process.env.JWT_SECRET!, + passReqToCallback: false, +}; + +const cookieExtractor = (req: Request): string | null => { + let token: string | null = null; + if (req && req.cookies) { + token = req.cookies["refreshToken"]; + } + return token; +}; + +const refreshTokenOptions: StrategyOptions = { + jwtFromRequest: cookieExtractor, + secretOrKey: process.env.JWT_SECRET!, + passReqToCallback: false, +}; + +type SafeUser = Omit; + +async function jwtVerify(payload: JwtPayload, done: VerifiedCallback) { + try { + const user: SafeUser | null = await userService.getUserById(payload.userId); + if (!user) { + return done(null, false); + } + return done(null, user as User); + } catch (error) { + return done(error, false); + } +} + +const accessTokenStrategy = new JwtStrategy(accessTokenOptions, jwtVerify); +const refreshTokenStrategy = new JwtStrategy(refreshTokenOptions, jwtVerify); + +export default { + accessTokenStrategy, + refreshTokenStrategy, +}; diff --git a/src/middlewares/uploadMiddleware.ts b/src/middlewares/uploadMiddleware.ts new file mode 100644 index 00000000..62ad7ce1 --- /dev/null +++ b/src/middlewares/uploadMiddleware.ts @@ -0,0 +1,74 @@ +import path from "path"; +import { S3Client } from "@aws-sdk/client-s3"; +import multerS3 from "multer-s3"; +import multer, { FileFilterCallback } from "multer"; +import { Request } from "express"; +import { CustomError } from "../utils/CustomError"; + +const { + AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY, + AWS_REGION, + AWS_BUCKET_NAME, +} = process.env; + +if ( + !AWS_ACCESS_KEY_ID || + !AWS_SECRET_ACCESS_KEY || + !AWS_REGION || + !AWS_BUCKET_NAME +) { + throw new Error( + "AWS S3 환경 변수가 설정되지 않았습니다. .env 파일을 확인해주세요. (필수: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_BUCKET_NAME)" + ); +} + +const s3 = new S3Client({ + credentials: { + accessKeyId: AWS_ACCESS_KEY_ID, + secretAccessKey: AWS_SECRET_ACCESS_KEY, + }, + region: AWS_REGION, +}); + +const storage = multerS3({ + s3: s3, + bucket: AWS_BUCKET_NAME, + contentType: multerS3.AUTO_CONTENT_TYPE, + key: ( + req: Request, + file: Express.Multer.File, + cb: (error: any, key?: string) => void + ) => { + const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9); + const extension = path.extname(file.originalname); + cb(null, `public/images/${uniqueSuffix}${extension}`); + }, +}); + +const fileFilter = ( + req: Request, + file: Express.Multer.File, + cb: FileFilterCallback +) => { + const allowedMimeTypes = ["image/jpeg", "image/png", "image/gif"]; + if (allowedMimeTypes.includes(file.mimetype)) { + cb(null, true); // 파일 허용 + } else { + const error = new CustomError( + 400, + "지원하지 않는 파일 형식입니다. (jpeg, png, gif만 허용됩니다.)" + ); + cb(error); + } +}; + +const uploadMiddleware = multer({ + storage: storage, + fileFilter: fileFilter, + limits: { + fileSize: 5 * 1024 * 1024, + }, +}); + +export default uploadMiddleware; diff --git a/src/repositories/articleRepository.ts b/src/repositories/articleRepository.ts new file mode 100644 index 00000000..977b9ee4 --- /dev/null +++ b/src/repositories/articleRepository.ts @@ -0,0 +1,196 @@ +import prisma from "../config/prisma"; +import { Prisma, Article, ArticleLike } from "@prisma/client"; + +const _writerSelection = { + select: { + id: true, + nickname: true, + image: true, + }, +} as const; + +const _countSelection = { + select: { likedBy: true, comments: true }, +} as const; + +interface ArticleFindAllOptions { + skip?: string | number; + take?: string | number; + where?: Prisma.ArticleWhereInput; + orderBy?: + | Prisma.ArticleOrderByWithRelationInput + | Prisma.ArticleOrderByWithRelationInput[]; +} + +async function create(data: Prisma.ArticleCreateInput): Promise< + Prisma.ArticleGetPayload<{ + include: { + writer: typeof _writerSelection; + _count: typeof _countSelection; + }; + }> +> { + return prisma.article.create({ + data, + include: { + writer: _writerSelection, + _count: _countSelection, + }, + }); +} + +async function findAll( + options: ArticleFindAllOptions = {}, + userId?: number +): Promise<{ + list: Prisma.ArticleGetPayload<{ + include: { + writer: typeof _writerSelection; + _count: typeof _countSelection; + }; + }>[]; + totalCount: number; +}> { + const { skip, take = 10, where, orderBy = { createdAt: "desc" } } = options; + + const articles = await prisma.article.findMany({ + where, + include: { + writer: _writerSelection, + _count: _countSelection, + }, + orderBy, + skip: skip !== undefined ? parseInt(String(skip), 10) : 0, + take: take !== undefined ? parseInt(String(take), 10) : 10, + }); + + const totalCount = await prisma.article.count({ where }); + + const listWithIsLiked = articles.map((article) => { + const likedByArray = (article as any).likedBy; + const isLiked = + userId && Array.isArray(likedByArray) + ? likedByArray.some( + (like: { userId: number }) => like.userId === userId + ) + : false; + return { ...article, isLiked }; + }); + + return { list: listWithIsLiked, totalCount }; +} + +async function findById( + id: number, + userId?: number +): Promise | null> { + return prisma.article.findUnique({ + where: { id }, + include: { + writer: _writerSelection, + comments: { + orderBy: { createdAt: "desc" }, + take: 5, + include: { + writer: _writerSelection, + }, + }, + likedBy: userId + ? { + where: { userId }, + select: { userId: true }, + } + : false, + _count: _countSelection, + }, + }); +} + +async function update( + id: number, + data: Prisma.ArticleUpdateInput +): Promise< + Prisma.ArticleGetPayload<{ + include: { + writer: typeof _writerSelection; + _count: typeof _countSelection; + }; + }> +> { + return prisma.article.update({ + where: { id }, + data, + include: { + writer: _writerSelection, + _count: _countSelection, + }, + }); +} + +async function deleteById(id: number): Promise
{ + return prisma.article.delete({ + where: { id }, + }); +} + +async function findLike( + articleId: number, + userId: number +): Promise { + return prisma.articleLike.findUnique({ + where: { + userId_articleId: { + userId, + articleId, + }, + }, + }); +} + +async function createLike( + articleId: number, + userId: number +): Promise { + return prisma.articleLike.create({ + data: { + articleId, + userId, + }, + }); +} + +async function deleteLike( + articleId: number, + userId: number +): Promise { + return prisma.articleLike.delete({ + where: { + userId_articleId: { + userId, + articleId, + }, + }, + }); +} + +export default { + create, + findAll, + findById, + update, + deleteById, + findLike, + createLike, + deleteLike, +}; diff --git a/src/repositories/commentRepository.ts b/src/repositories/commentRepository.ts new file mode 100644 index 00000000..e3db53ad --- /dev/null +++ b/src/repositories/commentRepository.ts @@ -0,0 +1,113 @@ +import prisma from "../config/prisma"; +import { Prisma, Comment as PrismaComment } from "@prisma/client"; + +const _writerSelection = { + select: { + id: true, + nickname: true, + image: true, + }, +} as const; + +type CommentWithWriter = Prisma.CommentGetPayload<{ + include: { writer: typeof _writerSelection }; +}>; + +interface CommentRepositoryPaginationOptions { + skip?: string | number; + take?: string | number; +} + +async function create( + data: Prisma.CommentCreateInput +): Promise { + return prisma.comment.create({ + data, + include: { + writer: _writerSelection, + }, + }); +} + +async function findByProductId( + productId: number, + options: CommentRepositoryPaginationOptions = {} +): Promise { + const { skip: rawSkip, take: rawTake = 10 } = options; + + const skip = + rawSkip !== undefined ? parseInt(String(rawSkip), 10) : undefined; + const take = parseInt(String(rawTake), 10); + + return prisma.comment.findMany({ + where: { productId }, + include: { + writer: _writerSelection, + }, + orderBy: { + createdAt: "desc", + }, + skip, + take, + }); +} + +async function findByArticleId( + articleId: number, + options: CommentRepositoryPaginationOptions = {} +): Promise { + const { skip: rawSkip, take: rawTake = 10 } = options; + + const skip = + rawSkip !== undefined ? parseInt(String(rawSkip), 10) : undefined; + const take = parseInt(String(rawTake), 10); + + return prisma.comment.findMany({ + where: { articleId }, + include: { + writer: _writerSelection, + }, + orderBy: { + createdAt: "desc", + }, + skip, + take, + }); +} + +async function findById(id: number): Promise { + return prisma.comment.findUnique({ + where: { id }, + include: { + writer: _writerSelection, + }, + }); +} + +async function update( + id: number, + data: Prisma.CommentUpdateInput +): Promise { + return prisma.comment.update({ + where: { id }, + data, + include: { + writer: _writerSelection, + }, + }); +} + +async function deleteById(id: number): Promise { + return prisma.comment.delete({ + where: { id }, + }); +} + +export default { + create, + findByProductId, + findByArticleId, + findById, + update, + deleteById, +}; diff --git a/src/repositories/productRepository.ts b/src/repositories/productRepository.ts new file mode 100644 index 00000000..ffe338b0 --- /dev/null +++ b/src/repositories/productRepository.ts @@ -0,0 +1,193 @@ +import prisma from "../config/prisma"; +import { Prisma, Product, ProductLike } from "@prisma/client"; + +// Helper for consistent selections +const _productOwnerSelection = { + select: { id: true, nickname: true }, +} as const; +const _productCountSelection = { select: { likedBy: true } } as const; + +interface ProductFindAllOptions { + skip?: string | number; + take?: string | number; + where?: Prisma.ProductWhereInput; + orderBy?: + | Prisma.ProductOrderByWithRelationInput + | Prisma.ProductOrderByWithRelationInput[]; +} + +interface ProductCreateInputArgs { + name: string; + description: string; // Changed from: string | null | undefined + price: number | string; + images?: string[]; + tags?: string[]; +} + +interface ProductUpdateDataArgs { + name?: string; + description?: string; // Changed from: string | null | undefined + price?: string | number; + images?: Prisma.ProductUpdateimagesInput | string[]; + tags?: Prisma.ProductUpdatetagsInput | string[]; + [key: string]: any; +} + +// Type for objects with core relations included (owner, _count) +// Used for create and update results +type ProductWithCoreInclusions = Prisma.ProductGetPayload<{ + include: { + owner: typeof _productOwnerSelection; + _count: typeof _productCountSelection; + }; +}>; + +// Type for objects that might also include conditional 'likedBy' info +// Used for findById and findAll results +type ProductWithOptionalLikes = ProductWithCoreInclusions & { + likedBy?: { userId: number }[]; +}; + +async function findById( + id: string, + userId?: number +): Promise { + const product = await prisma.product.findUnique({ + where: { + id: parseInt(id, 10), + }, + include: { + owner: _productOwnerSelection, + likedBy: userId + ? { + where: { userId }, + select: { userId: true }, + } + : false, + _count: _productCountSelection, + }, + }); + // Prisma's return type with conditional includes can be tricky. + // If likedBy is false, the property won't exist. + // Casting might be necessary if GetPayload cannot fully represent this. + return product as ProductWithOptionalLikes | null; +} + +async function create( + productData: ProductCreateInputArgs, + ownerId: number +): Promise { + const { name, description, price, images, tags } = productData; + return prisma.product.create({ + data: { + name, + description, // Now 'string', matching non-nullable schema expectation + price: typeof price === "string" ? parseInt(price, 10) : price, + images: images || [], + owner: { + connect: { id: ownerId }, + }, + tags: tags || [], + }, + include: { + owner: _productOwnerSelection, + _count: _productCountSelection, + }, + }); +} + +async function findAll( + options: ProductFindAllOptions = {}, + userId?: number +): Promise<{ list: ProductWithOptionalLikes[]; totalCount: number }> { + const { skip, take = 10, where, orderBy = { createdAt: "desc" } } = options; + + const products = await prisma.product.findMany({ + where, + include: { + owner: _productOwnerSelection, + likedBy: userId ? { where: { userId }, select: { userId: true } } : false, + _count: _productCountSelection, + }, + orderBy, + skip: skip !== undefined ? parseInt(String(skip), 10) : 0, + take: parseInt(String(take), 10), + }); + + const totalCount = await prisma.product.count({ where }); + + return { list: products as ProductWithOptionalLikes[], totalCount }; +} + +async function update( + id: string, + data: ProductUpdateDataArgs +): Promise { + const { name, description, price, images, tags, ...otherData } = data; + const finalUpdatePayload: Prisma.ProductUpdateInput = { ...otherData }; + + if (name !== undefined) finalUpdatePayload.name = name; + // description is now 'string | undefined'. If undefined, it's skipped. + // If string, it's assigned. This is compatible with a non-nullable schema field. + if (description !== undefined) finalUpdatePayload.description = description; + if (price !== undefined && price !== null) { + // price can be 0 + finalUpdatePayload.price = + typeof price === "string" ? parseInt(price, 10) : price; + } + if (images !== undefined) finalUpdatePayload.images = images; + if (tags !== undefined) finalUpdatePayload.tags = tags; + + return prisma.product.update({ + where: { id: parseInt(id, 10) }, + data: finalUpdatePayload, + include: { + owner: _productOwnerSelection, + _count: _productCountSelection, + }, + }); +} + +async function deleteById(id: string): Promise { + return prisma.product.delete({ + where: { id: parseInt(id, 10) }, + }); +} + +async function findLike( + productId: string, + userId: number +): Promise { + return prisma.productLike.findUnique({ + where: { userId_productId: { userId, productId: parseInt(productId, 10) } }, + }); +} + +async function createLike( + productId: string, + userId: number +): Promise { + return prisma.productLike.create({ + data: { userId, productId: parseInt(productId, 10) }, + }); +} + +async function deleteLike( + productId: string, + userId: number +): Promise { + return prisma.productLike.delete({ + where: { userId_productId: { userId, productId: parseInt(productId, 10) } }, + }); +} + +export default { + findById, + create, + findAll, + update, + deleteById, + findLike, + createLike, + deleteLike, +}; diff --git a/src/repositories/userRepository.ts b/src/repositories/userRepository.ts new file mode 100644 index 00000000..a41a1514 --- /dev/null +++ b/src/repositories/userRepository.ts @@ -0,0 +1,156 @@ +import prisma from "../config/prisma"; +import { Prisma, User, ProductLike, ArticleLike } from "@prisma/client"; + +type SafeUser = Omit; + +interface PaginationOptions { + skip?: number; + take?: number; +} + +type FavoriteProductItem = ProductLike & { + product: { + id: number; + name: string; + price: number; + images: string[]; + }; +}; + +type FavoriteArticleItem = ArticleLike & { + article: { + id: number; + title: string; + image: string | null; + }; +}; + +async function create(data: Prisma.UserCreateInput): Promise { + return prisma.user.create({ + data, + select: { + id: true, + email: true, + nickname: true, + image: true, + createdAt: true, + updatedAt: true, + }, + }); +} + +async function findById(id: number): Promise { + return prisma.user.findUnique({ + where: { id }, + select: { + id: true, + email: true, + nickname: true, + image: true, + createdAt: true, + updatedAt: true, + }, + }); +} + +async function findByIdWithPassword(id: number): Promise { + return prisma.user.findUnique({ + where: { id }, + }); +} + +async function findByEmail(email: string): Promise { + return prisma.user.findUnique({ + where: { email }, + }); +} + +async function findByNickname( + nickname: string +): Promise | null> { + return prisma.user.findUnique({ + where: { nickname }, + select: { + id: true, + nickname: true, + }, + }); +} + +async function update( + id: number, + data: Prisma.UserUpdateInput +): Promise { + return prisma.user.update({ + where: { id }, + data, + select: { + id: true, + email: true, + nickname: true, + image: true, + createdAt: true, + updatedAt: true, + }, + }); +} + +async function findUserFavoriteProducts( + userId: number, + options: PaginationOptions = {} +): Promise { + const { skip, take } = options; + return prisma.productLike.findMany({ + where: { userId }, + include: { + product: { + select: { + id: true, + name: true, + price: true, + images: true, + }, + }, + }, + skip, + take, + orderBy: { + createdAt: "desc", + }, + }); +} + +async function findUserFavoriteArticles( + userId: number, + options: PaginationOptions = {} +): Promise { + const { skip, take } = options; + return prisma.articleLike.findMany({ + where: { userId }, + include: { + article: { + select: { + id: true, + title: true, + image: true, + }, + }, + }, + skip, + take, + orderBy: { + createdAt: "desc", + }, + }); +} + +export default { + create, + findById, + findByIdWithPassword, + findByEmail, + findByNickname, + update, + findUserFavoriteProducts, + findUserFavoriteArticles, +}; diff --git a/src/services/articleService.ts b/src/services/articleService.ts new file mode 100644 index 00000000..abca628b --- /dev/null +++ b/src/services/articleService.ts @@ -0,0 +1,339 @@ +import articleRepository from "../repositories/articleRepository"; +import userRepository from "../repositories/userRepository"; +import { CustomError } from "../utils/CustomError"; +import { Prisma } from "@prisma/client"; + +type ArticleWithWriterAndCounts = Prisma.ArticleGetPayload<{ + include: { + writer: { select: { id: true; nickname: true; image: true } }; + _count: { select: { likedBy: true; comments: true } }; + }; +}>; + +function formatArticleForList(article: ArticleWithWriterAndCounts | null): { + id: number; + title: string; + content: string; + image: string | null; + writer: { id: number; nickname: string }; + likeCount: number; + commentCount: number; + createdAt: string; + updatedAt: string; +} | null { + if (!article) return null; + return { + id: article.id, + title: article.title, + content: article.content, + image: article.image, + writer: { + id: article.writer.id, + nickname: article.writer.nickname, + }, + likeCount: article._count?.likedBy || 0, + commentCount: article._count?.comments || 0, + createdAt: article.createdAt.toISOString(), + updatedAt: article.updatedAt.toISOString(), + }; +} + +type ArticleWithWriterCommentsLikes = Prisma.ArticleGetPayload<{ + include: { + writer: { select: { id: true; nickname: true; image: true } }; + comments: { + orderBy: { createdAt: "desc" }; + take: 5; + include: { + writer: { select: { id: true; nickname: true; image: true } }; + }; + }; + likedBy: { select: { userId: true } } | false; + _count: { select: { likedBy: true; comments: true } }; + }; +}>; + +type FormattedComment = { + id: number; + content: string; + createdAt: Date; + updatedAt: Date; + writer: { + id: number; + nickname: string; + image: string | null; + }; +}; + +function formatArticleForDetail( + article: ArticleWithWriterCommentsLikes | null, + userId?: number +): { + id: number; + title: string; + content: string; + image: string | null; + writer: { id: number; nickname: string; image: string | null }; + comments: FormattedComment[]; + likeCount: number; + commentCount: number; + isLiked: boolean; + createdAt: string; + updatedAt: string; +} | null { + if (!article) return null; + return { + id: article.id, + title: article.title, + content: article.content, + image: article.image, + writer: { + id: article.writer.id, + nickname: article.writer.nickname, + image: article.writer.image, + }, + comments: + article.comments?.map((comment: any) => ({ + id: comment.id, + content: comment.content, + createdAt: comment.createdAt, + updatedAt: comment.updatedAt, + writer: { + id: comment.writer.id, + nickname: comment.writer.nickname, + image: comment.writer.image, + }, + })) || [], + likeCount: article._count?.likedBy || 0, + commentCount: article._count?.comments || 0, + isLiked: + userId && Array.isArray(article.likedBy) + ? article.likedBy.some( + (like: { userId: number }) => like.userId === userId + ) + : false, + createdAt: article.createdAt.toISOString(), + updatedAt: article.updatedAt.toISOString(), + }; +} + +interface ArticleCreateData { + title: string; + content: string; + image?: string; + writerId: number; +} + +async function createArticle( + articleData: ArticleCreateData +): Promise | null> { + const { title, content, writerId, image } = articleData; + + if (!title || title.trim() === "") { + throw new CustomError(422, "게시글 제목을 입력해주세요."); + } + if (!content || content.trim() === "") { + throw new CustomError(422, "게시글 내용을 입력해주세요."); + } + if (!writerId) { + throw new CustomError(400, "작성자 정보가 누락되었습니다."); + } + + const writer = await userRepository.findById(writerId); + if (!writer) { + throw new CustomError(404, "게시글을 작성할 사용자를 찾을 수 없습니다."); + } + + const createdArticle = await articleRepository.create({ + title, + content, + image, + writer: { + connect: { id: writerId }, + }, + }); + + const detailedArticle = await articleRepository.findById( + createdArticle.id, + writerId + ); + return formatArticleForDetail(detailedArticle, writerId); +} + +interface GetArticlesQueryParams { + page?: string | number; + pageSize?: string | number; + orderBy?: "recent" | "like"; + keyword?: string; + [key: string]: any; +} + +async function getArticles( + queryParams: GetArticlesQueryParams, + userId?: number +): Promise<{ + totalCount: number; + list: (ReturnType | null)[]; +}> { + const { + page = "1", + pageSize = "10", + orderBy = "recent", + keyword, + } = queryParams; + + const skip = + (parseInt(String(page), 10) - 1) * parseInt(String(pageSize), 10); + const take = parseInt(String(pageSize), 10); + + const where: Prisma.ArticleWhereInput = {}; + if (keyword) { + where.OR = [ + { title: { contains: keyword, mode: "insensitive" } }, + { content: { contains: keyword, mode: "insensitive" } }, + ]; + } + + let prismaOrderBy: + | Prisma.ArticleOrderByWithRelationInput + | Prisma.ArticleOrderByWithRelationInput[] = {}; + if (orderBy === "like") { + prismaOrderBy = { likedBy: { _count: "desc" } }; + } else { + prismaOrderBy = { createdAt: "desc" }; + } + + const { list, totalCount } = await articleRepository.findAll( + { skip, take, where, orderBy: prismaOrderBy }, + userId + ); + + return { + totalCount, + list: list.map((article) => formatArticleForList(article)), + }; +} + +async function getArticleById( + articleId: number, + userId?: number +): Promise | null> { + const article = await articleRepository.findById(articleId, userId); + if (!article) { + throw new CustomError(404, "게시글을 찾을 수 없습니다."); + } + return formatArticleForDetail(article, userId); +} + +interface ArticleUpdateData { + title?: string; + content?: string; + image?: string | null; +} + +async function updateArticle( + articleId: number, + updateData: ArticleUpdateData, + writerId: number +): Promise | null> { + const article = await articleRepository.findById(articleId, writerId); + if (!article) { + throw new CustomError(404, "수정할 게시글을 찾을 수 없습니다."); + } + + if (article.writer.id !== writerId) { + throw new CustomError(403, "게시글을 수정할 권한이 없습니다."); + } + + const { title, content, image } = updateData; + + const dataToUpdate: Prisma.ArticleUpdateInput = {}; + + if (title !== undefined) { + const trimmedTitle = title.trim(); + if (!trimmedTitle) throw new CustomError(422, "제목은 공백일 수 없습니다."); + dataToUpdate.title = trimmedTitle; + } + + if (content !== undefined) { + const trimmedContent = content.trim(); + if (!trimmedContent) + throw new CustomError(422, "내용은 공백일 수 없습니다."); + dataToUpdate.content = trimmedContent; + } + + if (image !== undefined) { + dataToUpdate.image = image; + } + + if (Object.keys(dataToUpdate).length === 0) { + throw new CustomError(422, "수정할 내용이 없습니다."); + } + + await articleRepository.update(articleId, dataToUpdate); + const updatedArticle = await articleRepository.findById(articleId, writerId); + return formatArticleForDetail(updatedArticle, writerId); +} + +async function deleteArticle( + articleId: number, + writerId: number +): Promise { + const article = await articleRepository.findById(articleId, writerId); + if (!article) { + throw new CustomError(404, "삭제할 게시글을 찾을 수 없습니다."); + } + + if (article.writer.id !== writerId) { + throw new CustomError(403, "게시글을 삭제할 권한이 없습니다."); + } + await articleRepository.deleteById(articleId); +} + +async function likeArticle( + articleId: number, + userId: number +): Promise | null> { + const article = await articleRepository.findById(articleId, userId); + if (!article) { + throw new CustomError(404, "게시글을 찾을 수 없습니다."); + } + const existingLike = await articleRepository.findLike(articleId, userId); + if (existingLike) { + return formatArticleForDetail(article, userId); + } + await articleRepository.createLike(articleId, userId); + const updatedArticle = await articleRepository.findById(articleId, userId); + if (!updatedArticle) + throw new CustomError(404, "업데이트된 게시글을 찾을 수 없습니다."); + return formatArticleForDetail(updatedArticle, userId); +} + +async function unlikeArticle( + articleId: number, + userId: number +): Promise | null> { + const article = await articleRepository.findById(articleId, userId); + if (!article) { + throw new CustomError(404, "게시글을 찾을 수 없습니다."); + } + const existingLike = await articleRepository.findLike(articleId, userId); + if (!existingLike) { + return formatArticleForDetail(article, userId); + } + await articleRepository.deleteLike(articleId, userId); + const updatedArticle = await articleRepository.findById(articleId, userId); + if (!updatedArticle) + throw new CustomError(404, "업데이트된 게시글을 찾을 수 없습니다."); + return formatArticleForDetail(updatedArticle, userId); +} + +export default { + createArticle, + getArticles, + getArticleById, + updateArticle, + deleteArticle, + likeArticle, + unlikeArticle, +}; diff --git a/src/services/authService.ts b/src/services/authService.ts new file mode 100644 index 00000000..23cdd5ba --- /dev/null +++ b/src/services/authService.ts @@ -0,0 +1,121 @@ +import userRepository from "../repositories/userRepository"; +import bcrypt from "bcrypt"; +import jwt, { SignOptions } from "jsonwebtoken"; +import { User } from "@prisma/client"; +import { CustomError } from "../utils/CustomError"; + +const JWT_SECRET_ENV = process.env.JWT_SECRET; +if (!JWT_SECRET_ENV) { + throw new Error( + "JWT_SECRET is not defined in environment variables. Please set it." + ); +} +const JWT_SECRET: string = JWT_SECRET_ENV; + +const ACCESS_TOKEN_EXPIRES_IN = process.env.ACCESS_TOKEN_EXPIRES_IN || "15m"; +const REFRESH_TOKEN_EXPIRES_IN = process.env.REFRESH_TOKEN_EXPIRES_IN || "7d"; + +type SignUpUserData = Pick & { + password: User["password"]; + passwordConfirmation: string; +}; + +async function signUpUser({ + nickname, + email, + password, + passwordConfirmation, +}: SignUpUserData): Promise> { + if (password !== passwordConfirmation) { + throw new CustomError(422, "비밀번호가 일치하지 않습니다."); + } + const existingUserByEmail = await userRepository.findByEmail(email); + if (existingUserByEmail) { + throw new CustomError(409, "이미 사용중인 이메일입니다."); + } + + const existingUserByNickname = await userRepository.findByNickname(nickname); + if (existingUserByNickname) { + throw new CustomError(409, "이미 사용중인 닉네임입니다."); + } + + const hashedPassword = await bcrypt.hash(password, 10); + + const newUser: Omit = await userRepository.create({ + nickname, + email, + password: hashedPassword, + }); + + return newUser; +} + +type SignInResponse = { + accessToken: string; + refreshToken: string; + user: Omit; +}; + +async function signInUser( + email: User["email"], + passwordInput: User["password"] +): Promise { + const user: User | null = await userRepository.findByEmail(email); + if (!user) { + throw new CustomError(401, "이메일 또는 비밀번호가 일치하지 않습니다."); + } + + const isPasswordValid = await bcrypt.compare(passwordInput, user.password); + if (!isPasswordValid) { + throw new CustomError(401, "이메일 또는 비밀번호가 일치하지 않습니다."); + } + + const payload = { + userId: user.id, + email: user.email, + nickname: user.nickname, + }; + + const accessTokenOptions: SignOptions = { + expiresIn: ACCESS_TOKEN_EXPIRES_IN as SignOptions["expiresIn"], + }; + const refreshTokenOptions: SignOptions = { + expiresIn: REFRESH_TOKEN_EXPIRES_IN as SignOptions["expiresIn"], + }; + + const accessToken = jwt.sign(payload, JWT_SECRET, accessTokenOptions); + const refreshToken = jwt.sign(payload, JWT_SECRET, refreshTokenOptions); + + const { password: _, ...userWithoutPassword } = user; + + return { accessToken, refreshToken, user: userWithoutPassword }; +} + +export type TokenUserPayload = Pick; + +function generateNewAccessToken(user: TokenUserPayload): string { + if (!user || !user.id || !user.email || !user.nickname) { + throw new CustomError( + 400, + "새로운 액세스 토큰을 생성하기 위한 사용자 정보가 유효하지 않습니다." + ); + } + + const payload = { + userId: user.id, + email: user.email, + nickname: user.nickname, + }; + + const signOptions: SignOptions = { + expiresIn: ACCESS_TOKEN_EXPIRES_IN as SignOptions["expiresIn"], + }; + + return jwt.sign(payload, JWT_SECRET, signOptions); +} + +export default { + signUpUser, + signInUser, + generateNewAccessToken, +}; diff --git a/src/services/commentService.ts b/src/services/commentService.ts new file mode 100644 index 00000000..982a1141 --- /dev/null +++ b/src/services/commentService.ts @@ -0,0 +1,147 @@ +import commentRepository from "../repositories/commentRepository"; +import productRepository from "../repositories/productRepository"; +import articleRepository from "../repositories/articleRepository"; +import { CustomError } from "../utils/CustomError"; +import { Comment } from "@prisma/client"; + +interface CommentQueryParams { + page?: string | number; + pageSize?: string | number; +} + +async function createProductComment( + productId: string, + writerId: number, + content: string +): Promise { + if (!content || content.trim() === "") { + throw new CustomError(422, "댓글 내용을 입력해주세요."); + } + + const product = await productRepository.findById(productId); + if (!product) { + throw new CustomError(404, "댓글을 작성할 상품을 찾을 수 없습니다."); + } + + return commentRepository.create({ + content, + writer: { connect: { id: writerId } }, + product: { connect: { id: parseInt(productId, 10) } }, + }); +} + +async function getProductComments( + productId: string, + queryParams: CommentQueryParams +): Promise<{ list: Comment[]; totalCount: number }> { + const product = await productRepository.findById(productId); + if (!product) { + throw new CustomError(404, "상품을 찾을 수 없습니다."); + } + + const page = queryParams.page ? parseInt(String(queryParams.page), 10) : 1; + const pageSize = queryParams.pageSize + ? parseInt(String(queryParams.pageSize), 10) + : 10; + const skip = (page - 1) * pageSize; + const take = pageSize; + + const commentList = await commentRepository.findByProductId( + parseInt(productId, 10), + { skip, take } + ); + // NOTE: If commentRepository.findByProductId performs pagination, + // commentList.length will be the count for the current page, not the total. + return { list: commentList, totalCount: commentList.length }; +} + +async function createArticleComment( + articleId: number, + writerId: number, + content: string +): Promise { + if (!content || content.trim() === "") { + throw new CustomError(422, "댓글 내용을 입력해주세요."); + } + + const article = await articleRepository.findById(articleId); + if (!article) { + throw new CustomError(404, "댓글을 작성할 게시글을 찾을 수 없습니다."); + } + + return commentRepository.create({ + content, + writer: { connect: { id: writerId } }, + article: { connect: { id: articleId } }, + }); +} + +async function getArticleComments( + articleId: number, + queryParams: CommentQueryParams +): Promise<{ list: Comment[]; totalCount: number }> { + const article = await articleRepository.findById(articleId); + if (!article) { + throw new CustomError(404, "게시글을 찾을 수 없습니다."); + } + + const page = queryParams.page ? parseInt(String(queryParams.page), 10) : 1; + const pageSize = queryParams.pageSize + ? parseInt(String(queryParams.pageSize), 10) + : 10; + const skip = (page - 1) * pageSize; + const take = pageSize; + + const commentList = await commentRepository.findByArticleId(articleId, { + skip, + take, + }); + + return { list: commentList, totalCount: commentList.length }; +} + +async function updateComment( + commentId: number, + content: string, + writerId: number +): Promise { + if (!content || content.trim() === "") { + throw new CustomError(422, "댓글 내용을 입력해주세요."); + } + + const comment = await commentRepository.findById(commentId); + if (!comment) { + throw new CustomError(404, "수정할 댓글을 찾을 수 없습니다."); + } + + if (comment.writerId !== writerId) { + throw new CustomError(403, "댓글을 수정할 권한이 없습니다."); + } + + return commentRepository.update(commentId, { content }); +} + +async function deleteComment( + commentId: number, + writerId: number +): Promise { + const comment = await commentRepository.findById(commentId); + if (!comment) { + throw new CustomError(404, "삭제할 댓글을 찾을 수 없습니다."); + } + + if (comment.writerId !== writerId) { + throw new CustomError(403, "댓글을 삭제할 권한이 없습니다."); + } + + await commentRepository.deleteById(commentId); +} + +export default { + createProductComment, + getProductComments, + createArticleComment, + getArticleComments, + updateComment, + deleteComment, +}; diff --git a/src/services/productService.ts b/src/services/productService.ts new file mode 100644 index 00000000..f7cffa60 --- /dev/null +++ b/src/services/productService.ts @@ -0,0 +1,246 @@ +import productRepository from "../repositories/productRepository"; +import { CustomError } from "../utils/CustomError"; +import { Prisma, Product } from "@prisma/client"; + +type ProductWithCoreInclusions = Prisma.ProductGetPayload<{ + include: { + owner: { select: { id: true; nickname: true } }; + _count: { select: { likedBy: true } }; + }; +}>; + +type ProductWithOptionalLikes = ProductWithCoreInclusions & { + likedBy?: Array<{ userId: number }>; +}; + +function formatProductResponse( + product: ProductWithOptionalLikes | null, + userId?: number +) { + if (!product) return null; + return { + id: product.id, + name: product.name, + description: product.description, + price: product.price, + images: product.images || [], + tags: product.tags || [], + ownerId: product.owner.id, + ownerNickname: product.owner.nickname, + favoriteCount: product._count?.likedBy || 0, + createdAt: product.createdAt, + isFavorite: + userId && Array.isArray(product.likedBy) + ? product.likedBy.some( + (like: { userId: number }) => like.userId === userId + ) + : false, + }; +} + +async function getProductById(productId: string, userId?: number) { + const product = await productRepository.findById(productId, userId); + if (!product) { + throw new CustomError(404, "상품을 찾을 수 없습니다."); + } + return formatProductResponse(product, userId); +} + +interface CreateProductData { + name: string; + price: number | string; + description: string; + tags?: string[]; + images?: string[]; +} + +async function createProduct(productData: CreateProductData, ownerId: number) { + if (!productData.name || productData.price === undefined) { + throw new CustomError(422, "상품 이름과 가격은 필수입니다."); + } + if ( + (typeof productData.price !== "number" && + typeof productData.price !== "string") || + (typeof productData.price === "string" && + isNaN(parseInt(productData.price, 10))) || + (typeof productData.price === "number" && isNaN(productData.price)) || + Number(productData.price) < 0 + ) { + throw new CustomError(422, "가격은 0 이상의 숫자여야 합니다."); + } + if (productData.tags && !Array.isArray(productData.tags)) { + throw new CustomError(422, "태그는 배열이어야 합니다."); + } + if (productData.images && !Array.isArray(productData.images)) { + throw new CustomError(422, "이미지는 URL 배열이어야 합니다."); + } + + const createdProduct = await productRepository.create( + { + name: productData.name, + price: productData.price, + description: productData.description, + tags: productData.tags, + images: productData.images, + }, + ownerId + ); + return formatProductResponse(createdProduct, ownerId); +} + +interface GetProductsQueryParams { + page?: string | number; + pageSize?: string | number; + orderBy?: "recent" | "favorite"; + keyword?: string; + [key: string]: any; +} + +async function getProducts( + queryParams: GetProductsQueryParams, + userId?: number +) { + const { page = 1, pageSize = 10, orderBy = "recent", keyword } = queryParams; + + const skip = + (parseInt(String(page), 10) - 1) * parseInt(String(pageSize), 10); + const take = parseInt(String(pageSize), 10); + + const where: Prisma.ProductWhereInput = {}; + if (keyword) { + where.OR = [ + { name: { contains: keyword, mode: "insensitive" } }, + { description: { contains: keyword, mode: "insensitive" } }, + { tags: { has: keyword } }, + ]; + } + + let prismaOrderBy: + | Prisma.ProductOrderByWithRelationInput + | Prisma.ProductOrderByWithRelationInput[] = {}; + if (orderBy === "favorite") { + prismaOrderBy = { likedBy: { _count: "desc" } }; + } else { + prismaOrderBy = { createdAt: "desc" }; + } + + const { list, totalCount } = await productRepository.findAll( + { skip, take, where, orderBy: prismaOrderBy }, + userId + ); + + return { + totalCount, + list: list.map((product) => formatProductResponse(product, userId)), + }; +} + +interface UpdateProductData { + name?: string; + price?: string | number; + description?: string; + tags?: string[]; + images?: string[]; +} + +async function updateProduct( + productId: string, + updateData: UpdateProductData, + requesterId: number +) { + const productToUpdate = await productRepository.findById( + productId, + requesterId + ); + if (!productToUpdate) { + throw new CustomError(404, "수정할 상품을 찾을 수 없습니다."); + } + + if (productToUpdate.owner.id !== requesterId) { + throw new CustomError(403, "상품을 수정할 권한이 없습니다."); + } + + if (Object.keys(updateData).length === 0) { + throw new CustomError(400, "수정할 내용이 없습니다."); + } + if ( + updateData.price !== undefined && + ((typeof updateData.price !== "number" && + typeof updateData.price !== "string") || + (typeof updateData.price === "string" && + isNaN(parseInt(updateData.price, 10))) || + (typeof updateData.price === "number" && isNaN(updateData.price)) || + Number(updateData.price) < 0) + ) { + throw new CustomError(422, "가격은 0 이상의 숫자여야 합니다."); + } + if (updateData.tags && !Array.isArray(updateData.tags)) { + throw new CustomError(422, "태그는 배열이어야 합니다."); + } + if (updateData.images && !Array.isArray(updateData.images)) { + throw new CustomError(422, "이미지는 URL 배열이어야 합니다."); + } + + const updatedProductRaw = await productRepository.update( + productId, + updateData + ); + const finalProduct = await productRepository.findById(productId, requesterId); + return formatProductResponse(finalProduct, requesterId); +} + +async function deleteProduct(productId: string, requesterId: number) { + const product = await productRepository.findById(productId); + if (!product) { + throw new CustomError(404, "삭제할 상품을 찾을 수 없습니다."); + } + + if (product.owner.id !== requesterId) { + throw new CustomError(403, "상품을 삭제할 권한이 없습니다."); + } + + await productRepository.deleteById(productId); + return { id: parseInt(productId, 10) }; +} + +async function addFavorite(productId: string, userId: number) { + const product = await productRepository.findById(productId, userId); + if (!product) { + throw new CustomError(404, "즐겨찾기할 상품을 찾을 수 없습니다."); + } + + const existingLike = await productRepository.findLike(productId, userId); + if (existingLike) { + return formatProductResponse(product, userId); + } + + await productRepository.createLike(productId, userId); + const updatedProduct = await productRepository.findById(productId, userId); + return formatProductResponse(updatedProduct, userId); +} + +async function removeFavorite(productId: string, userId: number) { + const product = await productRepository.findById(productId, userId); + if (!product) { + throw new CustomError(404, "즐겨찾기에서 삭제할 상품을 찾을 수 없습니다."); + } + + const existingLike = await productRepository.findLike(productId, userId); + if (!existingLike) { + return formatProductResponse(product, userId); + } + + await productRepository.deleteLike(productId, userId); + const updatedProduct = await productRepository.findById(productId, userId); + return formatProductResponse(updatedProduct, userId); +} + +export default { + getProductById, + createProduct, + getProducts, + updateProduct, + deleteProduct, + addFavorite, + removeFavorite, +}; diff --git a/src/services/userService.ts b/src/services/userService.ts new file mode 100644 index 00000000..5ef3504d --- /dev/null +++ b/src/services/userService.ts @@ -0,0 +1,145 @@ +import userRepository from "../repositories/userRepository"; +import productRepository from "../repositories/productRepository"; +import articleRepository from "../repositories/articleRepository"; +import bcrypt from "bcrypt"; +import { User, Product, Article, Prisma } from "@prisma/client"; +import { CustomError } from "../utils/CustomError"; + +type SafeUser = Omit; + +async function getUserProfile(userId: number): Promise { + const user = await userRepository.findById(userId); + if (!user) { + throw new CustomError(404, "사용자를 찾을 수 없습니다."); + } + return user; +} + +type UserProfileUpdateData = Partial>; + +async function updateUserProfile( + userId: number, + updateData: UserProfileUpdateData +): Promise { + const allowedUpdates: (keyof UserProfileUpdateData)[] = ["nickname", "image"]; + const filteredUpdateData: UserProfileUpdateData = {}; + + for (const key of allowedUpdates) { + if (updateData[key] !== undefined) { + const value = updateData[key]; + + const valueToAssign = value === null ? undefined : value; + filteredUpdateData[key] = valueToAssign; + } + } + + if (Object.keys(filteredUpdateData).length === 0) { + throw new CustomError(400, "수정할 유효한 정보가 없습니다."); + } + + if ( + typeof filteredUpdateData.nickname === "string" && + filteredUpdateData.nickname + ) { + const existingUserByNickname = await userRepository.findByNickname( + filteredUpdateData.nickname + ); + if (existingUserByNickname && existingUserByNickname.id !== userId) { + throw new CustomError(409, "이미 사용중인 닉네임입니다."); + } + } + + const updatedUser = await userRepository.update( + userId, + filteredUpdateData as Prisma.UserUpdateInput + ); + return updatedUser; +} + +async function updateUserPassword( + userId: number, + currentPassword: string, + newPassword: string +): Promise { + const user = await userRepository.findByIdWithPassword(userId); + if (!user) { + throw new CustomError(404, "사용자를 찾을 수 없습니다."); + } + + const isPasswordValid = await bcrypt.compare(currentPassword, user.password); + if (!isPasswordValid) { + throw new CustomError(401, "현재 비밀번호가 일치하지 않습니다."); + } + + if (newPassword.length < 8) { + throw new CustomError(422, "새 비밀번호는 최소 8자 이상이어야 합니다."); + } + if (currentPassword === newPassword) { + throw new CustomError(422, "새 비밀번호는 현재 비밀번호와 달라야 합니다."); + } + + const hashedNewPassword = await bcrypt.hash(newPassword, 10); + await userRepository.update(userId, { + password: hashedNewPassword, + } as Prisma.UserUpdateInput); +} + +interface QueryParams { + page?: string | number; + pageSize?: string | number; + orderBy?: string; + keyword?: string; + [key: string]: any; +} + +async function getUserProducts( + userId: number, + queryParams: QueryParams +): Promise { + const prismaQueryParams: Prisma.ProductFindManyArgs = { + where: { ownerId: userId }, + }; + if (queryParams.page && queryParams.pageSize) { + prismaQueryParams.skip = + (Number(queryParams.page) - 1) * Number(queryParams.pageSize); + prismaQueryParams.take = Number(queryParams.pageSize); + } + return productRepository.findAll(prismaQueryParams, userId); +} + +async function getUserFavorites( + userId: number, + queryParams: QueryParams +): Promise<{ products: any[]; articles: any[] }> { + const options = { + skip: + queryParams.page && queryParams.pageSize + ? (Number(queryParams.page) - 1) * Number(queryParams.pageSize) + : undefined, + take: queryParams.pageSize ? Number(queryParams.pageSize) : undefined, + }; + + const favoriteProducts = await userRepository.findUserFavoriteProducts( + userId, + options + ); + const favoriteArticles = await userRepository.findUserFavoriteArticles( + userId, + options + ); + return { products: favoriteProducts, articles: favoriteArticles }; +} + +async function getUserById(id: number): Promise { + const user = await userRepository.findById(id); + return user; +} + +export default { + getUserProfile, + updateUserProfile, + updateUserPassword, + getUserProducts, + getUserFavorites, + getUserById, +}; diff --git a/src/utils/CustomError.ts b/src/utils/CustomError.ts new file mode 100644 index 00000000..ef0e0103 --- /dev/null +++ b/src/utils/CustomError.ts @@ -0,0 +1,9 @@ +class CustomError extends Error { + public statusCode: number; + constructor(statusCode: number, message: string) { + super(message); + this.statusCode = statusCode; + this.name = this.constructor.name; + } +} +export { CustomError }; diff --git a/src/utils/asyncHandler.ts b/src/utils/asyncHandler.ts new file mode 100644 index 00000000..0df84996 --- /dev/null +++ b/src/utils/asyncHandler.ts @@ -0,0 +1,13 @@ +import { Request, Response, NextFunction } from "express"; + +type AsyncFunction = ( + req: Request, + res: Response, + next: NextFunction +) => Promise; + +export const asyncHandler = + (fn: AsyncFunction) => + (req: Request, res: Response, next: NextFunction): void => { + Promise.resolve(fn(req, res, next)).catch(next); + }; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..433e6b21 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,115 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016" /* 컴파일될 JavaScript의 버전을 설정합니다. Node.js v22.x는 최신 ECMAScript 기능을 지원하므로 ESNext를 사용합니다. */, + //"lib": [] /* 'target' 설정에 맞는 내장 API 타입 정의를 포함합니다. (예: Promise, Map, Set 등) */, + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "libReplacement": true, /* Enable lib replacement. */ + //"experimentalDecorators": true /* 데코레이터(@) 사용을 허용합니다. (Prisma 등 ORM에서 사용될 수 있음) */, + //"emitDecoratorMetadata": true /* 데코레이터와 함께 사용될 때, 타입 메타데이터를 생성합니다. (주로 리플렉션 기반 라이브러리에서 필요) */, + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + "useDefineForClassFields": true /* 클래스 필드를 ECMAScript 표준에 맞게 처리합니다. (권장) */, + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "CommonJS" /* 생성될 모듈 코드를 지정합니다. package.json의 "type": "module" 설정과 호환되는 ES 모듈 시스템을 사용합니다. */, + "rootDir": "./src" /* TypeScript 소스 파일의 루트 디렉터리를 지정합니다. */, + //"moduleResolution": "Node" /* 모듈 해석 방식을 지정합니다. 'NodeNext'는 Node.js의 ES 모듈 해석 규칙을 따릅니다. */, + //"baseUrl": "./" /* 비상대적 모듈 이름을 해석할 기준 디렉터리를 지정합니다. 'paths' 옵션과 함께 사용됩니다. */, + //"paths": { + // "@/*": ["src/*"] + // } /* 모듈 경로 별칭을 설정합니다. 예: import User from '@/models/User'; */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + //"resolveJsonModule": true /* .json 파일을 모듈처럼 import 할 수 있도록 허용합니다. */, + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + //"allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": false, /* .js 파일과 .d.ts 파일을 모두 생성합니다. (기본값) */ + "sourceMap": true /* 컴파일된 JavaScript 파일에 대한 소스맵(.map) 파일을 생성합니다. (디버깅용) */, + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": false, /* 컴파일 결과물(.js, .map 등)을 생성합니다. (기본값) */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist" /* 컴파일된 모든 파일이 저장될 출력 폴더를 지정합니다. */, + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": false, /* JSDoc 주석에 '@internal'이 있는 선언도 포함하여 출력합니다. (기본값) */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": false, /* 타입 전용이 아닌 import/export 문을 변환하거나 생략하지 않도록 합니다. (기본값) */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* CommonJS 모듈을 ES6 모듈처럼 가져올 수 있도록 추가적인 JavaScript 코드를 생성합니다. ('allowSyntheticDefaultImports'를 암시적으로 활성화) */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* import 문의 파일 이름 대소문자가 실제 파일 시스템의 대소문자와 일치하도록 강제합니다. */, + + /* Type Checking */ + "strict": true /* 모든 엄격한 타입 검사 옵션을 활성화합니다. (권장) */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* 'bind', 'call', 'apply' 메소드의 인자가 원본 함수와 일치하는지 확인합니다. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* 모든 선언 파일(*.d.ts)의 타입 검사를 건너뛰어 빌드 속도를 향상시킵니다. */ + } +}