From d77856f659846525b9dee8513d6df8d03a4d9bf4 Mon Sep 17 00:00:00 2001 From: chlehdwon Date: Mon, 29 May 2023 15:48:02 +0000 Subject: [PATCH 001/261] Add: updateChatHandler --- src/routes/chats.js | 12 ++++++++++++ src/services/chats.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/routes/chats.js b/src/routes/chats.js index 812755f6..56febcfd 100644 --- a/src/routes/chats.js +++ b/src/routes/chats.js @@ -54,6 +54,18 @@ router.post( chatsHandlers.sendChatHandler ); +/** + * 채팅 정보를 업데이트 한 후, + * 같은 방에 있는 user들에게 업데이트를 요청합니다. + */ +router.post( + "/update", + body("roomId").isMongoId(), + body("lastMsgDate").isISO8601(), + validator, + chatsHandlers.updateChatHandler +); + // 채팅 이미지를 업로드할 수 있는 Presigned-url을 발급합니다. router.post( "/uploadChatImg/getPUrl", diff --git a/src/services/chats.js b/src/services/chats.js index 22e765b1..437470ed 100644 --- a/src/services/chats.js +++ b/src/services/chats.js @@ -165,6 +165,33 @@ const sendChatHandler = async (req, res) => { } }; +const updateChatHandler = async (req, res) => { + try { + const io = req.app.get("io"); + const { userId } = req; + const { roomId, lastMsgDate } = req.body; + const user = await userModel.findOne({ id: userId }); + + if (!userId || !user) { + return res.status(500).send("Chat/update : internal server error"); + } + if (!io) { + return res.status(403).send("Chat/update : socket did not connected"); + } + + const isPart = await isUserInRoom(userId, roomId); + if (!isPart) { + return res + .status(403) + .send("Chat/send : user did not participated in the room"); + } + + /* TODO */ + } catch (e) { + res.status(500).send("Chat/update : internal server error"); + } +}; + const uploadChatImgGetPUrlHandler = async (req, res) => { try { const { type, roomId } = req.body; @@ -277,4 +304,5 @@ module.exports = { sendChatHandler, uploadChatImgGetPUrlHandler, uploadChatImgDoneHandler, + updateChatHandler, }; From 5ece188a534e4d0f78e6057010f1c84a66b46a9f Mon Sep 17 00:00:00 2001 From: ptptsw Date: Tue, 30 May 2023 20:28:49 +0900 Subject: [PATCH 002/261] Refactor: Add node cron: --- src/schedules/index.js | 11 +++++++ src/schedules/notifyBeforeDepart.js | 48 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/schedules/index.js create mode 100644 src/schedules/notifyBeforeDepart.js diff --git a/src/schedules/index.js b/src/schedules/index.js new file mode 100644 index 00000000..c4ba74bf --- /dev/null +++ b/src/schedules/index.js @@ -0,0 +1,11 @@ +const cron = require("node-cron"); +const { + expression: sendReminderExpression, + sendReminder, +} = require("./notifyBeforeDepart"); + +const registerSchedules = () => { + cron.schedule(sendReminderExpression, sendReminder); +}; + +module.exports = registerSchedules; diff --git a/src/schedules/notifyBeforeDepart.js b/src/schedules/notifyBeforeDepart.js new file mode 100644 index 00000000..c486b064 --- /dev/null +++ b/src/schedules/notifyBeforeDepart.js @@ -0,0 +1,48 @@ +const { chatModel, userModel, roomModel } = require("../modules/stores/mongo"); +const { emitChatEvent } = require("../modules/socket"); + +const expression = "*/5 * * * *"; +const MS_PER_MINUTE = 60000; + +/** + * 출발까지 15분 남은 방들에 참여하고 있는 사용자들에게 리마인더 알림을 전송합니다. + * @summary 메시지는 보내지 않습니다. ㅎㅎ + * @return {Promise} 알림 전송에 실패한 기기 수를 반환합니다. + */ + +const sendReminder = async (req, res) => { + const currentDate = new Date(Date.now() - 15 * MS_PER_MINUTE).toISOString(); + + const roomIds = await roomModel + .find({ time: { $gte: currentDate } }) + .populate("ongoingRoom"); + console.log(roomIds); + + if (!roomIds) { + return res.status(404).json({ + error: "NotifyBeforeDepartment: cannot find room", + }); + } + // alert to each room object + + await emitChatEvent(req.app.get("io"), { + roomId: roomModel._id, + type: "department", + content: user.id, + }); + // department 채팅 메세지 유형 추가해야함 + + roomIds.map( + async (roomId) => + await emitChatEvent(req.app.get("io"), { + roomId: roomId, + type: "department", + content: user.id, + }) + ); +}; + +module.exports = { + expression, + sendReminder, +}; From 5c40faa456ecdb83502ffcfe40e594a0a6796e07 Mon Sep 17 00:00:00 2001 From: chlehdwon Date: Tue, 30 May 2023 14:28:37 +0000 Subject: [PATCH 003/261] Add: emitUpdateEvent --- src/modules/socket.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/modules/socket.js b/src/modules/socket.js index c16e7856..1cf3ef7a 100644 --- a/src/modules/socket.js +++ b/src/modules/socket.js @@ -177,6 +177,35 @@ const emitChatEvent = async (io, chat) => { } }; +const emitUpdateEvent = async (io, roomId, userId) => { + try { + // 방의 모든 사용자에게 socket 메세지 업데이트 이벤트를 발생시킵니다. + if (!io || !roomId || !userId) { + throw new IllegalArgumentsException(); + } + + const { name, part } = await roomModel.findById(roomId, "name part"); + + if (!name || !part) { + throw new IllegalArgumentsException(); + } + + const userIds = part.map((participant) => participant.user); + await Promise.all( + userIds.map(async (userId) => + io.in(`user-${userId}`).emit("chat_update", { + roomId, + }) + ) + ); + + return true; + } catch (err) { + logger.error(err); + return false; + } +}; + // https://socket.io/how-to/use-with-express-session 참고 const startSocketServer = (server) => { const io = new Server(server, { @@ -237,5 +266,6 @@ const startSocketServer = (server) => { module.exports = { transformChatsForRoom, emitChatEvent, + emitUpdateEvent, startSocketServer, }; From a9c3b010da192389f8b962557f7e95c97e098b83 Mon Sep 17 00:00:00 2001 From: chlehdwon Date: Tue, 30 May 2023 15:59:16 +0000 Subject: [PATCH 004/261] Add: sendChatHandler --- src/services/chats.js | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/services/chats.js b/src/services/chats.js index 437470ed..0183730b 100644 --- a/src/services/chats.js +++ b/src/services/chats.js @@ -1,7 +1,15 @@ const { chatModel, userModel, roomModel } = require("../modules/stores/mongo"); const { chatPopulateOption } = require("../modules/populates/chats"); const awsS3 = require("../modules/stores/awsS3"); -const { transformChatsForRoom, emitChatEvent } = require("../modules/socket"); +const { + transformChatsForRoom, + emitChatEvent, + emitUpdateEvent, +} = require("../modules/socket"); +const { + roomPopulateOption, + formatSettlement, +} = require("../modules/populates/rooms"); const chatCount = 60; @@ -178,15 +186,40 @@ const updateChatHandler = async (req, res) => { if (!io) { return res.status(403).send("Chat/update : socket did not connected"); } - const isPart = await isUserInRoom(userId, roomId); if (!isPart) { return res .status(403) - .send("Chat/send : user did not participated in the room"); + .send("Chat/update : user did not participated in the room"); + } + + const roomObject = await roomModel + .findOneAndUpdate( + { + _id: roomId, + part: { + $elemMatch: { + user: user._id, + }, + }, + }, + { + $set: { "part.$[updater].readAt": lastMsgDate }, + }, + { + new: true, + arrayFilters: [{ "updater.user": { $eq: user._id } }], + } + ) + .lean() + .populate(roomPopulateOption); + if (!roomObject) { + return res.status(404).send("Chat/update : cannot find room info"); } - /* TODO */ + /* TODO: Return Formatting */ + if (await emitUpdateEvent(io)) res.json({ part: roomObject.part }); + else res.status(500).send("Chat/update : internal server error"); } catch (e) { res.status(500).send("Chat/update : internal server error"); } From 76844ff35c7f1f1f355a76df7d57119f64745f61 Mon Sep 17 00:00:00 2001 From: chlehdwon Date: Tue, 30 May 2023 16:00:40 +0000 Subject: [PATCH 005/261] Add: readAt in participantSchema --- src/modules/stores/mongo.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index f9b49e84..9defbd25 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -34,6 +34,7 @@ const participantSchema = Schema({ enum: ["not-departed", "paid", "send-required", "sent"], default: "not-departed", }, + readAt: { type: Date, required: true, default: () => Date.now() }, }); const deviceTokenSchema = Schema({ From 81b6ebf8da96bc87d4cae0733d76139fd1dd1f4e Mon Sep 17 00:00:00 2001 From: ptptsw Date: Fri, 30 Jun 2023 23:25:08 +0900 Subject: [PATCH 006/261] Refactor: alert department --- app.js | 2 + package-lock.json | 12234 +------------------------- package.json | 1 + src/modules/socket.js | 4 +- src/modules/stores/mongo.js | 11 +- src/schedules/notifyBeforeDepart.js | 47 +- 6 files changed, 194 insertions(+), 12105 deletions(-) diff --git a/app.js b/app.js index 3e936950..681d1964 100644 --- a/app.js +++ b/app.js @@ -59,3 +59,5 @@ const serverHttp = http // socket.io 서버 시작 및 app 인스턴스에 저장 app.set("io", startSocketServer(serverHttp)); + +require("./src/schedules/index")(app.get("io")); diff --git a/package-lock.json b/package-lock.json index 73853d65..dd7300f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11942 +1,8 @@ { "name": "taxi-back", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "taxi-back", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "@adminjs/express": "^5.1.0", - "@adminjs/mongoose": "^3.0.3", - "adminjs": "^6.8.7", - "ajv": "^8.12.0", - "ajv-errors": "^3.0.0", - "ajv-formats": "^2.1.1", - "aws-sdk": "^2.1182.0", - "axios": "^0.27.2", - "ci": "^2.2.0", - "connect-mongo": "^4.6.0", - "connect-redis": "^6.1.3", - "cookie-parser": "^1.4.5", - "cors": "^2.8.5", - "cross-env": "^7.0.3", - "dotenv": "^16.0.1", - "eslint-config-prettier": "^8.3.0", - "express": "^4.17.1", - "express-formidable": "^1.2.0", - "express-rate-limit": "^6.6.0", - "express-session": "^1.17.3", - "express-validator": "^6.14.0", - "firebase-admin": "^11.4.1", - "jsonwebtoken": "^8.5.1", - "mongoose": "^6.5.2", - "node-mocks-http": "^1.12.1", - "querystring": "^0.2.1", - "redis": "^4.2.0", - "response-time": "^2.3.2", - "socket.io": "^4.6.1", - "swagger-ui-express": "^4.6.0", - "validator": "^13.7.0", - "winston": "^3.8.1", - "winston-daily-rotate-file": "^4.7.1" - }, - "devDependencies": { - "chai": "*", - "eslint": "^8.22.0", - "eslint-plugin-mocha": "^10.1.0", - "mocha": "*", - "nodemon": "^2.0.14", - "supertest": "^6.2.4" - } - }, - "node_modules/@adminjs/design-system": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@adminjs/design-system/-/design-system-3.1.8.tgz", - "integrity": "sha512-M0l8NXoHKFoJ9XLv6BkrgRPnE0hCYNYWVNiQKA4qOpzifB2LAPAViqQ36Qyxgz1mL9nnzl7OJpGlb8cHSrIajg==", - "dependencies": { - "@carbon/icons-react": "^11.8.0", - "@hypnosphi/create-react-context": "^0.3.1", - "@tiptap/core": "2.0.0-beta.217", - "@tiptap/extension-bubble-menu": "2.0.0-beta.217", - "@tiptap/extension-character-count": "2.0.0-beta.217", - "@tiptap/extension-code": "2.0.0-beta.217", - "@tiptap/extension-document": "2.0.0-beta.217", - "@tiptap/extension-floating-menu": "2.0.0-beta.217", - "@tiptap/extension-heading": "2.0.0-beta.217", - "@tiptap/extension-image": "2.0.0-beta.217", - "@tiptap/extension-link": "2.0.0-beta.217", - "@tiptap/extension-table": "2.0.0-beta.217", - "@tiptap/extension-table-cell": "2.0.0-beta.217", - "@tiptap/extension-table-header": "2.0.0-beta.217", - "@tiptap/extension-table-row": "2.0.0-beta.217", - "@tiptap/extension-text": "2.0.0-beta.217", - "@tiptap/extension-text-align": "2.0.0-beta.217", - "@tiptap/extension-typography": "2.0.0-beta.217", - "@tiptap/pm": "2.0.0-beta.217", - "@tiptap/react": "2.0.0-beta.217", - "@tiptap/starter-kit": "2.0.0-beta.217", - "date-fns": "2.15.0", - "jw-paginate": "^1.0.4", - "lodash": "^4.17.20", - "polished": "^3.6.5", - "react-currency-input-field": "^3.6.5", - "react-datepicker": "^4.8.0", - "react-phone-input-2": "^2.15.1", - "react-select": "^5.2.2", - "react-text-mask": "^5.4.3", - "styled-system": "^5.1.5", - "text-mask-addons": "^3.8.0" - }, - "peerDependencies": { - "react": "^18.1.0", - "react-dom": "^18.1.0", - "styled-components": "^5.3.5" - } - }, - "node_modules/@adminjs/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@adminjs/express/-/express-5.1.0.tgz", - "integrity": "sha512-+mrtDmoAYA9R+/FTYWOLL48g005yrgcAWC2phdwqGzznIxGKSp2YERcfzdTI7Svtnlaal72/QW8Q3OhzJjVLzQ==", - "dependencies": { - "path-to-regexp": "^6.2.0" - }, - "peerDependencies": { - "adminjs": ">=6.0.0", - "express": ">=4.16.4", - "express-formidable": "^1.2.0", - "express-session": ">=1.15.6", - "tslib": "^2.3.1" - } - }, - "node_modules/@adminjs/mongoose": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@adminjs/mongoose/-/mongoose-3.0.3.tgz", - "integrity": "sha512-J/Ogz3oJ2ytOsbeqBpjgIFtiAmGk3MVVfJq2cUidXJ1phrvNHhb7AjiaKd+pcdFcT84COUHaoo6uPYvrLhZEQg==", - "dependencies": { - "escape-regexp": "0.0.1", - "lodash": "^4.17.21" - }, - "peerDependencies": { - "adminjs": ">=6.0.0", - "mongoose": ">=5" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@aws-crypto/ie11-detection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", - "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", - "optional": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "optional": true - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", - "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", - "optional": true, - "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "optional": true - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", - "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", - "optional": true, - "dependencies": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "optional": true - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", - "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", - "optional": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "optional": true - }, - "node_modules/@aws-crypto/util": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", - "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "optional": true - }, - "node_modules/@aws-sdk/abort-controller": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.290.0.tgz", - "integrity": "sha512-Q4AqucQnhcsauH6tDf1bSRuOW/Ejwjs1qHPLlvknwX1IoxZettP3lXz9LLd8KZnEMFQLHPmBTbFIW+Ivpzl+vw==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.290.0.tgz", - "integrity": "sha512-hVDSCUkiM23jJe7t7CeYrDRq1umfr8IO599F0jjJg9AdWrLOWPKDv8Fxwk/VKivRgZjBZvYvgH482mFq3oJ94w==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.290.0", - "@aws-sdk/config-resolver": "3.290.0", - "@aws-sdk/credential-provider-node": "3.290.0", - "@aws-sdk/fetch-http-handler": "3.290.0", - "@aws-sdk/hash-node": "3.290.0", - "@aws-sdk/invalid-dependency": "3.290.0", - "@aws-sdk/middleware-content-length": "3.290.0", - "@aws-sdk/middleware-endpoint": "3.290.0", - "@aws-sdk/middleware-host-header": "3.290.0", - "@aws-sdk/middleware-logger": "3.290.0", - "@aws-sdk/middleware-recursion-detection": "3.290.0", - "@aws-sdk/middleware-retry": "3.290.0", - "@aws-sdk/middleware-serde": "3.290.0", - "@aws-sdk/middleware-signing": "3.290.0", - "@aws-sdk/middleware-stack": "3.290.0", - "@aws-sdk/middleware-user-agent": "3.290.0", - "@aws-sdk/node-config-provider": "3.290.0", - "@aws-sdk/node-http-handler": "3.290.0", - "@aws-sdk/protocol-http": "3.290.0", - "@aws-sdk/smithy-client": "3.290.0", - "@aws-sdk/types": "3.290.0", - "@aws-sdk/url-parser": "3.290.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.290.0", - "@aws-sdk/util-defaults-mode-node": "3.290.0", - "@aws-sdk/util-endpoints": "3.290.0", - "@aws-sdk/util-retry": "3.290.0", - "@aws-sdk/util-user-agent-browser": "3.290.0", - "@aws-sdk/util-user-agent-node": "3.290.0", - "@aws-sdk/util-utf8": "3.254.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.290.0.tgz", - "integrity": "sha512-FUFAbptuJSRKnzBgFJqXxusSG7PzECSqX0FnMh2vxCVu2PifaAE4stiMW8Myj8ABQAbfIrAWM+17upcrfmudoA==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.290.0", - "@aws-sdk/fetch-http-handler": "3.290.0", - "@aws-sdk/hash-node": "3.290.0", - "@aws-sdk/invalid-dependency": "3.290.0", - "@aws-sdk/middleware-content-length": "3.290.0", - "@aws-sdk/middleware-endpoint": "3.290.0", - "@aws-sdk/middleware-host-header": "3.290.0", - "@aws-sdk/middleware-logger": "3.290.0", - "@aws-sdk/middleware-recursion-detection": "3.290.0", - "@aws-sdk/middleware-retry": "3.290.0", - "@aws-sdk/middleware-serde": "3.290.0", - "@aws-sdk/middleware-stack": "3.290.0", - "@aws-sdk/middleware-user-agent": "3.290.0", - "@aws-sdk/node-config-provider": "3.290.0", - "@aws-sdk/node-http-handler": "3.290.0", - "@aws-sdk/protocol-http": "3.290.0", - "@aws-sdk/smithy-client": "3.290.0", - "@aws-sdk/types": "3.290.0", - "@aws-sdk/url-parser": "3.290.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.290.0", - "@aws-sdk/util-defaults-mode-node": "3.290.0", - "@aws-sdk/util-endpoints": "3.290.0", - "@aws-sdk/util-retry": "3.290.0", - "@aws-sdk/util-user-agent-browser": "3.290.0", - "@aws-sdk/util-user-agent-node": "3.290.0", - "@aws-sdk/util-utf8": "3.254.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.290.0.tgz", - "integrity": "sha512-/+OSYCjyf2TjA57beWLBjG05yPwWlpqK4gO3GwpVqfygaRh6g5jS0CBVQs+z+xc7gmI0weC/nhc+BXR9qcJJAA==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.290.0", - "@aws-sdk/fetch-http-handler": "3.290.0", - "@aws-sdk/hash-node": "3.290.0", - "@aws-sdk/invalid-dependency": "3.290.0", - "@aws-sdk/middleware-content-length": "3.290.0", - "@aws-sdk/middleware-endpoint": "3.290.0", - "@aws-sdk/middleware-host-header": "3.290.0", - "@aws-sdk/middleware-logger": "3.290.0", - "@aws-sdk/middleware-recursion-detection": "3.290.0", - "@aws-sdk/middleware-retry": "3.290.0", - "@aws-sdk/middleware-serde": "3.290.0", - "@aws-sdk/middleware-stack": "3.290.0", - "@aws-sdk/middleware-user-agent": "3.290.0", - "@aws-sdk/node-config-provider": "3.290.0", - "@aws-sdk/node-http-handler": "3.290.0", - "@aws-sdk/protocol-http": "3.290.0", - "@aws-sdk/smithy-client": "3.290.0", - "@aws-sdk/types": "3.290.0", - "@aws-sdk/url-parser": "3.290.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.290.0", - "@aws-sdk/util-defaults-mode-node": "3.290.0", - "@aws-sdk/util-endpoints": "3.290.0", - "@aws-sdk/util-retry": "3.290.0", - "@aws-sdk/util-user-agent-browser": "3.290.0", - "@aws-sdk/util-user-agent-node": "3.290.0", - "@aws-sdk/util-utf8": "3.254.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.290.0.tgz", - "integrity": "sha512-E2X/7tZLziKLgi/owYoUL5gcorGJrbM2tANJdJmaqVUPhPvoY4wU8P91pGPKon9nQj0RQexre5ClZawYD6lTzA==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.290.0", - "@aws-sdk/credential-provider-node": "3.290.0", - "@aws-sdk/fetch-http-handler": "3.290.0", - "@aws-sdk/hash-node": "3.290.0", - "@aws-sdk/invalid-dependency": "3.290.0", - "@aws-sdk/middleware-content-length": "3.290.0", - "@aws-sdk/middleware-endpoint": "3.290.0", - "@aws-sdk/middleware-host-header": "3.290.0", - "@aws-sdk/middleware-logger": "3.290.0", - "@aws-sdk/middleware-recursion-detection": "3.290.0", - "@aws-sdk/middleware-retry": "3.290.0", - "@aws-sdk/middleware-sdk-sts": "3.290.0", - "@aws-sdk/middleware-serde": "3.290.0", - "@aws-sdk/middleware-signing": "3.290.0", - "@aws-sdk/middleware-stack": "3.290.0", - "@aws-sdk/middleware-user-agent": "3.290.0", - "@aws-sdk/node-config-provider": "3.290.0", - "@aws-sdk/node-http-handler": "3.290.0", - "@aws-sdk/protocol-http": "3.290.0", - "@aws-sdk/smithy-client": "3.290.0", - "@aws-sdk/types": "3.290.0", - "@aws-sdk/url-parser": "3.290.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.290.0", - "@aws-sdk/util-defaults-mode-node": "3.290.0", - "@aws-sdk/util-endpoints": "3.290.0", - "@aws-sdk/util-retry": "3.290.0", - "@aws-sdk/util-user-agent-browser": "3.290.0", - "@aws-sdk/util-user-agent-node": "3.290.0", - "@aws-sdk/util-utf8": "3.254.0", - "fast-xml-parser": "4.1.2", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/config-resolver": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.290.0.tgz", - "integrity": "sha512-Ovskri6IR4iBK0+3ttgjPSgOUEC+fd5tqRN5JlPCCZ9VwqwF/z26yYC4fAPaMUAJwPVRFeYYzQoszXGoxPyG7g==", - "optional": true, - "dependencies": { - "@aws-sdk/signature-v4": "3.290.0", - "@aws-sdk/types": "3.290.0", - "@aws-sdk/util-config-provider": "3.208.0", - "@aws-sdk/util-middleware": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.290.0.tgz", - "integrity": "sha512-i2ZMIwnQ8bUP/qwzcWrfDxHHMB13M/935K8J0cJ4WmHaPWYRKECGgn2k1mJk2GLbUQ8yNEfM6RjA5EsPwhShvg==", - "optional": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.290.0", - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.290.0.tgz", - "integrity": "sha512-gWsllElBm4DWZcc42Zb6sxaw77KBf6cY9iEezbVzVbJioqR9hIr1Pq3Nx30z1Q+1KiHSnt/Wl9cYYHOoNw2DnQ==", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-imds": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.290.0.tgz", - "integrity": "sha512-PkYEs7zzUVWnhkR9TlU1ORDcCnkD7qoqR1loXXSZc+EIOX9M7f+sXGLtCXVl9wV1Ekx3a5Tjud+aQcOJjjFePA==", - "optional": true, - "dependencies": { - "@aws-sdk/node-config-provider": "3.290.0", - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/types": "3.290.0", - "@aws-sdk/url-parser": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.290.0.tgz", - "integrity": "sha512-n3OGvkvNgMS6Kb2fuFrmNeCI8CP7DGOsEvcfYPMiXsQWx9hHAh/XIv7ksD3TL5Mn8Dr0NHmB6uY5WgUZDatqfw==", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.290.0", - "@aws-sdk/credential-provider-imds": "3.290.0", - "@aws-sdk/credential-provider-process": "3.290.0", - "@aws-sdk/credential-provider-sso": "3.290.0", - "@aws-sdk/credential-provider-web-identity": "3.290.0", - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/shared-ini-file-loader": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.290.0.tgz", - "integrity": "sha512-snLmeD7yAYq1x7lngCTM1VGmHYCZ4iUW5JRG9XPr7Npl7VWVdnNqaf5XBYEANgaFoWxjN3dNyDPg05+5Ew6QCA==", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.290.0", - "@aws-sdk/credential-provider-imds": "3.290.0", - "@aws-sdk/credential-provider-ini": "3.290.0", - "@aws-sdk/credential-provider-process": "3.290.0", - "@aws-sdk/credential-provider-sso": "3.290.0", - "@aws-sdk/credential-provider-web-identity": "3.290.0", - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/shared-ini-file-loader": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.290.0.tgz", - "integrity": "sha512-PNnWDYSaE8dMepH59cyrXs45Ucdmzdnyuhcn/fVwQ0Nc7FzESxw1G7SgJZhYF4tMRDiepu6lbFEN0QXsTIM8Iw==", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/shared-ini-file-loader": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.290.0.tgz", - "integrity": "sha512-tX5Ez3EiMrXDx6Vsn2gMq7ga3y4iyPneenCNToRUlmZrhF61DhMfA22gRwdwuP8hlFKXY4LRg51pBfJeq0ga8w==", - "optional": true, - "dependencies": { - "@aws-sdk/client-sso": "3.290.0", - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/shared-ini-file-loader": "3.290.0", - "@aws-sdk/token-providers": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.290.0.tgz", - "integrity": "sha512-Apv6AnYtb5LTUreDVsqlXFNgiU0TQAZ8sfPg23pGrBGZvZU3KfDhF9n5j0i9Uca44O+/vB7UvbbvNAZS200vsQ==", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-providers": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.290.0.tgz", - "integrity": "sha512-CmIvhuOL3GhvzdfDexDiyofi3X2s8sUu4KX9UYJbA4aZnpANc0ie9QWb/b9EYsh8x/r8Ahj+1HUC15/XtnU7AQ==", - "optional": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.290.0", - "@aws-sdk/client-sso": "3.290.0", - "@aws-sdk/client-sts": "3.290.0", - "@aws-sdk/credential-provider-cognito-identity": "3.290.0", - "@aws-sdk/credential-provider-env": "3.290.0", - "@aws-sdk/credential-provider-imds": "3.290.0", - "@aws-sdk/credential-provider-ini": "3.290.0", - "@aws-sdk/credential-provider-node": "3.290.0", - "@aws-sdk/credential-provider-process": "3.290.0", - "@aws-sdk/credential-provider-sso": "3.290.0", - "@aws-sdk/credential-provider-web-identity": "3.290.0", - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/shared-ini-file-loader": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/fetch-http-handler": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.290.0.tgz", - "integrity": "sha512-hehbIxcqyJeiUBTbbP3C4tmY2p9UIh7bnLTKhocqaUcdEXQwlIRiQlnnA+TrQ5Uyoe+W3fAmv25tq08rB9ddhw==", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.290.0", - "@aws-sdk/querystring-builder": "3.290.0", - "@aws-sdk/types": "3.290.0", - "@aws-sdk/util-base64": "3.208.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/hash-node": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.290.0.tgz", - "integrity": "sha512-ayqJBOPoMa3H3eUhZHPu9ikNjoydu3nxj+R6tp8nMrKfFYDUu0XCdkpB0Wk/EBpMyWA2ZeyyfgXEUtQkqkAWBA==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.290.0", - "@aws-sdk/util-buffer-from": "3.208.0", - "@aws-sdk/util-utf8": "3.254.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/invalid-dependency": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.290.0.tgz", - "integrity": "sha512-plJpEJ+PPTrpaMfg5KKsAfdXUi6iUZTc/PgP0/CPqCe3kuiWb1xb2GeTxOL5InzfBffVdHWeTanYu9+V0MIxVw==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/is-array-buffer": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz", - "integrity": "sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-content-length": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.290.0.tgz", - "integrity": "sha512-9I+vnGSe/S0U98ZRCbOAdQngYfO7kYvXb5gjjX08XUQDfbB+ooIM1VdKngHhzUCTAs48z/43PzpBCjGJvGjB9w==", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-endpoint": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.290.0.tgz", - "integrity": "sha512-A7wIujIHHoQaQaqjlRynqoZ3S4S8ExYDReXUBwf4Dzx0wZ5A50owLMY9MKFyd9uukirZs8mDnPPYZuwUI4wR7w==", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-serde": "3.290.0", - "@aws-sdk/protocol-http": "3.290.0", - "@aws-sdk/signature-v4": "3.290.0", - "@aws-sdk/types": "3.290.0", - "@aws-sdk/url-parser": "3.290.0", - "@aws-sdk/util-config-provider": "3.208.0", - "@aws-sdk/util-middleware": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.290.0.tgz", - "integrity": "sha512-j1ss8pjSJyG0aB+X0VPYgTfoieB8m5c+PrWw85JRM/qgbQeurkyD3d/F00V1hkZI42gygOaPlmYMik3kQnmITw==", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.290.0.tgz", - "integrity": "sha512-wJOK31t/Y/Km6B5ULF/k2RmQB/6MXSN/hMuCiYsLMapFT+86mBlY8cXytYXtLS8afRKpuNy29EY+O6ovfjz6Ig==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.290.0.tgz", - "integrity": "sha512-m8Y7SE3NfVTyGubiRhueyHF7uqC5dCbD1bSLgVjvrSjO2yEL0Dv9QR1ad7a+p5ilS+Fq3RnOu1VeujfTHy0qRQ==", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-retry": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.290.0.tgz", - "integrity": "sha512-mvXvYd/3L/f5ZcnFI1Q2hwk0OtzKMmkDfWW1BcoVzK0XHf2aeehbs7xgI92ICEi/5Ali0IG5krv5LqfgO154Sw==", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.290.0", - "@aws-sdk/service-error-classification": "3.290.0", - "@aws-sdk/types": "3.290.0", - "@aws-sdk/util-middleware": "3.290.0", - "@aws-sdk/util-retry": "3.290.0", - "tslib": "^2.3.1", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.290.0.tgz", - "integrity": "sha512-NaYnDhFtmz/e9jNBNeY10A4AldCvjF46ZfeIWoBUsk/4qDlSP9kaCjTufEjNf/zMTtYzGiP/FUtLS7T6tfXdoQ==", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-signing": "3.290.0", - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/protocol-http": "3.290.0", - "@aws-sdk/signature-v4": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-serde": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.290.0.tgz", - "integrity": "sha512-lZCKlfJzosi3cVx02RKRTVvbAijHTfd16EiSyKRsQOF2rCu7Qt4LzygIlqUonCeHG6eSqOMMf7LAJ22IHafBbw==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-signing": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.290.0.tgz", - "integrity": "sha512-mEJZQrbXkOTI+BdFlpAd1CleVJL8B7qayANMNj9nrZqvZ7HzVDLEkNaJqFz9JFizYTfZC2ZjtATPrSiYDvFEfg==", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/protocol-http": "3.290.0", - "@aws-sdk/signature-v4": "3.290.0", - "@aws-sdk/types": "3.290.0", - "@aws-sdk/util-middleware": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-stack": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.290.0.tgz", - "integrity": "sha512-25iC/7oAokRfxixGkDjBSIAkNwtx2kcO+xMoDczFus9UrlOr2pBY0IXbPn6bB56q2zwsBTHcmMTn0H7FJSIQmw==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.290.0.tgz", - "integrity": "sha512-ZR49PPra3LtqZBmXAtV8YrUSrkVG0hPBICE8cma/wMwbKGHa0G+Xu4pOZP0oQXs5XeGu1cs/Nx3AOJ2fgaMjhQ==", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-config-provider": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.290.0.tgz", - "integrity": "sha512-dQLnyCy5iT7Q5Ot2JOciNH9WkaixWwmEnvW6nBa6febzAYZVy78sfJOOP1EZ7ClG1aIhrsAN7/7wPebpn/Peiw==", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/shared-ini-file-loader": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-http-handler": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.290.0.tgz", - "integrity": "sha512-HfzuzdpAJpO/ob9DQ3aEB/WmPCS5vZOic9T4TtSCmRd5e3+xdMtK/MQUizp8XkbUGBat7jPmcV13Gy4YmwfAuw==", - "optional": true, - "dependencies": { - "@aws-sdk/abort-controller": "3.290.0", - "@aws-sdk/protocol-http": "3.290.0", - "@aws-sdk/querystring-builder": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/property-provider": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.290.0.tgz", - "integrity": "sha512-2Zrh6/KecmiZ/cKVaeDtHRAfyOnAEfwJsgxfFugs3RxjJtYr0AbYJTF+mYp3f8Xc7DCjdxR055axo9TCTBSrwg==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/protocol-http": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.290.0.tgz", - "integrity": "sha512-3VHbfmo7vaA/0ugJedjwyK85MT+OKQanz7ktUnAONH5KdG2/gPpa9ZSTyfK9kCVFin93YzC3pznZgr6oNYgGgg==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/querystring-builder": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.290.0.tgz", - "integrity": "sha512-7q8x8ux1RCUxUolqxsXfSbCObyMzvSwfJb9GgZ8rDi2U61l8W760a9ejHzizfQJvdldRSwFqmynkRAqYbvKixg==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.290.0", - "@aws-sdk/util-uri-escape": "3.201.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/querystring-parser": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.290.0.tgz", - "integrity": "sha512-8QPDihJKSFYFphxUl5+FfXMQowhAoHuDeoqd1ce3byL0bm7k8emcGfiYD6QGxuDlpno+F4O1/Mz+e+cwNCdPVA==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/service-error-classification": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.290.0.tgz", - "integrity": "sha512-QP+QgL5Gm6RKl4KGwTRyG1kw0SxBbcmp/a/yhywVHmRI0/+4VsL+cooTqtjFr3xVmKoCX+/JZZ8P96VGFvRSZA==", - "optional": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/shared-ini-file-loader": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.290.0.tgz", - "integrity": "sha512-kvLW5rwr4lwHdwkYnoHYpFVfWwZYwQO44eRnkrDnyvvhZTcCH3rBLApu6uvomnL+Ep4bEJ1anDKt3WywlGg5Qw==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.290.0.tgz", - "integrity": "sha512-SUMflc8b8PC0ITV3AdYBSlTcn4oFjumBAPNNXBLKIpifQ1l7ZufFIulDPlqeouXTDwsuCVINAwE0DbItDe/7Qw==", - "optional": true, - "dependencies": { - "@aws-sdk/is-array-buffer": "3.201.0", - "@aws-sdk/types": "3.290.0", - "@aws-sdk/util-hex-encoding": "3.201.0", - "@aws-sdk/util-middleware": "3.290.0", - "@aws-sdk/util-uri-escape": "3.201.0", - "@aws-sdk/util-utf8": "3.254.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/smithy-client": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.290.0.tgz", - "integrity": "sha512-MDa+BJqM1FP2HYugVAscufoLJuapEdUTZPoyERVGfUEznKfKH33QXRoeqW1wzUNyhcxFONHLnXp1aYFBtnLx7g==", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-stack": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.290.0.tgz", - "integrity": "sha512-fc5y8WH7RVwoaUaEdK3cRanxgHShZKAPZ0rCtHjoLURF8IjZIrn3AaZqV8YTgAAmIKNVC+argpj1G+suqXEB/Q==", - "optional": true, - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.290.0", - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/shared-ini-file-loader": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.290.0.tgz", - "integrity": "sha512-uQLD9tLv8Q87CwrSB/taUoQ8wkGeFb1Gygc+kt5oClfMFP9HYzu944kW/1R7/J5LtBLT1QFYccd4gz6eOUNlsw==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/url-parser": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.290.0.tgz", - "integrity": "sha512-19EAlyH4LyNMbAROE6KSuhFKhOwl67kciDavPjS8gFiHr6slon3oqXfz10+uzKf/pJKuY6qOpkUb9h7LnF4bFQ==", - "optional": true, - "dependencies": { - "@aws-sdk/querystring-parser": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/util-base64": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.208.0.tgz", - "integrity": "sha512-PQniZph5A6N7uuEOQi+1hnMz/FSOK/8kMFyFO+4DgA1dZ5pcKcn5wiFwHkcTb/BsgVqQa3Jx0VHNnvhlS8JyTg==", - "optional": true, - "dependencies": { - "@aws-sdk/util-buffer-from": "3.208.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-body-length-browser": { - "version": "3.188.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.188.0.tgz", - "integrity": "sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/util-body-length-node": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.208.0.tgz", - "integrity": "sha512-3zj50e5g7t/MQf53SsuuSf0hEELzMtD8RX8C76f12OSRo2Bca4FLLYHe0TZbxcfQHom8/hOaeZEyTyMogMglqg==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-buffer-from": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.208.0.tgz", - "integrity": "sha512-7L0XUixNEFcLUGPeBF35enCvB9Xl+K6SQsmbrPk1P3mlV9mguWSDQqbOBwY1Ir0OVbD6H/ZOQU7hI/9RtRI0Zw==", - "optional": true, - "dependencies": { - "@aws-sdk/is-array-buffer": "3.201.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-config-provider": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.208.0.tgz", - "integrity": "sha512-DSRqwrERUsT34ug+anlMBIFooBEGwM8GejC7q00Y/9IPrQy50KnG5PW2NiTjuLKNi7pdEOlwTSEocJE15eDZIg==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-defaults-mode-browser": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.290.0.tgz", - "integrity": "sha512-8Mt6/OA465uw1wSA/LCCd+6IjeIUTAbg2GiqfSBCBMNJNuqPwPXuWVjg6kBd1eEChyEtAuoLTygMefaBywg4HQ==", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/types": "3.290.0", - "bowser": "^2.11.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/util-defaults-mode-node": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.290.0.tgz", - "integrity": "sha512-9c0jS7w1aZxfKkFXlTjp80QaKYKnutMmlsfP+/YXN9+s3yvwFcnsENMTNg5YVvkZa9e+Rhw/ySxVKTEJ7n/SOA==", - "optional": true, - "dependencies": { - "@aws-sdk/config-resolver": "3.290.0", - "@aws-sdk/credential-provider-imds": "3.290.0", - "@aws-sdk/node-config-provider": "3.290.0", - "@aws-sdk/property-provider": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.290.0.tgz", - "integrity": "sha512-nDdSyWdxYEPE84qABQKasIFhm6oWjhiyM92g8zsHTqzrn67a4caA72FTL6cztgJOEd5GWvHn6r1BnRVhkG68Qw==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-hex-encoding": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.201.0.tgz", - "integrity": "sha512-7t1vR1pVxKx0motd3X9rI3m/xNp78p3sHtP5yo4NP4ARpxyJ0fokBomY8ScaH2D/B+U5o9ARxldJUdMqyBlJcA==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.208.0.tgz", - "integrity": "sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-middleware": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.290.0.tgz", - "integrity": "sha512-lXGM9YSqwZgCeEPltc++jiGyZ/FLuh62IjrWSIVSL/FvkL6D8KSKNBd7Ab/KDDu5jt4iP5UZ4k3SGVk6monUZg==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-retry": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.290.0.tgz", - "integrity": "sha512-UjyUEguu2upaBvDJkeSUQPE4ryBTA7JhPyl6M7XA6rFSRtU5+1NI8KknSNw46buviNit0Yu0E6TzxNQyS70hKA==", - "optional": true, - "dependencies": { - "@aws-sdk/service-error-classification": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@aws-sdk/util-uri-escape": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz", - "integrity": "sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.290.0.tgz", - "integrity": "sha512-I+B5ooKRYQ9jHcdg7TOf20LlTfcBUlCJQ2AAqI1ukmJqal22OD1CtC1E+/XbplpU5mxRs4s2UQbxNaPA0yIrBA==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.290.0", - "bowser": "^2.11.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.290.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.290.0.tgz", - "integrity": "sha512-7juKgEMqpa0il6jZmiBKGDJslM4UIKX1bvhlqkSvvPfV3zFdfi0V2xavh68GfelWduBBkYLGRjsLunqzw64f8A==", - "optional": true, - "dependencies": { - "@aws-sdk/node-config-provider": "3.290.0", - "@aws-sdk/types": "3.290.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/util-utf8": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.254.0.tgz", - "integrity": "sha512-14Kso/eIt5/qfIBmhEL9L1IfyUqswjSTqO2mY7KOzUZ9SZbwn3rpxmtkhmATkRjD7XIlLKaxBkI7tU9Zjzj8Kw==", - "optional": true, - "dependencies": { - "@aws-sdk/util-buffer-from": "3.208.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", - "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", - "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.0.tgz", - "integrity": "sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.0", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.21.0", - "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.0", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.21.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz", - "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==", - "dependencies": { - "@babel/types": "^7.21.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz", - "integrity": "sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-member-expression-to-functions": "^7.21.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/helper-split-export-declaration": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.0.tgz", - "integrity": "sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", - "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz", - "integrity": "sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==", - "dependencies": { - "@babel/types": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", - "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.2", - "@babel/types": "^7.21.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", - "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", - "dependencies": { - "@babel/types": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", - "dependencies": { - "@babel/types": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", - "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", - "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", - "dependencies": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", - "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", - "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz", - "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", - "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", - "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", - "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", - "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz", - "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz", - "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", - "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz", - "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz", - "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", - "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/template": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz", - "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz", - "integrity": "sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", - "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", - "dependencies": { - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz", - "integrity": "sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==", - "dependencies": { - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-simple-access": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", - "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", - "dependencies": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-identifier": "^7.19.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", - "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.20.5", - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz", - "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.0.tgz", - "integrity": "sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", - "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", - "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", - "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "regenerator-transform": "^0.15.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.0.tgz", - "integrity": "sha512-ReY6pxwSzEU0b3r2/T/VhqMKg/AkceBT19X0UptA3/tYi5Pe2eXgEUH+NNMC5nok6c6XQz5tyVTUpuezRfSMSg==", - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", - "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.0.tgz", - "integrity": "sha512-xo///XTPp3mDzTtrqXoBlK9eiAYW3wv9JXglcn/u1bi60RW11dEUxIgA8cbnDhutS1zacjMRmAwxE0gMklLnZg==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-typescript": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/polyfill": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.12.1.tgz", - "integrity": "sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==", - "deprecated": "🚨 This package has been deprecated in favor of separate inclusion of a polyfill and regenerator-runtime (when needed). See the @babel/polyfill docs (https://babeljs.io/docs/en/babel-polyfill) for more information.", - "dependencies": { - "core-js": "^2.6.5", - "regenerator-runtime": "^0.13.4" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", - "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", - "dependencies": { - "@babel/compat-data": "^7.20.1", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.20.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.20.2", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.20.2", - "@babel/plugin-transform-classes": "^7.20.2", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.20.2", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.19.6", - "@babel/plugin-transform-modules-commonjs": "^7.19.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.6", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.20.1", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.20.2", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-react": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", - "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-react-display-name": "^7.18.6", - "@babel/plugin-transform-react-jsx": "^7.18.6", - "@babel/plugin-transform-react-jsx-development": "^7.18.6", - "@babel/plugin-transform-react-pure-annotations": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.21.0.tgz", - "integrity": "sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.21.0", - "@babel/plugin-transform-typescript": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/register": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.21.0.tgz", - "integrity": "sha512-9nKsPmYDi5DidAqJaQooxIhsLJiNMkGr8ypQ8Uic7cIox7UCDsM7HuUGxdGT7mSDTYbqzIdsOWzfBton/YJrMw==", - "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.5", - "source-map-support": "^0.5.16" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" - }, - "node_modules/@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.2.tgz", - "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==", - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.1", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.2", - "@babel/types": "^7.21.2", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz", - "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==", - "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@carbon/icon-helpers": { - "version": "10.39.0", - "resolved": "https://registry.npmjs.org/@carbon/icon-helpers/-/icon-helpers-10.39.0.tgz", - "integrity": "sha512-iFWIfjKABjusb+gUz6s0FdEBHe8Ms63CKDxozhtiSZ9LfF9X5QQztO8df3szqcNsmw30pYhSnm+zJwifO9tdRw==" - }, - "node_modules/@carbon/icons-react": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@carbon/icons-react/-/icons-react-11.17.0.tgz", - "integrity": "sha512-PheX1aGh12mfybHeLB9w8Y8UzARuqpVanJ3zTDCUQKfPFaOezAt+6E9lqjj5xq3QgpyZcrCEOFiaPV90n3MqGQ==", - "hasInstallScript": true, - "dependencies": { - "@carbon/icon-helpers": "^10.39.0", - "@carbon/telemetry": "0.1.0", - "prop-types": "^15.7.2" - }, - "peerDependencies": { - "react": ">=16" - } - }, - "node_modules/@carbon/telemetry": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@carbon/telemetry/-/telemetry-0.1.0.tgz", - "integrity": "sha512-kNWt0bkgPwGW0i5h7HFuljbKRXPvIhsKbB+1tEURAYLXoJg9iJLF1eGvWN5iVoFCS2zje4GR3OGOsvvKVe7Hlg==", - "bin": { - "carbon-telemetry": "bin/carbon-telemetry.js" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, - "node_modules/@emotion/babel-plugin": { - "version": "11.10.6", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz", - "integrity": "sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/serialize": "^1.1.1", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.1.3" - } - }, - "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@emotion/babel-plugin/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@emotion/cache": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", - "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", - "dependencies": { - "@emotion/memoize": "^0.8.0", - "@emotion/sheet": "^1.2.1", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "stylis": "4.1.3" - } - }, - "node_modules/@emotion/hash": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", - "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" - }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", - "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", - "dependencies": { - "@emotion/memoize": "^0.8.0" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" - }, - "node_modules/@emotion/react": { - "version": "11.10.6", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.6.tgz", - "integrity": "sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.6", - "@emotion/cache": "^11.10.5", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "hoist-non-react-statics": "^3.3.1" - }, - "peerDependencies": { - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/serialize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz", - "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==", - "dependencies": { - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/unitless": "^0.8.0", - "@emotion/utils": "^1.2.0", - "csstype": "^3.0.2" - } - }, - "node_modules/@emotion/sheet": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", - "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" - }, - "node_modules/@emotion/stylis": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" - }, - "node_modules/@emotion/unitless": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", - "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" - }, - "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", - "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@emotion/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" - }, - "node_modules/@emotion/weak-memoize": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", - "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.2.0.tgz", - "integrity": "sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==", - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz", - "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", - "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.5.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", - "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@fastify/busboy": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", - "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", - "dependencies": { - "text-decoding": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@firebase/app-types": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", - "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==" - }, - "node_modules/@firebase/auth-interop-types": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", - "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==" - }, - "node_modules/@firebase/component": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", - "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", - "dependencies": { - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.4.tgz", - "integrity": "sha512-+Ea/IKGwh42jwdjCyzTmeZeLM3oy1h0mFPsTy6OqCWzcu/KFqRAr5Tt1HRCOBlNOdbh84JPZC47WLU18n2VbxQ==", - "dependencies": { - "@firebase/auth-interop-types": "0.2.1", - "@firebase/component": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-compat": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz", - "integrity": "sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg==", - "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/database": "0.14.4", - "@firebase/database-types": "0.10.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-types": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz", - "integrity": "sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ==", - "dependencies": { - "@firebase/app-types": "0.9.0", - "@firebase/util": "1.9.3" - } - }, - "node_modules/@firebase/logger": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", - "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/util": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", - "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.4.tgz", - "integrity": "sha512-SQOeVbMwb1di+mVWWJLpsUTToKfqVNioXys011beCAhyOIFtS+GQoW4EQSneuxzmQKddExDwQ+X0hLl4lJJaSQ==" - }, - "node_modules/@floating-ui/dom": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.4.tgz", - "integrity": "sha512-4+k+BLhtWj+peCU60gp0+rHeR8+Ohqx6kjJf/lHMnJ8JD5Qj6jytcq1+SZzRwD7rvHKRhR7TDiWWddrNrfwQLg==", - "dependencies": { - "@floating-ui/core": "^1.2.3" - } - }, - "node_modules/@google-cloud/firestore": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.5.0.tgz", - "integrity": "sha512-U0QwG6pEQxO5c0v0eUylswozmuvlvz7iXSW+I18jzqR2hAFrUq2Weu1wm3NaH8wGD4ZL7W9Be4cMHG5CYU8LuQ==", - "optional": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "functional-red-black-tree": "^1.0.1", - "google-gax": "^3.5.7", - "protobufjs": "^7.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/paginator": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", - "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", - "optional": true, - "dependencies": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/projectify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", - "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", - "optional": true, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/promisify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", - "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", - "optional": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@google-cloud/storage": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.9.4.tgz", - "integrity": "sha512-5Li+0xRJ8wgc+vlf7Tgew8COKEJgRzRmC5ozdSYaBj7BK+X39aPPBP6ROsDTiCZ0MpAg7dxIc+HhKiCvQDplXQ==", - "optional": true, - "dependencies": { - "@google-cloud/paginator": "^3.0.7", - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", - "abort-controller": "^3.0.0", - "async-retry": "^1.3.3", - "compressible": "^2.0.12", - "duplexify": "^4.0.0", - "ent": "^2.2.0", - "extend": "^3.0.2", - "gaxios": "^5.0.0", - "google-auth-library": "^8.0.1", - "mime": "^3.0.0", - "mime-types": "^2.0.8", - "p-limit": "^3.0.1", - "retry-request": "^5.0.0", - "teeny-request": "^8.0.0", - "uuid": "^8.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@grpc/grpc-js": { - "version": "1.8.12", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.12.tgz", - "integrity": "sha512-MbUMvpVvakeKhdYux6gbSIPJaFMLNSY8jw4PqLI+FFztGrQRrYYAnHlR94+ncBQQewkpXQaW449m3tpH/B/ZnQ==", - "optional": true, - "dependencies": { - "@grpc/proto-loader": "^0.7.0", - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.5.tgz", - "integrity": "sha512-mfcTuMbFowq1wh/Rn5KQl6qb95M21Prej3bewD9dUQMurYGVckGO/Pbe2Ocwto6sD05b/mxZLspvqwx60xO2Rg==", - "optional": true, - "dependencies": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^7.0.0", - "yargs": "^16.2.0" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@hello-pangea/dnd": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-16.2.0.tgz", - "integrity": "sha512-inACvMcvvLr34CG0P6+G/3bprVKhwswxjcsFUSJ+fpOGjhvDj9caiA9X3clby0lgJ6/ILIJjyedHZYECB7GAgA==", - "dependencies": { - "@babel/runtime": "^7.19.4", - "css-box-model": "^1.2.1", - "memoize-one": "^6.0.0", - "raf-schd": "^4.0.3", - "react-redux": "^8.0.4", - "redux": "^4.2.0", - "use-memo-one": "^1.1.3" - }, - "peerDependencies": { - "react": "^16.8.5 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" - }, - "node_modules/@hypnosphi/create-react-context": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", - "integrity": "sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==", - "dependencies": { - "gud": "^1.0.0", - "warning": "^4.0.3" - }, - "peerDependencies": { - "prop-types": "^15.0.0", - "react": ">=0.14.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/@jsdoc/salty": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.5.tgz", - "integrity": "sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==", - "optional": true, - "dependencies": { - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=v12.0.0" - } - }, - "node_modules/@linaria/core": { - "version": "3.0.0-beta.13", - "resolved": "https://registry.npmjs.org/@linaria/core/-/core-3.0.0-beta.13.tgz", - "integrity": "sha512-3zEi5plBCOsEzUneRVuQb+2SAx3qaC1dj0FfFAI6zIJQoDWu0dlSwKijMRack7oO9tUWrchfj3OkKQAd1LBdVg==" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "optional": true - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "optional": true - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "optional": true - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "optional": true - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "optional": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "optional": true - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "optional": true - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "optional": true - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "optional": true - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "optional": true - }, - "node_modules/@redis/bloom": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", - "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/client": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.6.tgz", - "integrity": "sha512-dFD1S6je+A47Lj22jN/upVU2fj4huR7S9APd7/ziUXsIXDL+11GPYti4Suv5y8FuXaN+0ZG4JF+y1houEJ7ToA==", - "dependencies": { - "cluster-key-slot": "1.1.2", - "generic-pool": "3.9.0", - "yallist": "4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@redis/client/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/@redis/graph": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz", - "integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/json": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz", - "integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/search": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.2.tgz", - "integrity": "sha512-/cMfstG/fOh/SsE+4/BQGeuH/JJloeWuH+qJzM8dbxuWvdWibWAOAHHCZTMPhV3xIlH4/cUEIA8OV5QnYpaVoA==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/time-series": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz", - "integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@remirror/core-constants": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-2.0.0.tgz", - "integrity": "sha512-vpePPMecHJllBqCWXl6+FIcZqS+tRUM2kSCCKFeEo1H3XUEv3ocijBIPhnlSAa7g6maX+12ATTgxrOsLpWVr2g==", - "dependencies": { - "@babel/runtime": "^7.13.10" - } - }, - "node_modules/@remirror/core-helpers": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@remirror/core-helpers/-/core-helpers-2.0.1.tgz", - "integrity": "sha512-s8M1pn33aBUhduvD1QR02uUQMegnFkGaTr4c1iBzxTTyg0rbQstzuQ7Q8TkL6n64JtgCdJS9jLz2dONb2meBKQ==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@linaria/core": "3.0.0-beta.13", - "@remirror/core-constants": "^2.0.0", - "@remirror/types": "^1.0.0", - "@types/object.omit": "^3.0.0", - "@types/object.pick": "^1.3.1", - "@types/throttle-debounce": "^2.1.0", - "case-anything": "^2.1.10", - "dash-get": "^1.0.2", - "deepmerge": "^4.2.2", - "fast-deep-equal": "^3.1.3", - "make-error": "^1.3.6", - "object.omit": "^3.0.0", - "object.pick": "^1.3.0", - "throttle-debounce": "^3.0.1" - } - }, - "node_modules/@remirror/types": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@remirror/types/-/types-1.0.0.tgz", - "integrity": "sha512-7HQbW7k8VxrAtfzs9FxwO6XSDabn8tSFDi1wwzShOnU+cvaYpfxu0ygyTk3TpXsag1hgFKY3ZIlAfB4WVz2LkQ==", - "dependencies": { - "type-fest": "^2.0.0" - } - }, - "node_modules/@remix-run/router": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz", - "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==", - "engines": { - "node": ">=14" - } - }, - "node_modules/@rollup/plugin-babel": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", - "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", - "dependencies": { - "@babel/helper-module-imports": "^7.10.4", - "@rollup/pluginutils": "^3.1.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "@types/babel__core": "^7.1.9", - "rollup": "^1.20.0||^2.0.0" - }, - "peerDependenciesMeta": { - "@types/babel__core": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "22.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-22.0.2.tgz", - "integrity": "sha512-//NdP6iIwPbMTcazYsiBMbJW7gfmpHom33u1beiIoHDEM0Q9clvtQB1T0efvMqHeKsGohiHo97BCPCkBXdscwg==", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "commondir": "^1.0.1", - "estree-walker": "^2.0.1", - "glob": "^7.1.6", - "is-reference": "^1.2.1", - "magic-string": "^0.25.7", - "resolve": "^1.17.0" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "rollup": "^2.68.0" - } - }, - "node_modules/@rollup/plugin-json": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", - "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", - "dependencies": { - "@rollup/pluginutils": "^3.0.8" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz", - "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "rollup": "^2.42.0" - } - }, - "node_modules/@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" - }, - "node_modules/@styled-system/background": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/background/-/background-5.1.2.tgz", - "integrity": "sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/border": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/border/-/border-5.1.5.tgz", - "integrity": "sha512-JvddhNrnhGigtzWRCVuAHepniyVi6hBlimxWDVAdcTuk7aRn9BYJUwfHslURtwYFsF5FoEs8Zmr1oZq2M1AP0A==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/color": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/color/-/color-5.1.2.tgz", - "integrity": "sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/core": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/core/-/core-5.1.2.tgz", - "integrity": "sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw==", - "dependencies": { - "object-assign": "^4.1.1" - } - }, - "node_modules/@styled-system/css": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/css/-/css-5.1.5.tgz", - "integrity": "sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A==" - }, - "node_modules/@styled-system/flexbox": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/flexbox/-/flexbox-5.1.2.tgz", - "integrity": "sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/grid": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/grid/-/grid-5.1.2.tgz", - "integrity": "sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/layout": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/layout/-/layout-5.1.2.tgz", - "integrity": "sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/position": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/position/-/position-5.1.2.tgz", - "integrity": "sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/shadow": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/shadow/-/shadow-5.1.2.tgz", - "integrity": "sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/space": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/space/-/space-5.1.2.tgz", - "integrity": "sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/typography": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/typography/-/typography-5.1.2.tgz", - "integrity": "sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/variant": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/variant/-/variant-5.1.5.tgz", - "integrity": "sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw==", - "dependencies": { - "@styled-system/core": "^5.1.2", - "@styled-system/css": "^5.1.5" - } - }, - "node_modules/@tiptap/core": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.0-beta.217.tgz", - "integrity": "sha512-Vifwcg5SglkVjEmtFbnwHOKWU4UUenOhe7ke5fqGhh7FNfGkccu6sK8W1JTDbG4ARWZ1b/632kQ51YE+WuPe7g==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/pm": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-blockquote": { - "version": "2.0.0-beta.220", - "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.220.tgz", - "integrity": "sha512-uE1VRU/doQzXsfsZ/JqsbSbXeZYTJnyQkSfHYA2ZYhbEM2XqDEsYkgcmZEJgunUZJpERf+3ZTfTpqaHq29iMMg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.1" - } - }, - "node_modules/@tiptap/extension-bold": { - "version": "2.0.0-beta.220", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.220.tgz", - "integrity": "sha512-KcEuKI85Drug/cCWbDy+HxhYrD+rLXHEBG10DmKPvgPpKHG/2wOau6LwUwyV4muWR8CR2mIO+mEc3yVBD8nNwQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-bubble-menu": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.217.tgz", - "integrity": "sha512-f52hjIzNbvAJy0P3pTf6sv/65XlMU1LCLXam4VCTkYJ2HHVvW+LZcu+dG1M8YmGkM9l+6dQPs5L2rriAQDXt+w==", - "dependencies": { - "lodash": "^4.17.21", - "tippy.js": "^6.3.7" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209", - "@tiptap/pm": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-bullet-list": { - "version": "2.0.0-beta.220", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.220.tgz", - "integrity": "sha512-QQ/0ZlYy6Hgb+UAc79V+fxvI+AaQf20cbKtBXaR8TIZ0x4FotSma89bKh+CIXMhFiBGXTcYBaYhl7OwACsKtxw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-character-count": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.0.0-beta.217.tgz", - "integrity": "sha512-jj3GzuoUbHcPsRyIkxr/X/+FmnvKeDUtzvWhhpEPOlCf9q5Wjq95VbVYZdNPlEXCNCdMu0ePvyW+v8+kFr+Qlw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209", - "@tiptap/pm": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-code": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.0.0-beta.217.tgz", - "integrity": "sha512-IPpuI8MeVX5N0ueWQcIvh2nD+EN6DbFtKSsoU+FgFJ9meOkiEWGibSJ5yFQinPsiE9zrwYIaZzzjhr4oeY2JNA==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-code-block": { - "version": "2.0.0-beta.220", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.220.tgz", - "integrity": "sha512-fgA7yTfHqhBtMJF7I9FPJ6UWuZPtxOQiN45Iv9LNmFIB6YRucdpmF+daZ27sElu0a+eICZyXwVn4w4iJphifuw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209", - "@tiptap/pm": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-document": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.0.0-beta.217.tgz", - "integrity": "sha512-CDCTutbVO1Ub7QUULPCYILl2px48ezCX2kxbspQZzPD7nMoYZNmUMZ8gRJvVDdnSpMNlEl7oNnCAU7uC/lny/A==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-dropcursor": { - "version": "2.0.0-beta.220", - "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.220.tgz", - "integrity": "sha512-BIaA4Lvb3xL9KFN+K6SO2IHqLO6hDmGN2/rGKHFaU3Eh+oiXM2G73KTSS5KIP1u872zY1RpAtswSc4kjv3cuVw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209", - "@tiptap/pm": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-floating-menu": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.217.tgz", - "integrity": "sha512-mP77UrimZo3sdC+Xam0DsPeYfXn29hl4ixEuZeiQQBMqz7g1K9T7vOEsRMQCBVFS7Hsc7MWBqLa1lL1GtyBB9Q==", - "dependencies": { - "tippy.js": "^6.3.7" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209", - "@tiptap/pm": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-gapcursor": { - "version": "2.0.0-beta.220", - "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.220.tgz", - "integrity": "sha512-W5N2Ey+thufUOrs2TFGpEGBGue7ZEhcUXvxcsZlGbrjVa9Y+4rEp68Du4y7yM0hCeSj2GGwiV+uPzkc0CSDE/g==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209", - "@tiptap/pm": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-hard-break": { - "version": "2.0.0-beta.220", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.220.tgz", - "integrity": "sha512-oY3454o53YNFbuokzyGzG4PdMHkIYreY3nrALioZ0SwYeoFNcGA6Zcn4rDRfdp+QvbbiHfeBTR/CpWF13HZYTg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-heading": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.217.tgz", - "integrity": "sha512-v7G/1R0+qmPm0U2sOCOftqZef5yd89tEg1R+KVF78ucFvuLgH50Pldes8lhHEBlfoRShye2Ht54E6JHDP73J5w==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-history": { - "version": "2.0.0-beta.220", - "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.0.0-beta.220.tgz", - "integrity": "sha512-qNL2a9UhnlmCs4y2iQYrfeMB8vEX3bHozBJanHu0PWNQJcj90R5xqorBp/bRcqZdi0kuQfxcTnGHtLUpN/U0TA==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209", - "@tiptap/pm": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-horizontal-rule": { - "version": "2.0.0-beta.220", - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.220.tgz", - "integrity": "sha512-XMIs4R+4BoH5LpIxey513mZuus0XLHqjVayqtf03enmjBTLWzkixvvWLPLw4a47FJL5Q8l4REFHxjNifRzOKkg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209", - "@tiptap/pm": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-image": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.0.0-beta.217.tgz", - "integrity": "sha512-8ibJOicpcqhFeiPLqYOY8SO9sxkz2bemzwhtzRvgkevy/QERe9D0mbAL0qm1gg88LtXy3z2eFUdwD8suq1xsPQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-italic": { - "version": "2.0.0-beta.220", - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.220.tgz", - "integrity": "sha512-aWAgqoR8fql9fJ7T/ZrEqovkEjZXbUpvlvWEvdBDMG3id8ZTGNDpdDKdvI6J/Rl5ZGPIg1TpHJtd+UixheWQsQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-link": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.0.0-beta.217.tgz", - "integrity": "sha512-pC1UnK1OrbW+NDdBdHE8sfmZ+tOpIOsHVAeeX6pg1fkdP/FlbPFFvcvILgJc20dr7DiM6dRqYq16H473G9vyEg==", - "dependencies": { - "linkifyjs": "^3.0.5" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209", - "@tiptap/pm": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-list-item": { - "version": "2.0.0-beta.220", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.220.tgz", - "integrity": "sha512-+O0ivwxPP2l/m9PAowb2ytDT/cM5kwu0s1W5MUsHPIqf+M6ahnl4ESjhWZfDHUzvjqPq6MTbqoQLHbB1KS/N7w==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-ordered-list": { - "version": "2.0.0-beta.220", - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.220.tgz", - "integrity": "sha512-j3DmxJfwmNxFfMnvO7glmGlhYeZSIUnRrKnZu2KkpD6OcGJSh9y/yfnYwcuK80XbzEG/jKKIw0M2yRveOvyVwA==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-paragraph": { - "version": "2.0.0-beta.220", - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.220.tgz", - "integrity": "sha512-ZGCzNGFYV4wa3l1nXtDIaYp7O6f0DrGTSl3alKkDTQe3SOmzXS2HjgWl9yPw8VXpU9W5mMGhXd+nGn/jUk+f/A==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-strike": { - "version": "2.0.0-beta.220", - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.220.tgz", - "integrity": "sha512-cIM2ma6mzk08pijOn+KS3ZoHWaUVsVT+OF3m6xewjwJdC0ILg9nApEOhPFrhbeDcxcPmJMlgBl/xeUrEu1HQMg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-table": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.0.0-beta.217.tgz", - "integrity": "sha512-8PwfNXIRPy1zxZAk0kS+sqFeUE2M6al1y/mA6p0SA9YhSN0iWvjQfmq9Ds52hmRcL2Dv9QmLR97S7WGRmHKcQg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209", - "@tiptap/pm": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-table-cell": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.0.0-beta.217.tgz", - "integrity": "sha512-W5UxsZxQdBms916hHp4giXi6AOkwCEfSaTXfi3FQqxcg/EQnmzMNB82/9BcVqBUaoJrx1dIVm4ploIL+GikG/w==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-table-header": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.0.0-beta.217.tgz", - "integrity": "sha512-oahTLhItvoPzCA9RuGLowZ0ZGro+Yn3+1NefXu/yGlp3twKQyhrwOv3+TqZ21L+8uKGOVLfLgPZnF6oNozEdJQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-table-row": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.0.0-beta.217.tgz", - "integrity": "sha512-6ie3YtnOliIzER4JtVh0T8HQl3Z2gwTBoCOvqoetsoKIk0zNdsai+ZjVjVN4ZiMLFNYm5xnCUfr83usp9kawhQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-text": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.0.0-beta.217.tgz", - "integrity": "sha512-l5PkVhZzVnxbGyMiCMqpLU3ZomJWcZXhuBD0aajry+l85DWc6TK0mhLEAVEU1LoTL8hXFmICoW1KJ1m20Va9dg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-text-align": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.0.0-beta.217.tgz", - "integrity": "sha512-oFr1xkUFJKHGJkFA0CDcfkEA4z3jr1umFRkr6qejKiYqwAUdnn5YrtfdGICJzBEa6/1wOCZz0ODzCnv9XmGDkA==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/extension-typography": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/extension-typography/-/extension-typography-2.0.0-beta.217.tgz", - "integrity": "sha512-xr/KBgdcI3qbTshbY0FCN8BiSRW++MH7yQwvAKlhk47Qt0UcVsRIGc/FfXFD2MhfoZ4vaVrIn5daU4P4p3d+wA==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/pm": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.0.0-beta.217.tgz", - "integrity": "sha512-krEplJli2BbBB3U2c+cxDPw4mpHsUEwOMJCM3fr1GTVv7qAOvsrWt2ndRPOwraPSvmUF3rw9gvoP86eyK2nDmA==", - "dependencies": { - "prosemirror-changeset": "^2.2.0", - "prosemirror-collab": "^1.3.0", - "prosemirror-commands": "^1.3.1", - "prosemirror-dropcursor": "^1.5.0", - "prosemirror-gapcursor": "^1.3.1", - "prosemirror-history": "^1.3.0", - "prosemirror-inputrules": "^1.2.0", - "prosemirror-keymap": "^1.2.0", - "prosemirror-markdown": "^1.10.1", - "prosemirror-menu": "^1.2.1", - "prosemirror-model": "^1.18.1", - "prosemirror-schema-basic": "^1.2.0", - "prosemirror-schema-list": "^1.2.2", - "prosemirror-state": "^1.4.1", - "prosemirror-tables": "^1.3.0", - "prosemirror-trailing-node": "^2.0.2", - "prosemirror-transform": "^1.7.0", - "prosemirror-view": "^1.28.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209" - } - }, - "node_modules/@tiptap/react": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.0.0-beta.217.tgz", - "integrity": "sha512-aNyvte3fuY97SFilldaz36g2D36D86UZSb/jjxTEAhYc6F4pY8O/4sh20fw6zvoeKAW4DTH/r8v0BRc18+Vm3g==", - "dependencies": { - "@tiptap/extension-bubble-menu": "^2.0.0-beta.217", - "@tiptap/extension-floating-menu": "^2.0.0-beta.217" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.0.0-beta.209", - "@tiptap/pm": "^2.0.0-beta.209", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - } - }, - "node_modules/@tiptap/starter-kit": { - "version": "2.0.0-beta.217", - "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.0.0-beta.217.tgz", - "integrity": "sha512-9FPH8lOP0AfRMKm2OUy2OQKEdcIo2ic7MNO2Jtd5vcUz1skfaQPjYLJYHbhkw2iH0ILUOPPbGpZuIlV8uqSu/g==", - "dependencies": { - "@tiptap/core": "^2.0.0-beta.217", - "@tiptap/extension-blockquote": "^2.0.0-beta.217", - "@tiptap/extension-bold": "^2.0.0-beta.217", - "@tiptap/extension-bullet-list": "^2.0.0-beta.217", - "@tiptap/extension-code": "^2.0.0-beta.217", - "@tiptap/extension-code-block": "^2.0.0-beta.217", - "@tiptap/extension-document": "^2.0.0-beta.217", - "@tiptap/extension-dropcursor": "^2.0.0-beta.217", - "@tiptap/extension-gapcursor": "^2.0.0-beta.217", - "@tiptap/extension-hard-break": "^2.0.0-beta.217", - "@tiptap/extension-heading": "^2.0.0-beta.217", - "@tiptap/extension-history": "^2.0.0-beta.217", - "@tiptap/extension-horizontal-rule": "^2.0.0-beta.217", - "@tiptap/extension-italic": "^2.0.0-beta.217", - "@tiptap/extension-list-item": "^2.0.0-beta.217", - "@tiptap/extension-ordered-list": "^2.0.0-beta.217", - "@tiptap/extension-paragraph": "^2.0.0-beta.217", - "@tiptap/extension-strike": "^2.0.0-beta.217", - "@tiptap/extension-text": "^2.0.0-beta.217" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - } - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "optional": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@types/babel-core": { - "version": "6.25.7", - "resolved": "https://registry.npmjs.org/@types/babel-core/-/babel-core-6.25.7.tgz", - "integrity": "sha512-WPnyzNFVRo6bxpr7bcL27qXtNKNQ3iToziNBpibaXHyKGWQA0+tTLt73QQxC/5zzbM544ih6Ni5L5xrck6rGwg==", - "dependencies": { - "@types/babel-generator": "*", - "@types/babel-template": "*", - "@types/babel-traverse": "*", - "@types/babel-types": "*", - "@types/babylon": "*" - } - }, - "node_modules/@types/babel-generator": { - "version": "6.25.5", - "resolved": "https://registry.npmjs.org/@types/babel-generator/-/babel-generator-6.25.5.tgz", - "integrity": "sha512-lhbwMlAy5rfWG+R6l8aPtJdEFX/kcv6LMFIuvUb0i89ehqgD24je9YcB+0fRspQhgJGlEsUImxpw4pQeKS/+8Q==", - "dependencies": { - "@types/babel-types": "*" - } - }, - "node_modules/@types/babel-template": { - "version": "6.25.2", - "resolved": "https://registry.npmjs.org/@types/babel-template/-/babel-template-6.25.2.tgz", - "integrity": "sha512-QKtDQRJmAz3Y1HSxfMl0syIHebMc/NnOeH/8qeD0zjgU2juD0uyC922biMxCy5xjTNvHinigML2l8kxE8eEBmw==", - "dependencies": { - "@types/babel-types": "*", - "@types/babylon": "*" - } - }, - "node_modules/@types/babel-traverse": { - "version": "6.25.7", - "resolved": "https://registry.npmjs.org/@types/babel-traverse/-/babel-traverse-6.25.7.tgz", - "integrity": "sha512-BeQiEGLnVzypzBdsexEpZAHUx+WucOMXW6srEWDkl4SegBlaCy+iBvRO+4vz6EZ+BNQg22G4MCdDdvZxf+jW5A==", - "dependencies": { - "@types/babel-types": "*" - } - }, - "node_modules/@types/babel-types": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.11.tgz", - "integrity": "sha512-pkPtJUUY+Vwv6B1inAz55rQvivClHJxc9aVEPPmaq2cbyeMLCiDpbKpcKyX4LAwpNGi+SHBv0tHv6+0gXv0P2A==" - }, - "node_modules/@types/babylon": { - "version": "6.16.6", - "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.6.tgz", - "integrity": "sha512-G4yqdVlhr6YhzLXFKy5F7HtRBU8Y23+iWy7UKthMq/OSQnL1hbsoeXESQ2LY8zEDlknipDG3nRGhUC9tkwvy/w==", - "dependencies": { - "@types/babel-types": "*" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" - }, - "node_modules/@types/cors": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", - "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" - }, - "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.33", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", - "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "node_modules/@types/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", - "optional": true, - "dependencies": { - "@types/minimatch": "^5.1.2", - "@types/node": "*" - } - }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", - "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/linkify-it": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", - "optional": true - }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", - "optional": true - }, - "node_modules/@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", - "optional": true, - "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" - } - }, - "node_modules/@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", - "optional": true - }, - "node_modules/@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" - }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "optional": true - }, - "node_modules/@types/node": { - "version": "18.15.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", - "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==" - }, - "node_modules/@types/object.omit": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/object.omit/-/object.omit-3.0.0.tgz", - "integrity": "sha512-I27IoPpH250TUzc9FzXd0P1BV/BMJuzqD3jOz98ehf9dQqGkxlq+hO1bIqZGWqCg5bVOy0g4AUVJtnxe0klDmw==" - }, - "node_modules/@types/object.pick": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/object.pick/-/object.pick-1.3.2.tgz", - "integrity": "sha512-sn7L+qQ6RLPdXRoiaE7bZ/Ek+o4uICma/lBFPyJEKDTPTBP1W8u0c4baj3EiS4DiqLs+Hk+KUGvMVJtAw3ePJg==" - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "node_modules/@types/react": { - "version": "18.0.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", - "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==", - "optional": true, - "dependencies": { - "@types/glob": "*", - "@types/node": "*" - } - }, - "node_modules/@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "node_modules/@types/serve-static": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", - "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", - "dependencies": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "node_modules/@types/throttle-debounce": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz", - "integrity": "sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==" - }, - "node_modules/@types/triple-beam": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", - "integrity": "sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==" - }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" - }, - "node_modules/@types/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" - }, - "node_modules/@types/whatwg-url": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", - "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", - "dependencies": { - "@types/node": "*", - "@types/webidl-conversions": "*" - } - }, - "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==", - "dev": true - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "optional": true, - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "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.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/adminjs": { - "version": "6.8.7", - "resolved": "https://registry.npmjs.org/adminjs/-/adminjs-6.8.7.tgz", - "integrity": "sha512-D3jBb+OgD65qlhrO/dW2AYIiDUTNsJThxYYNiaqufl4yZ9IF+zcYffLRvUJ5EDlv00pIMCM5zff5TMQIRqF8bA==", - "dependencies": { - "@adminjs/design-system": "^3.1.8", - "@babel/core": "^7.10.2", - "@babel/parser": "^7.10.2", - "@babel/plugin-transform-runtime": "^7.10.1", - "@babel/polyfill": "^7.10.1", - "@babel/preset-env": "^7.16.4", - "@babel/preset-react": "^7.10.1", - "@babel/preset-typescript": "^7.10.1", - "@babel/register": "^7.10.1", - "@hello-pangea/dnd": "^16.0.0", - "@rollup/plugin-babel": "^5.2.1", - "@rollup/plugin-commonjs": "^22.0.1", - "@rollup/plugin-json": "^4.1.0", - "@rollup/plugin-node-resolve": "^13.3.0", - "@rollup/plugin-replace": "^2.3.3", - "@types/babel-core": "^6.25.7", - "@types/react": "^18.0.8", - "axios": "^0.27.2", - "babel-plugin-styled-components": "^1.11.1", - "commander": "^5.1.0", - "flat": "^5.0.2", - "i18next": "^21.9.2", - "lodash": "^4.17.21", - "ora": "^5.4.1", - "prop-types": "^15.7.2", - "punycode": "^2.1.1", - "react": "^18.1.0", - "react-dom": "^18.1.0", - "react-i18next": "^11.18.6", - "react-is": "^18.1.0", - "react-redux": "^8.0.2", - "react-router": "^6.3.0", - "react-router-dom": "^6.3.0", - "redux": "^4.2.0", - "rollup": "^2.79.1", - "rollup-plugin-terser": "^7.0.2", - "slash": "^3.0.0", - "styled-components": "^5.3.5", - "uuid": "^8.3.2", - "xss": "^1.0.13" - }, - "bin": { - "admin": "cli.js" - } - }, - "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==", - "optional": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", - "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", - "peerDependencies": { - "ajv": "^8.0.1" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "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/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "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/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true - }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "node_modules/async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", - "optional": true, - "dependencies": { - "retry": "0.13.1" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/aws-sdk": { - "version": "2.1334.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1334.0.tgz", - "integrity": "sha512-nJuV8QYY39sI1Q7u7Dsd2XcxDkUG/Z5oGosc41LrTBAHQjPib3rXV06zaL9jS+4yQ9Ko7qon2f/0ZVVuUPJjDA==", - "dependencies": { - "buffer": "4.9.2", - "events": "1.1.1", - "ieee754": "1.1.13", - "jmespath": "0.16.0", - "querystring": "0.2.0", - "sax": "1.2.1", - "url": "0.10.3", - "util": "^0.12.4", - "uuid": "8.0.0", - "xml2js": "0.4.19" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/aws-sdk/node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/aws-sdk/node_modules/uuid": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", - "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-styled-components": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.13.3.tgz", - "integrity": "sha512-meGStRGv+VuKA/q0/jXxrPNWEm4LPfYIqxooDTdmh8kFsP/Ph7jJG5rUPwUPX3QHUvggwdbgdGpo88P/rRYsVw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-module-imports": "^7.15.4", - "babel-plugin-syntax-jsx": "^6.18.0", - "lodash": "^4.17.11" - }, - "peerDependencies": { - "styled-components": ">= 2" - } - }, - "node_modules/babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" - }, - "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" - } - ] - }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", - "optional": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "optional": true - }, - "node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/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/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "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==", - "optional": true - }, - "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.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "dependencies": { - "buffer": "^5.6.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/bson/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "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==" - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001466", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001466.tgz", - "integrity": "sha512-ewtFBSfWjEmxUgNBSZItFSmVtvk9zkwkl1OfRZlKA8slltRN+/C/tuGVrF9styXkN36Yu3+SeJ1qkXxDEyNZ5w==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/case-anything": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz", - "integrity": "sha512-JczJwVrCP0jPKh05McyVsuOg6AYosrB9XWZKbQzXeDAm2ClE/PJE/BcrrQrVyGYH7Jg8V/LDupmyL4kFlVsVFQ==", - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", - "optional": true, - "dependencies": { - "lodash": "^4.17.15" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "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" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/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/ci": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ci/-/ci-2.2.0.tgz", - "integrity": "sha512-lBkEN6XclyW0jnprtFQ+dsbP+9zwmo37Z1cV38h4FSDgI2QzFqwknJnVSvRxK9UXkPC4ZcVOVFyCVrNylTX52Q==", - "bin": { - "ci": "lib/ci.js" - }, - "funding": { - "url": "https://github.com/privatenumber/ci?sponsor=1" - } - }, - "node_modules/classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "devOptional": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", - "dependencies": { - "color": "^3.1.3", - "text-hex": "1.0.x" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "optional": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "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/connect-mongo": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.6.0.tgz", - "integrity": "sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==", - "dependencies": { - "debug": "^4.3.1", - "kruptein": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "mongodb": "^4.1.0" - } - }, - "node_modules/connect-redis": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-6.1.3.tgz", - "integrity": "sha512-aaNluLlAn/3JPxRwdzw7lhvEoU6Enb+d83xnokUNhC9dktqBoawKWL+WuxinxvBLTz6q9vReTnUDnUslaz74aw==", - "engines": { - "node": ">=12" - } - }, - "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/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, - "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", - "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", - "dependencies": { - "cookie": "0.4.1", - "cookie-signature": "1.0.6" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "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/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true - }, - "node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "hasInstallScript": true - }, - "node_modules/core-js-compat": { - "version": "3.29.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.29.1.tgz", - "integrity": "sha512-QmchCua884D8wWskMX8tW5ydINzd8oSJVx38lx/pVkFGqztxt73GYre3pm/hyYq8bPf+MW5In4I/uRShFDsbrA==", - "dependencies": { - "browserslist": "^4.21.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "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==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/crelt": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz", - "integrity": "sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==" - }, - "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "dependencies": { - "tiny-invariant": "^1.0.6" - } - }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", - "engines": { - "node": ">=4" - } - }, - "node_modules/css-to-react-native": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, - "node_modules/cssfilter": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", - "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==" - }, - "node_modules/csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" - }, - "node_modules/dash-get": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/dash-get/-/dash-get-1.0.2.tgz", - "integrity": "sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==" - }, - "node_modules/date-fns": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.15.0.tgz", - "integrity": "sha512-ZCPzAMJZn3rNUvvQIMlXhDr4A+Ar07eLeGsGREoWU19a3Pqf5oYa+ccd+B3F6XVtQY6HANMFdOQ8A+ipFnvJdQ==", - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "node_modules/deepmerge": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", - "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "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/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "dev": true, - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "node_modules/dotenv": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", - "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", - "optional": true, - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.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/electron-to-chromium": { - "version": "1.4.328", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.328.tgz", - "integrity": "sha512-DE9tTy2PNmy1v55AZAO542ui+MLC2cvINMK4P2LXGsJdput/ThVG9t+QGecPuAZZSgC8XoI+Jh9M1OG9IoNSCw==" - }, - "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==", - "devOptional": true - }, - "node_modules/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" - }, - "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/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "optional": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/engine.io": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", - "integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.11.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io-parser": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", - "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", - "optional": true - }, - "node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" - } - }, - "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/escape-regexp": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/escape-regexp/-/escape-regexp-0.0.1.tgz", - "integrity": "sha512-jVgdsYRa7RKxTT6MKNC3gdT+BF0Gfhpel19+HMRZJC2L0PufB0XOBuXBoXj29NKHwuktnAXd1Z1lyiH/8vOTpw==" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "optional": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "optional": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "optional": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "optional": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "optional": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "optional": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", - "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.1", - "@eslint/js": "8.36.0", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.5.0", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.7.0.tgz", - "integrity": "sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA==", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-mocha": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.1.0.tgz", - "integrity": "sha512-xLqqWUF17llsogVOC+8C6/jvQ+4IoOREbN7ZCHuOHuD6cT5cDD4h7f2LgsZuzMAiwswWE21tO7ExaknHVDrSkw==", - "dev": true, - "dependencies": { - "eslint-utils": "^3.0.0", - "rambda": "^7.1.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", - "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "optional": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "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/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express-formidable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/express-formidable/-/express-formidable-1.2.0.tgz", - "integrity": "sha512-w1vXjF3gb50UKTNkFaW8/4rqY4dUrKfZ1sAZzwAF9YxCAgj/29QZsycf71di0GkskrZOAkubk9pvGYfxyAMYiw==", - "dependencies": { - "formidable": "^1.0.17" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/express-rate-limit": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz", - "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==", - "engines": { - "node": ">= 12.9.0" - }, - "peerDependencies": { - "express": "^4 || ^5" - } - }, - "node_modules/express-session": { - "version": "1.17.3", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", - "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", - "dependencies": { - "cookie": "0.4.2", - "cookie-signature": "1.0.6", - "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.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express-session/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/express-session/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/express-validator": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.15.0.tgz", - "integrity": "sha512-r05VYoBL3i2pswuehoFSy+uM8NBuVaY7avp5qrYjQBDzagx2Z5A77FZqPT8/gNLF3HopWkIzaTFaC4JysWXLqg==", - "dependencies": { - "lodash": "^4.17.21", - "validator": "^13.9.0" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/express/node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/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/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/express/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/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "optional": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true - }, - "node_modules/fast-text-encoding": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", - "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", - "optional": true - }, - "node_modules/fast-xml-parser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz", - "integrity": "sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==", - "optional": true, - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - }, - "funding": { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-stream-rotator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", - "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", - "dependencies": { - "moment": "^2.29.1" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "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/finalhandler/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/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/firebase-admin": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.5.0.tgz", - "integrity": "sha512-bBdlYtNvXx8yZGdCd00NrfZl1o1A0aXOw5h8q5PwC8RXikOLNXq8vYtSKW44dj8zIaafVP6jFdcUXZem/LMsHA==", - "dependencies": { - "@fastify/busboy": "^1.1.0", - "@firebase/database-compat": "^0.3.0", - "@firebase/database-types": "^0.10.0", - "@types/node": ">=12.12.47", - "jsonwebtoken": "^9.0.0", - "jwks-rsa": "^3.0.1", - "node-forge": "^1.3.1", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "@google-cloud/firestore": "^6.4.0", - "@google-cloud/storage": "^6.5.2" - } - }, - "node_modules/firebase-admin/node_modules/jsonwebtoken": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", - "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", - "dependencies": { - "jws": "^3.2.2", - "lodash": "^4.17.21", - "ms": "^2.1.1", - "semver": "^7.3.8" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/firebase-admin/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/firebase-admin/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/firebase-admin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/firebase-admin/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/firebase-admin/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/firebase-admin/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/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" - }, - "node_modules/fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" - }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formidable": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", - "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", - "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "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.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.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "optional": true - }, - "node_modules/gaxios": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.0.tgz", - "integrity": "sha512-aezGIjb+/VfsJtIcHGcBSerNEDdfdHeMros+RbYbGpmonKWQCOVOes0LVZhn1lDtIgq55qq0HaxymIoae3Fl/A==", - "optional": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/gcp-metadata": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", - "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", - "optional": true, - "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/generic-pool": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", - "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "devOptional": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "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==", - "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": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/google-auth-library": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", - "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", - "optional": true, - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^5.0.0", - "gcp-metadata": "^5.0.0", - "gtoken": "^6.1.0", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/google-auth-library/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/google-auth-library/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true - }, - "node_modules/google-gax": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.8.tgz", - "integrity": "sha512-bkqxv7YdYP3FGh+dywvgyctj8XM07toJ/JCWwAkmmE15QTt3ieF/f7Hpz7xG85+dGmZtQR8Y+yMo0ENFmhypNA==", - "optional": true, - "dependencies": { - "@grpc/grpc-js": "~1.8.0", - "@grpc/proto-loader": "^0.7.0", - "@types/long": "^4.0.0", - "@types/rimraf": "^3.0.2", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^8.0.2", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^1.0.0", - "protobufjs": "7.2.2", - "protobufjs-cli": "1.1.1", - "retry-request": "^5.0.0" - }, - "bin": { - "compileProtos": "build/tools/compileProtos.js", - "minifyProtoJson": "build/tools/minify.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/google-p12-pem": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", - "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", - "optional": true, - "dependencies": { - "node-forge": "^1.3.1" - }, - "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "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/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "optional": true - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" - }, - "node_modules/gtoken": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", - "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", - "optional": true, - "dependencies": { - "gaxios": "^5.0.1", - "google-p12-pem": "^4.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/gud": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", - "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "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==", - "engines": { - "node": ">=4" - } - }, - "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-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/html-parse-stringify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", - "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", - "dependencies": { - "void-elements": "3.1.0" - } - }, - "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/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" - }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "optional": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "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==", - "optional": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/i18next": { - "version": "21.10.0", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.10.0.tgz", - "integrity": "sha512-YeuIBmFsGjUfO3qBmMOc0rQaun4mIpGKET5WDwvu8lU7gvwpcariZLNtL0Fzj+zazcHUrlXHiptcFhBMFaxzfg==", - "funding": [ - { - "type": "individual", - "url": "https://locize.com" - }, - { - "type": "individual", - "url": "https://locize.com/i18next.html" - }, - { - "type": "individual", - "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" - } - ], - "dependencies": { - "@babel/runtime": "^7.17.2" - } - }, - "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.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "engines": { - "node": ">= 4" - } - }, - "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/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "engines": { - "node": ">=0.8.19" - } - }, - "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==", - "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/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" - }, - "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-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "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-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "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==", - "devOptional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" - }, - "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/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-stream-ended": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", - "optional": true - }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jmespath": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", - "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/jose": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.13.1.tgz", - "integrity": "sha512-MSJQC5vXco5Br38mzaQKiq9mwt7lwj2eXpgpRyQYNHYt2lq1PjkWa7DLXX0WVcQLE9HhMh3jPiufS7fhJf+CLQ==", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/js2xmlparser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", - "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", - "optional": true, - "dependencies": { - "xmlcreate": "^2.0.4" - } - }, - "node_modules/jsdoc": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", - "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", - "optional": true, - "dependencies": { - "@babel/parser": "^7.20.15", - "@jsdoc/salty": "^0.2.1", - "@types/markdown-it": "^12.2.3", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^3.0.0", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "underscore": "~1.13.2" - }, - "bin": { - "jsdoc": "jsdoc.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/jsdoc/node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "optional": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/jsdoc/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jsdoc/node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "optional": true, - "dependencies": { - "uc.micro": "^1.0.1" - } - }, - "node_modules/jsdoc/node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", - "optional": true, - "dependencies": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "bin": { - "markdown-it": "bin/markdown-it.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "optional": true, - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=4", - "npm": ">=1.4.28" - } - }, - "node_modules/jsonwebtoken/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/jsonwebtoken/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/jsonwebtoken/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/jw-paginate": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/jw-paginate/-/jw-paginate-1.0.4.tgz", - "integrity": "sha512-W0bv782exgCoynUL/egbRpaYwf/r6T6e02H870H5u3hfSgEYrxgz5POwmFF5aApS6iPi6yhZ0VF8IbafNFsntA==" - }, - "node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "optional": true, - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jwks-rsa": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", - "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", - "dependencies": { - "@types/express": "^4.17.14", - "@types/jsonwebtoken": "^9.0.0", - "debug": "^4.3.4", - "jose": "^4.10.4", - "limiter": "^1.1.5", - "lru-memoizer": "^2.1.4" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "optional": true, - "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/kareem": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", - "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "optional": true, - "dependencies": { - "graceful-fs": "^4.1.9" - } - }, - "node_modules/kruptein": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.6.tgz", - "integrity": "sha512-EQJjTwAJfQkC4NfdQdo3HXM2a9pmBm8oidzH270cYu1MbgXPNPMJuldN7OPX+qdhPO5rw4X3/iKz0BFBfkXGKA==", - "dependencies": { - "asn1.js": "^5.4.1" - }, - "engines": { - "node": ">8" - } - }, - "node_modules/kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/limiter": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", - "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "node_modules/linkify-it": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", - "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", - "dependencies": { - "uc.micro": "^1.0.1" - } - }, - "node_modules/linkifyjs": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-3.0.5.tgz", - "integrity": "sha512-1Y9XQH65eQKA9p2xtk+zxvnTeQBG7rdAXSkUG97DmuI/Xhji9uaUzaWxRj6rf9YC0v8KKHkxav7tnLX82Sz5Fg==" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "optional": true - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" - }, - "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.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "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/lodash.reduce": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==" - }, - "node_modules/lodash.startswith": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz", - "integrity": "sha512-XClYR1h4/fJ7H+mmCKppbiBmljN/nGs73iq2SjCT9SF4CBPoUHzLvWmH1GtZMhMBZSiRkHXfeA2RY1eIlJ75ww==" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logform": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.5.1.tgz", - "integrity": "sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==", - "dependencies": { - "@colors/colors": "1.5.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - } - }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "optional": true - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/lru-memoizer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz", - "integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==", - "dependencies": { - "lodash.clonedeep": "^4.5.0", - "lru-cache": "~4.0.0" - } - }, - "node_modules/lru-memoizer/node_modules/lru-cache": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", - "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", - "dependencies": { - "pseudomap": "^1.0.1", - "yallist": "^2.0.0" - } - }, - "node_modules/lru-memoizer/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "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==" - }, - "node_modules/markdown-it": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", - "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", - "dependencies": { - "argparse": "^2.0.1", - "entities": "~3.0.1", - "linkify-it": "^4.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "bin": { - "markdown-it": "bin/markdown-it.js" - } - }, - "node_modules/markdown-it-anchor": { - "version": "8.6.7", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", - "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", - "optional": true, - "peerDependencies": { - "@types/markdown-it": "*", - "markdown-it": "*" - } - }, - "node_modules/marked": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", - "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", - "optional": true, - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" - }, - "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/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" - }, - "node_modules/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true - }, - "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/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "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": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "optional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "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/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "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==", - "optional": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "optional": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/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==", - "dev": true - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "engines": { - "node": "*" - } - }, - "node_modules/mongodb": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.14.0.tgz", - "integrity": "sha512-coGKkWXIBczZPr284tYKFLg+KbGPPLlSbdgfKAb6QqCFt5bo5VFZ50O3FFzsw4rnkqjwT6D8Qcoo9nshYKM7Mg==", - "dependencies": { - "bson": "^4.7.0", - "mongodb-connection-string-url": "^2.5.4", - "socks": "^2.7.1" - }, - "engines": { - "node": ">=12.9.0" - }, - "optionalDependencies": { - "@aws-sdk/credential-providers": "^3.186.0", - "saslprep": "^1.0.3" - } - }, - "node_modules/mongodb-connection-string-url": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", - "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", - "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "node_modules/mongoose": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.10.3.tgz", - "integrity": "sha512-fZ3pIlQn7lM632r1l4qiU58lKrJ+FufKVG8TNeRXSChAeu9alCl5KoQ9bLw4jnQNYevSq9o+sqZmFDHP+EVW3g==", - "dependencies": { - "bson": "^4.7.0", - "kareem": "2.5.1", - "mongodb": "4.14.0", - "mpath": "0.9.0", - "mquery": "4.0.3", - "ms": "2.1.3", - "sift": "16.0.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, - "node_modules/mongoose/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/mpath": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", - "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mquery": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz", - "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==", - "dependencies": { - "debug": "4.x" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "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==" - }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" - }, - "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-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "optional": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "optional": true - }, - "node_modules/node-fetch/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==", - "optional": true - }, - "node_modules/node-fetch/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==", - "optional": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-mocks-http": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/node-mocks-http/-/node-mocks-http-1.12.2.tgz", - "integrity": "sha512-xhWwC0dh35R9rf0j3bRZXuISXdHxxtMx0ywZQBwjrg3yl7KpRETzogfeCamUIjltpn0Fxvs/ZhGJul1vPLrdJQ==", - "dependencies": { - "accepts": "^1.3.7", - "content-disposition": "^0.5.3", - "depd": "^1.1.0", - "fresh": "^0.5.2", - "merge-descriptors": "^1.0.1", - "methods": "^1.1.2", - "mime": "^1.3.4", - "parseurl": "^1.3.3", - "range-parser": "^1.2.0", - "type-is": "^1.6.18" - }, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/node-mocks-http/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-mocks-http/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/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" - }, - "node_modules/nodemon": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.21.tgz", - "integrity": "sha512-djN/n2549DUtY33S7o1djRCd7dEm0kBnj9c7S9XVXqRUbuggN1MZH/Nqa+5RFQr63Fbefq37nFXAE9VU86yL1A==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/nodemon/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, - "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/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-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "optional": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.omit": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-3.0.0.tgz", - "integrity": "sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ==", - "dependencies": { - "is-extendable": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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/one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "dependencies": { - "fn.name": "1.x.x" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/ora/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/orderedmap": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.0.tgz", - "integrity": "sha512-/pIFexOm6S70EPdznemIz3BQZoJ4VTFrhqzu0ACBqBgeLsLxq8e6Jim63ImIfwW/zAD1AlXpRMlOv3aghmo4dA==" - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "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-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "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==" - }, - "node_modules/path-to-regexp": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "engines": { - "node": ">=6" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/polished": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/polished/-/polished-3.7.2.tgz", - "integrity": "sha512-pQKtpZGmsZrW8UUpQMAnR7s3ppHeMQVNyMDKtUyKwuvDmklzcEyM5Kllb3JyE/sE/x7arDmyd35i+4vp99H6sQ==", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/prosemirror-changeset": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.0.tgz", - "integrity": "sha512-QM7ohGtkpVpwVGmFb8wqVhaz9+6IUXcIQBGZ81YNAKYuHiFJ1ShvSzab4pKqTinJhwciZbrtBEk/2WsqSt2PYg==", - "dependencies": { - "prosemirror-transform": "^1.0.0" - } - }, - "node_modules/prosemirror-collab": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.0.tgz", - "integrity": "sha512-+S/IJ69G2cUu2IM5b3PBekuxs94HO1CxJIWOFrLQXUaUDKL/JfBx+QcH31ldBlBXyDEUl+k3Vltfi1E1MKp2mA==", - "dependencies": { - "prosemirror-state": "^1.0.0" - } - }, - "node_modules/prosemirror-commands": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.5.1.tgz", - "integrity": "sha512-ga1ga/RkbzxfAvb6iEXYmrEpekn5NCwTb8w1dr/gmhSoaGcQ0VPuCzOn5qDEpC45ql2oDkKoKQbRxLJwKLpMTQ==", - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" - } - }, - "node_modules/prosemirror-dropcursor": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.7.1.tgz", - "integrity": "sha512-GmWk9bAwhfHwA8xmJhBFjPcebxUG9zAPYtqpIr7NTDigWZZEJCgUYyUQeqgyscLr8ZHoh9aeprX9kW7BihUT+w==", - "dependencies": { - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.1.0", - "prosemirror-view": "^1.1.0" - } - }, - "node_modules/prosemirror-gapcursor": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.1.tgz", - "integrity": "sha512-GKTeE7ZoMsx5uVfc51/ouwMFPq0o8YrZ7Hx4jTF4EeGbXxBveUV8CGv46mSHuBBeXGmvu50guoV2kSnOeZZnUA==", - "dependencies": { - "prosemirror-keymap": "^1.0.0", - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-view": "^1.0.0" - } - }, - "node_modules/prosemirror-history": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.3.0.tgz", - "integrity": "sha512-qo/9Wn4B/Bq89/YD+eNWFbAytu6dmIM85EhID+fz9Jcl9+DfGEo8TTSrRhP15+fFEoaPqpHSxlvSzSEbmlxlUA==", - "dependencies": { - "prosemirror-state": "^1.2.2", - "prosemirror-transform": "^1.0.0", - "rope-sequence": "^1.3.0" - } - }, - "node_modules/prosemirror-inputrules": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.2.0.tgz", - "integrity": "sha512-eAW/M/NTSSzpCOxfR8Abw6OagdG0MiDAiWHQMQveIsZtoKVYzm0AflSPq/ymqJd56/Su1YPbwy9lM13wgHOFmQ==", - "dependencies": { - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" - } - }, - "node_modules/prosemirror-keymap": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.1.tgz", - "integrity": "sha512-kVK6WGC+83LZwuSJnuCb9PsADQnFZllt94qPP3Rx/vLcOUV65+IbBeH2nS5cFggPyEVJhGkGrgYFRrG250WhHQ==", - "dependencies": { - "prosemirror-state": "^1.0.0", - "w3c-keyname": "^2.2.0" - } - }, - "node_modules/prosemirror-markdown": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.10.1.tgz", - "integrity": "sha512-s7iaTLiX+qO5z8kF2NcMmy2T7mIlxzkS4Sp3vTKSYChPtbMpg6YxFkU0Y06rUg2WtKlvBu7v1bXzlGBkfjUWAA==", - "dependencies": { - "markdown-it": "^13.0.1", - "prosemirror-model": "^1.0.0" - } - }, - "node_modules/prosemirror-menu": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.1.tgz", - "integrity": "sha512-sBirXxVfHalZO4f1ZS63WzewINK4182+7dOmoMeBkqYO8wqMBvBS7wQuwVOHnkMWPEh0+N0LJ856KYUN+vFkmQ==", - "dependencies": { - "crelt": "^1.0.0", - "prosemirror-commands": "^1.0.0", - "prosemirror-history": "^1.0.0", - "prosemirror-state": "^1.0.0" - } - }, - "node_modules/prosemirror-model": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.19.0.tgz", - "integrity": "sha512-/CvFGJnwc41EJSfDkQLly1cAJJJmBpZwwUJtwZPTjY2RqZJfM8HVbCreOY/jti8wTRbVyjagcylyGoeJH/g/3w==", - "dependencies": { - "orderedmap": "^2.0.0" - } - }, - "node_modules/prosemirror-schema-basic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.1.tgz", - "integrity": "sha512-vYBdIHsYKSDIqYmPBC7lnwk9DsKn8PnVqK97pMYP5MLEDFqWIX75JiaJTzndBii4bRuNqhC2UfDOfM3FKhlBHg==", - "dependencies": { - "prosemirror-model": "^1.19.0" - } - }, - "node_modules/prosemirror-schema-list": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.2.2.tgz", - "integrity": "sha512-rd0pqSDp86p0MUMKG903g3I9VmElFkQpkZ2iOd3EOVg1vo5Cst51rAsoE+5IPy0LPXq64eGcCYlW1+JPNxOj2w==", - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" - } - }, - "node_modules/prosemirror-state": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.2.tgz", - "integrity": "sha512-puuzLD2mz/oTdfgd8msFbe0A42j5eNudKAAPDB0+QJRw8cO1ygjLmhLrg9RvDpf87Dkd6D4t93qdef00KKNacQ==", - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-transform": "^1.0.0", - "prosemirror-view": "^1.27.0" - } - }, - "node_modules/prosemirror-tables": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.3.2.tgz", - "integrity": "sha512-/9JTeN6s58Zq66HXaxP6uf8PAmc7XXKZFPlOGVtLvxEd6xBP6WtzaJB9wBjiGUzwbdhdMEy7V62yuHqk/3VrnQ==", - "dependencies": { - "prosemirror-keymap": "^1.1.2", - "prosemirror-model": "^1.8.1", - "prosemirror-state": "^1.3.1", - "prosemirror-transform": "^1.2.1", - "prosemirror-view": "^1.13.3" - } - }, - "node_modules/prosemirror-trailing-node": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.3.tgz", - "integrity": "sha512-lGrjMrn97KWkjQSW/FjdvnhJmqFACmQIyr6lKYApvHitDnKsCoZz6XzrHB7RZYHni/0NxQmZ01p/2vyK2SkvaA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@remirror/core-constants": "^2.0.0", - "@remirror/core-helpers": "^2.0.1", - "escape-string-regexp": "^4.0.0" - }, - "peerDependencies": { - "prosemirror-model": "^1", - "prosemirror-state": "^1", - "prosemirror-view": "^1" - } - }, - "node_modules/prosemirror-trailing-node/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/prosemirror-transform": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.7.1.tgz", - "integrity": "sha512-VteoifAfpt46z0yEt6Fc73A5OID9t/y2QIeR5MgxEwTuitadEunD/V0c9jQW8ziT8pbFM54uTzRLJ/nLuQjMxg==", - "dependencies": { - "prosemirror-model": "^1.0.0" - } - }, - "node_modules/prosemirror-view": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.30.2.tgz", - "integrity": "sha512-nTNzZvalQf9kHeEyO407LiV6DoOs/pXsid88UqW9Vvybo4ozJW2PJhkfZUxCUF1hR/9vJLdhxX84wuw9P9HsXA==", - "dependencies": { - "prosemirror-model": "^1.16.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.1.0" - } - }, - "node_modules/proto3-json-serializer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.0.tgz", - "integrity": "sha512-SjXwUWe/vANGs/mJJTbw5++7U67nwsymg7qsoPtw6GiXqw3kUy8ByojrlEdVE2efxAdKreX8WkDafxvYW95ZQg==", - "optional": true, - "dependencies": { - "protobufjs": "^7.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/protobufjs": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.2.tgz", - "integrity": "sha512-++PrQIjrom+bFDPpfmqXfAGSQs40116JRrqqyf53dymUMvvb5d/LMRyicRoF1AUKoXVS1/IgJXlEgcpr4gTF3Q==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/protobufjs-cli": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.1.tgz", - "integrity": "sha512-VPWMgIcRNyQwWUv8OLPyGQ/0lQY/QTQAVN5fh+XzfDwsVw1FZ2L3DM/bcBf8WPiRz2tNpaov9lPZfNcmNo6LXA==", - "optional": true, - "dependencies": { - "chalk": "^4.0.0", - "escodegen": "^1.13.0", - "espree": "^9.0.0", - "estraverse": "^5.1.0", - "glob": "^8.0.0", - "jsdoc": "^4.0.0", - "minimist": "^1.2.0", - "semver": "^7.1.2", - "tmp": "^0.2.1", - "uglify-js": "^3.7.7" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "protobufjs": "^7.0.0" - } - }, - "node_modules/protobufjs-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/protobufjs-cli/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "optional": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/protobufjs-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "optional": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/protobufjs-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "optional": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/protobufjs-cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "optional": true - }, - "node_modules/protobufjs-cli/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "optional": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/protobufjs-cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/protobufjs-cli/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/protobufjs-cli/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "optional": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/protobufjs-cli/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "optional": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/protobufjs-cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "optional": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protobufjs-cli/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true - }, - "node_modules/protobufjs/node_modules/long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", - "optional": true - }, - "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/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" - }, - "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/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "engines": { - "node": ">=6" - } - }, - "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/querystring": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", - "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "engines": { - "node": ">=0.4.x" - } - }, - "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" - } - ] - }, - "node_modules/raf-schd": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", - "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" - }, - "node_modules/rambda": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", - "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", - "dev": true - }, - "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/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "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.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-currency-input-field": { - "version": "3.6.10", - "resolved": "https://registry.npmjs.org/react-currency-input-field/-/react-currency-input-field-3.6.10.tgz", - "integrity": "sha512-KRAJJaLujarBTLlEVbznsUxQ56+Qyqwoe5w9DnGxmsGnHv4ycQRpRkuuCDfF9BcXHmegzsOXesfIGpW7Cw9mTQ==", - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-datepicker": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.10.0.tgz", - "integrity": "sha512-6IfBCZyWj54ZZGLmEZJ9c4Yph0s9MVfEGDC2evOvf9AmVz+RRcfP2Czqad88Ff9wREbcbqa4dk7IFYeXF1d3Ag==", - "dependencies": { - "@popperjs/core": "^2.9.2", - "classnames": "^2.2.6", - "date-fns": "^2.24.0", - "prop-types": "^15.7.2", - "react-onclickoutside": "^6.12.2", - "react-popper": "^2.3.0" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17 || ^18", - "react-dom": "^16.9.0 || ^17 || ^18" - } - }, - "node_modules/react-datepicker/node_modules/date-fns": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", - "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/react-fast-compare": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", - "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" - }, - "node_modules/react-i18next": { - "version": "11.18.6", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.6.tgz", - "integrity": "sha512-yHb2F9BiT0lqoQDt8loZ5gWP331GwctHz9tYQ8A2EIEUu+CcEdjBLQWli1USG3RdWQt3W+jqQLg/d4rrQR96LA==", - "dependencies": { - "@babel/runtime": "^7.14.5", - "html-parse-stringify": "^3.0.1" - }, - "peerDependencies": { - "i18next": ">= 19.0.0", - "react": ">= 16.8.0" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, - "node_modules/react-onclickoutside": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.12.2.tgz", - "integrity": "sha512-NMXGa223OnsrGVp5dJHkuKxQ4czdLmXSp5jSV9OqiCky9LOpPATn3vLldc+q5fK3gKbEHvr7J1u0yhBh/xYkpA==", - "funding": { - "type": "individual", - "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md" - }, - "peerDependencies": { - "react": "^15.5.x || ^16.x || ^17.x || ^18.x", - "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x" - } - }, - "node_modules/react-phone-input-2": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.15.1.tgz", - "integrity": "sha512-W03abwhXcwUoq+vUFvC6ch2+LJYMN8qSOiO889UH6S7SyMCQvox/LF3QWt+cZagZrRdi5z2ON3omnjoCUmlaYw==", - "dependencies": { - "classnames": "^2.2.6", - "lodash.debounce": "^4.0.8", - "lodash.memoize": "^4.1.2", - "lodash.reduce": "^4.6.0", - "lodash.startswith": "^4.2.1", - "prop-types": "^15.7.2" - }, - "peerDependencies": { - "react": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0", - "react-dom": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0" - } - }, - "node_modules/react-popper": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", - "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", - "dependencies": { - "react-fast-compare": "^3.0.1", - "warning": "^4.0.2" - }, - "peerDependencies": { - "@popperjs/core": "^2.0.0", - "react": "^16.8.0 || ^17 || ^18", - "react-dom": "^16.8.0 || ^17 || ^18" - } - }, - "node_modules/react-redux": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", - "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", - "dependencies": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" - }, - "peerDependencies": { - "@types/react": "^16.8 || ^17.0 || ^18.0", - "@types/react-dom": "^16.8 || ^17.0 || ^18.0", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0", - "react-native": ">=0.59", - "redux": "^4" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, - "node_modules/react-router": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.9.0.tgz", - "integrity": "sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==", - "dependencies": { - "@remix-run/router": "1.4.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.9.0.tgz", - "integrity": "sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==", - "dependencies": { - "@remix-run/router": "1.4.0", - "react-router": "6.9.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/react-select": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.0.tgz", - "integrity": "sha512-lJGiMxCa3cqnUr2Jjtg9YHsaytiZqeNOKeibv6WF5zbK/fPegZ1hg3y/9P1RZVLhqBTs0PfqQLKuAACednYGhQ==", - "dependencies": { - "@babel/runtime": "^7.12.0", - "@emotion/cache": "^11.4.0", - "@emotion/react": "^11.8.1", - "@floating-ui/dom": "^1.0.1", - "@types/react-transition-group": "^4.4.0", - "memoize-one": "^6.0.0", - "prop-types": "^15.6.0", - "react-transition-group": "^4.3.0", - "use-isomorphic-layout-effect": "^1.1.2" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-text-mask": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/react-text-mask/-/react-text-mask-5.5.0.tgz", - "integrity": "sha512-SLJlJQxa0uonMXsnXRpv5abIepGmHz77ylQcra0GNd7Jtk4Wj2Mtp85uGQHv1avba2uI8ZvRpIEQPpJKsqRGYw==", - "dependencies": { - "prop-types": "^15.5.6" - }, - "peerDependencies": { - "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, - "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/redis": { - "version": "4.6.5", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.5.tgz", - "integrity": "sha512-O0OWA36gDQbswOdUuAhRL6mTZpHFN525HlgZgDaVNgCJIAZR3ya06NTESb0R+TUZ+BFaDpz6NnnVvoMx9meUFg==", - "dependencies": { - "@redis/bloom": "1.2.0", - "@redis/client": "1.5.6", - "@redis/graph": "1.1.0", - "@redis/json": "1.0.4", - "@redis/search": "1.1.2", - "@redis/time-series": "1.0.4" - } - }, - "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "node_modules/regenerator-transform": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "dependencies": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requizzle": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", - "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", - "optional": true, - "dependencies": { - "lodash": "^4.17.21" - } - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/response-time": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.2.tgz", - "integrity": "sha512-MUIDaDQf+CVqflfTdQ5yam+aYCkXj1PY8fjlPDQ6ppxJlmgZb864pHtA750mayywNg8tx4rS7qH9JXd/OF+3gw==", - "dependencies": { - "depd": "~1.1.0", - "on-headers": "~1.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/response-time/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "optional": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/retry-request": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", - "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", - "optional": true, - "dependencies": { - "debug": "^4.1.1", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/rollup-plugin-terser": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", - "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", - "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "jest-worker": "^26.2.1", - "serialize-javascript": "^4.0.0", - "terser": "^5.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0" - } - }, - "node_modules/rollup-plugin-terser/node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/rope-sequence": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.3.tgz", - "integrity": "sha512-85aZYCxweiD5J8yTEbw+E6A27zSnLPNDL0WfPdw3YYodq7WjnTKo0q4dtyQ2gz23iPT8Q9CUyJtAaUNcTxRf5Q==" - }, - "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" - } - ], - "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/safe-stable-stringify": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.2.tgz", - "integrity": "sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==", - "engines": { - "node": ">=10" - } - }, - "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/saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "dependencies": { - "sparse-bitfield": "^3.0.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" - }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "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/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/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/send/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/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/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "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/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sift": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", - "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" - }, - "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-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, - "node_modules/simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", - "dev": true, - "dependencies": { - "semver": "~7.0.0" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socket.io": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", - "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.2", - "engine.io": "~6.4.1", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", - "dependencies": { - "ws": "~8.11.0" - } - }, - "node_modules/socket.io-parser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", - "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", - "dependencies": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, - "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==", - "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==", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead" - }, - "node_modules/sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "optional": true, - "dependencies": { - "memory-pager": "^1.0.2" - } - }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "engines": { - "node": "*" - } - }, - "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-events": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", - "optional": true, - "dependencies": { - "stubs": "^3.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "optional": true - }, - "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==", - "devOptional": true, - "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-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "optional": true - }, - "node_modules/stubs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", - "optional": true - }, - "node_modules/styled-components": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", - "integrity": "sha512-Aj3kb13B75DQBo2oRwRa/APdB5rSmwUfN5exyarpX+x/tlM/rwZA2vVk2vQgVSP6WKaZJHWwiFrzgHt+CLtB4A==", - "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^1.1.0", - "@emotion/stylis": "^0.8.4", - "@emotion/unitless": "^0.7.4", - "babel-plugin-styled-components": ">= 1.12.0", - "css-to-react-native": "^3.0.0", - "hoist-non-react-statics": "^3.0.0", - "shallowequal": "^1.1.0", - "supports-color": "^5.5.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0", - "react-is": ">= 16.8.0" - } - }, - "node_modules/styled-components/node_modules/@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" - }, - "node_modules/styled-system": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/styled-system/-/styled-system-5.1.5.tgz", - "integrity": "sha512-7VoD0o2R3RKzOzPK0jYrVnS8iJdfkKsQJNiLRDjikOpQVqQHns/DXWaPZOH4tIKkhAT7I6wIsy9FWTWh2X3q+A==", - "dependencies": { - "@styled-system/background": "^5.1.2", - "@styled-system/border": "^5.1.5", - "@styled-system/color": "^5.1.2", - "@styled-system/core": "^5.1.2", - "@styled-system/flexbox": "^5.1.2", - "@styled-system/grid": "^5.1.2", - "@styled-system/layout": "^5.1.2", - "@styled-system/position": "^5.1.2", - "@styled-system/shadow": "^5.1.2", - "@styled-system/space": "^5.1.2", - "@styled-system/typography": "^5.1.2", - "@styled-system/variant": "^5.1.5", - "object-assign": "^4.1.1" - } - }, - "node_modules/stylis": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", - "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" - }, - "node_modules/superagent": { - "version": "8.0.9", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.9.tgz", - "integrity": "sha512-4C7Bh5pyHTvU33KpZgwrNKh/VQnvgtCSqPRfJAUdmrtSYePVzVg4E4OzsrbkhJj9O7SO6Bnv75K/F8XVZT8YHA==", - "dev": true, - "dependencies": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.4", - "debug": "^4.3.4", - "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^2.1.2", - "methods": "^1.1.2", - "mime": "2.6.0", - "qs": "^6.11.0", - "semver": "^7.3.8" - }, - "engines": { - "node": ">=6.4.0 <13 || >=14" - } - }, - "node_modules/superagent/node_modules/formidable": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", - "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", - "dev": true, - "dependencies": { - "dezalgo": "^1.0.4", - "hexoid": "^1.0.0", - "once": "^1.4.0", - "qs": "^6.11.0" - }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/superagent/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/superagent/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/superagent/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/superagent/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/supertest": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz", - "integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==", - "dev": true, - "dependencies": { - "methods": "^1.1.2", - "superagent": "^8.0.5" - }, - "engines": { - "node": ">=6.4.0" - } - }, - "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==", - "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==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/swagger-ui-dist": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.18.1.tgz", - "integrity": "sha512-n7AT4wzKIPpHy/BGflJOepGMrbY/7Cd5yVd9ptVczaJGAKScbVJrZxFbAE2ZSZa8KmqdQ0+pOs3/5mWY5tSMZQ==" - }, - "node_modules/swagger-ui-express": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.2.tgz", - "integrity": "sha512-MHIOaq9JrTTB3ygUJD+08PbjM5Tt/q7x80yz9VTFIatw8j5uIWKcr90S0h5NLMzFEDC6+eVprtoeA5MDZXCUKQ==", - "dependencies": { - "swagger-ui-dist": ">=4.11.0" - }, - "engines": { - "node": ">= v0.10.32" - }, - "peerDependencies": { - "express": ">=4.0.0" - } - }, - "node_modules/teeny-request": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.3.tgz", - "integrity": "sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww==", - "optional": true, - "dependencies": { - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/teeny-request/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/terser": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.6.tgz", - "integrity": "sha512-IBZ+ZQIA9sMaXmRZCUMDjNH0D5AQQfdn4WUjHL0+1lF4TP1IHRJbrhb6fNaXWikrYQTSkb7SLxkeXAiy1p7mbg==", - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/text-decoding": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", - "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" - }, - "node_modules/text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" - }, - "node_modules/text-mask-addons": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/text-mask-addons/-/text-mask-addons-3.8.0.tgz", - "integrity": "sha512-VSZSdc/tKn4zGxgpJ+uNBzoW1t472AoAFIlbw1K7hSNXz0DfSBYDJNRxLqgxOfWw1BY2z6DQpm7g0sYZn5qLpg==" - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" - }, - "node_modules/throttle-debounce": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", - "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/tiny-invariant": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", - "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" - }, - "node_modules/tippy.js": { - "version": "6.3.7", - "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", - "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", - "dependencies": { - "@popperjs/core": "^2.9.0" - } - }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "optional": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "dependencies": { - "nopt": "~1.0.10" - }, - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/triple-beam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", - "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" - }, - "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" - }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "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/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "node_modules/underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", - "optional": true - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "engines": { - "node": ">=4" - } - }, - "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/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", - "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" - }, - "node_modules/url/node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/use-isomorphic-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-memo-one": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", - "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "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": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/validator": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", - "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==", - "engines": { - "node": ">= 0.10" - } - }, - "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/void-elements": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", - "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/w3c-keyname": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", - "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==" - }, - "node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/winston": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", - "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", - "dependencies": { - "@colors/colors": "1.5.0", - "@dabh/diagnostics": "^2.0.2", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.4.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.5.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston-daily-rotate-file": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.7.1.tgz", - "integrity": "sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==", - "dependencies": { - "file-stream-rotator": "^0.6.1", - "object-hash": "^2.0.1", - "triple-beam": "^1.3.0", - "winston-transport": "^4.4.0" - }, - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "winston": "^3" - } - }, - "node_modules/winston-daily-rotate-file/node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/winston-transport": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", - "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", - "dependencies": { - "logform": "^2.3.2", - "readable-stream": "^3.6.0", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 6.4.0" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "devOptional": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true - }, - "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/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" - } - }, - "node_modules/xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/xmlcreate": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", - "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", - "optional": true - }, - "node_modules/xss": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz", - "integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==", - "dependencies": { - "commander": "^2.20.3", - "cssfilter": "0.0.10" - }, - "bin": { - "xss": "bin/xss" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/xss/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "devOptional": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "devOptional": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "devOptional": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, "dependencies": { "@adminjs/design-system": { "version": "3.1.8", @@ -14173,8 +2239,7 @@ "@emotion/use-insertion-effect-with-fallbacks": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", - "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", - "requires": {} + "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==" }, "@emotion/utils": { "version": "1.2.0", @@ -14190,6 +2255,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.2.0.tgz", "integrity": "sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==", + "dev": true, "requires": { "eslint-visitor-keys": "^3.3.0" } @@ -14197,12 +2263,14 @@ "@eslint-community/regexpp": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz", - "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==" + "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==", + "dev": true }, "@eslint/eslintrc": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", + "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -14219,6 +2287,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -14230,6 +2299,7 @@ "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, "requires": { "type-fest": "^0.20.2" } @@ -14237,19 +2307,22 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true } } }, "@eslint/js": { "version": "8.36.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", - "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==" + "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", + "dev": true }, "@fastify/busboy": { "version": "1.2.1", @@ -14442,6 +2515,7 @@ "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -14451,12 +2525,14 @@ "@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true }, "@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true }, "@hypnosphi/create-react-context": { "version": "0.3.1", @@ -14539,6 +2615,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -14547,12 +2624,14 @@ "@nodelib/fs.stat": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true }, "@nodelib/fs.walk": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -14630,8 +2709,7 @@ "@redis/bloom": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", - "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", - "requires": {} + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==" }, "@redis/client": { "version": "1.5.6", @@ -14653,26 +2731,22 @@ "@redis/graph": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz", - "integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==", - "requires": {} + "integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==" }, "@redis/json": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz", - "integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==", - "requires": {} + "integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==" }, "@redis/search": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.2.tgz", - "integrity": "sha512-/cMfstG/fOh/SsE+4/BQGeuH/JJloeWuH+qJzM8dbxuWvdWibWAOAHHCZTMPhV3xIlH4/cUEIA8OV5QnYpaVoA==", - "requires": {} + "integrity": "sha512-/cMfstG/fOh/SsE+4/BQGeuH/JJloeWuH+qJzM8dbxuWvdWibWAOAHHCZTMPhV3xIlH4/cUEIA8OV5QnYpaVoA==" }, "@redis/time-series": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz", - "integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==", - "requires": {} + "integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==" }, "@remirror/core-constants": { "version": "2.0.0", @@ -14897,20 +2971,17 @@ "@tiptap/core": { "version": "2.0.0-beta.217", "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.0-beta.217.tgz", - "integrity": "sha512-Vifwcg5SglkVjEmtFbnwHOKWU4UUenOhe7ke5fqGhh7FNfGkccu6sK8W1JTDbG4ARWZ1b/632kQ51YE+WuPe7g==", - "requires": {} + "integrity": "sha512-Vifwcg5SglkVjEmtFbnwHOKWU4UUenOhe7ke5fqGhh7FNfGkccu6sK8W1JTDbG4ARWZ1b/632kQ51YE+WuPe7g==" }, "@tiptap/extension-blockquote": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.220.tgz", - "integrity": "sha512-uE1VRU/doQzXsfsZ/JqsbSbXeZYTJnyQkSfHYA2ZYhbEM2XqDEsYkgcmZEJgunUZJpERf+3ZTfTpqaHq29iMMg==", - "requires": {} + "integrity": "sha512-uE1VRU/doQzXsfsZ/JqsbSbXeZYTJnyQkSfHYA2ZYhbEM2XqDEsYkgcmZEJgunUZJpERf+3ZTfTpqaHq29iMMg==" }, "@tiptap/extension-bold": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.220.tgz", - "integrity": "sha512-KcEuKI85Drug/cCWbDy+HxhYrD+rLXHEBG10DmKPvgPpKHG/2wOau6LwUwyV4muWR8CR2mIO+mEc3yVBD8nNwQ==", - "requires": {} + "integrity": "sha512-KcEuKI85Drug/cCWbDy+HxhYrD+rLXHEBG10DmKPvgPpKHG/2wOau6LwUwyV4muWR8CR2mIO+mEc3yVBD8nNwQ==" }, "@tiptap/extension-bubble-menu": { "version": "2.0.0-beta.217", @@ -14924,38 +2995,32 @@ "@tiptap/extension-bullet-list": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.220.tgz", - "integrity": "sha512-QQ/0ZlYy6Hgb+UAc79V+fxvI+AaQf20cbKtBXaR8TIZ0x4FotSma89bKh+CIXMhFiBGXTcYBaYhl7OwACsKtxw==", - "requires": {} + "integrity": "sha512-QQ/0ZlYy6Hgb+UAc79V+fxvI+AaQf20cbKtBXaR8TIZ0x4FotSma89bKh+CIXMhFiBGXTcYBaYhl7OwACsKtxw==" }, "@tiptap/extension-character-count": { "version": "2.0.0-beta.217", "resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.0.0-beta.217.tgz", - "integrity": "sha512-jj3GzuoUbHcPsRyIkxr/X/+FmnvKeDUtzvWhhpEPOlCf9q5Wjq95VbVYZdNPlEXCNCdMu0ePvyW+v8+kFr+Qlw==", - "requires": {} + "integrity": "sha512-jj3GzuoUbHcPsRyIkxr/X/+FmnvKeDUtzvWhhpEPOlCf9q5Wjq95VbVYZdNPlEXCNCdMu0ePvyW+v8+kFr+Qlw==" }, "@tiptap/extension-code": { "version": "2.0.0-beta.217", "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.0.0-beta.217.tgz", - "integrity": "sha512-IPpuI8MeVX5N0ueWQcIvh2nD+EN6DbFtKSsoU+FgFJ9meOkiEWGibSJ5yFQinPsiE9zrwYIaZzzjhr4oeY2JNA==", - "requires": {} + "integrity": "sha512-IPpuI8MeVX5N0ueWQcIvh2nD+EN6DbFtKSsoU+FgFJ9meOkiEWGibSJ5yFQinPsiE9zrwYIaZzzjhr4oeY2JNA==" }, "@tiptap/extension-code-block": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.220.tgz", - "integrity": "sha512-fgA7yTfHqhBtMJF7I9FPJ6UWuZPtxOQiN45Iv9LNmFIB6YRucdpmF+daZ27sElu0a+eICZyXwVn4w4iJphifuw==", - "requires": {} + "integrity": "sha512-fgA7yTfHqhBtMJF7I9FPJ6UWuZPtxOQiN45Iv9LNmFIB6YRucdpmF+daZ27sElu0a+eICZyXwVn4w4iJphifuw==" }, "@tiptap/extension-document": { "version": "2.0.0-beta.217", "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.0.0-beta.217.tgz", - "integrity": "sha512-CDCTutbVO1Ub7QUULPCYILl2px48ezCX2kxbspQZzPD7nMoYZNmUMZ8gRJvVDdnSpMNlEl7oNnCAU7uC/lny/A==", - "requires": {} + "integrity": "sha512-CDCTutbVO1Ub7QUULPCYILl2px48ezCX2kxbspQZzPD7nMoYZNmUMZ8gRJvVDdnSpMNlEl7oNnCAU7uC/lny/A==" }, "@tiptap/extension-dropcursor": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.220.tgz", - "integrity": "sha512-BIaA4Lvb3xL9KFN+K6SO2IHqLO6hDmGN2/rGKHFaU3Eh+oiXM2G73KTSS5KIP1u872zY1RpAtswSc4kjv3cuVw==", - "requires": {} + "integrity": "sha512-BIaA4Lvb3xL9KFN+K6SO2IHqLO6hDmGN2/rGKHFaU3Eh+oiXM2G73KTSS5KIP1u872zY1RpAtswSc4kjv3cuVw==" }, "@tiptap/extension-floating-menu": { "version": "2.0.0-beta.217", @@ -14968,44 +3033,37 @@ "@tiptap/extension-gapcursor": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.220.tgz", - "integrity": "sha512-W5N2Ey+thufUOrs2TFGpEGBGue7ZEhcUXvxcsZlGbrjVa9Y+4rEp68Du4y7yM0hCeSj2GGwiV+uPzkc0CSDE/g==", - "requires": {} + "integrity": "sha512-W5N2Ey+thufUOrs2TFGpEGBGue7ZEhcUXvxcsZlGbrjVa9Y+4rEp68Du4y7yM0hCeSj2GGwiV+uPzkc0CSDE/g==" }, "@tiptap/extension-hard-break": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.220.tgz", - "integrity": "sha512-oY3454o53YNFbuokzyGzG4PdMHkIYreY3nrALioZ0SwYeoFNcGA6Zcn4rDRfdp+QvbbiHfeBTR/CpWF13HZYTg==", - "requires": {} + "integrity": "sha512-oY3454o53YNFbuokzyGzG4PdMHkIYreY3nrALioZ0SwYeoFNcGA6Zcn4rDRfdp+QvbbiHfeBTR/CpWF13HZYTg==" }, "@tiptap/extension-heading": { "version": "2.0.0-beta.217", "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.217.tgz", - "integrity": "sha512-v7G/1R0+qmPm0U2sOCOftqZef5yd89tEg1R+KVF78ucFvuLgH50Pldes8lhHEBlfoRShye2Ht54E6JHDP73J5w==", - "requires": {} + "integrity": "sha512-v7G/1R0+qmPm0U2sOCOftqZef5yd89tEg1R+KVF78ucFvuLgH50Pldes8lhHEBlfoRShye2Ht54E6JHDP73J5w==" }, "@tiptap/extension-history": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.0.0-beta.220.tgz", - "integrity": "sha512-qNL2a9UhnlmCs4y2iQYrfeMB8vEX3bHozBJanHu0PWNQJcj90R5xqorBp/bRcqZdi0kuQfxcTnGHtLUpN/U0TA==", - "requires": {} + "integrity": "sha512-qNL2a9UhnlmCs4y2iQYrfeMB8vEX3bHozBJanHu0PWNQJcj90R5xqorBp/bRcqZdi0kuQfxcTnGHtLUpN/U0TA==" }, "@tiptap/extension-horizontal-rule": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.220.tgz", - "integrity": "sha512-XMIs4R+4BoH5LpIxey513mZuus0XLHqjVayqtf03enmjBTLWzkixvvWLPLw4a47FJL5Q8l4REFHxjNifRzOKkg==", - "requires": {} + "integrity": "sha512-XMIs4R+4BoH5LpIxey513mZuus0XLHqjVayqtf03enmjBTLWzkixvvWLPLw4a47FJL5Q8l4REFHxjNifRzOKkg==" }, "@tiptap/extension-image": { "version": "2.0.0-beta.217", "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.0.0-beta.217.tgz", - "integrity": "sha512-8ibJOicpcqhFeiPLqYOY8SO9sxkz2bemzwhtzRvgkevy/QERe9D0mbAL0qm1gg88LtXy3z2eFUdwD8suq1xsPQ==", - "requires": {} + "integrity": "sha512-8ibJOicpcqhFeiPLqYOY8SO9sxkz2bemzwhtzRvgkevy/QERe9D0mbAL0qm1gg88LtXy3z2eFUdwD8suq1xsPQ==" }, "@tiptap/extension-italic": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.220.tgz", - "integrity": "sha512-aWAgqoR8fql9fJ7T/ZrEqovkEjZXbUpvlvWEvdBDMG3id8ZTGNDpdDKdvI6J/Rl5ZGPIg1TpHJtd+UixheWQsQ==", - "requires": {} + "integrity": "sha512-aWAgqoR8fql9fJ7T/ZrEqovkEjZXbUpvlvWEvdBDMG3id8ZTGNDpdDKdvI6J/Rl5ZGPIg1TpHJtd+UixheWQsQ==" }, "@tiptap/extension-link": { "version": "2.0.0-beta.217", @@ -15018,68 +3076,57 @@ "@tiptap/extension-list-item": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.220.tgz", - "integrity": "sha512-+O0ivwxPP2l/m9PAowb2ytDT/cM5kwu0s1W5MUsHPIqf+M6ahnl4ESjhWZfDHUzvjqPq6MTbqoQLHbB1KS/N7w==", - "requires": {} + "integrity": "sha512-+O0ivwxPP2l/m9PAowb2ytDT/cM5kwu0s1W5MUsHPIqf+M6ahnl4ESjhWZfDHUzvjqPq6MTbqoQLHbB1KS/N7w==" }, "@tiptap/extension-ordered-list": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.220.tgz", - "integrity": "sha512-j3DmxJfwmNxFfMnvO7glmGlhYeZSIUnRrKnZu2KkpD6OcGJSh9y/yfnYwcuK80XbzEG/jKKIw0M2yRveOvyVwA==", - "requires": {} + "integrity": "sha512-j3DmxJfwmNxFfMnvO7glmGlhYeZSIUnRrKnZu2KkpD6OcGJSh9y/yfnYwcuK80XbzEG/jKKIw0M2yRveOvyVwA==" }, "@tiptap/extension-paragraph": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.220.tgz", - "integrity": "sha512-ZGCzNGFYV4wa3l1nXtDIaYp7O6f0DrGTSl3alKkDTQe3SOmzXS2HjgWl9yPw8VXpU9W5mMGhXd+nGn/jUk+f/A==", - "requires": {} + "integrity": "sha512-ZGCzNGFYV4wa3l1nXtDIaYp7O6f0DrGTSl3alKkDTQe3SOmzXS2HjgWl9yPw8VXpU9W5mMGhXd+nGn/jUk+f/A==" }, "@tiptap/extension-strike": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.220.tgz", - "integrity": "sha512-cIM2ma6mzk08pijOn+KS3ZoHWaUVsVT+OF3m6xewjwJdC0ILg9nApEOhPFrhbeDcxcPmJMlgBl/xeUrEu1HQMg==", - "requires": {} + "integrity": "sha512-cIM2ma6mzk08pijOn+KS3ZoHWaUVsVT+OF3m6xewjwJdC0ILg9nApEOhPFrhbeDcxcPmJMlgBl/xeUrEu1HQMg==" }, "@tiptap/extension-table": { "version": "2.0.0-beta.217", "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.0.0-beta.217.tgz", - "integrity": "sha512-8PwfNXIRPy1zxZAk0kS+sqFeUE2M6al1y/mA6p0SA9YhSN0iWvjQfmq9Ds52hmRcL2Dv9QmLR97S7WGRmHKcQg==", - "requires": {} + "integrity": "sha512-8PwfNXIRPy1zxZAk0kS+sqFeUE2M6al1y/mA6p0SA9YhSN0iWvjQfmq9Ds52hmRcL2Dv9QmLR97S7WGRmHKcQg==" }, "@tiptap/extension-table-cell": { "version": "2.0.0-beta.217", "resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.0.0-beta.217.tgz", - "integrity": "sha512-W5UxsZxQdBms916hHp4giXi6AOkwCEfSaTXfi3FQqxcg/EQnmzMNB82/9BcVqBUaoJrx1dIVm4ploIL+GikG/w==", - "requires": {} + "integrity": "sha512-W5UxsZxQdBms916hHp4giXi6AOkwCEfSaTXfi3FQqxcg/EQnmzMNB82/9BcVqBUaoJrx1dIVm4ploIL+GikG/w==" }, "@tiptap/extension-table-header": { "version": "2.0.0-beta.217", "resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.0.0-beta.217.tgz", - "integrity": "sha512-oahTLhItvoPzCA9RuGLowZ0ZGro+Yn3+1NefXu/yGlp3twKQyhrwOv3+TqZ21L+8uKGOVLfLgPZnF6oNozEdJQ==", - "requires": {} + "integrity": "sha512-oahTLhItvoPzCA9RuGLowZ0ZGro+Yn3+1NefXu/yGlp3twKQyhrwOv3+TqZ21L+8uKGOVLfLgPZnF6oNozEdJQ==" }, "@tiptap/extension-table-row": { "version": "2.0.0-beta.217", "resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.0.0-beta.217.tgz", - "integrity": "sha512-6ie3YtnOliIzER4JtVh0T8HQl3Z2gwTBoCOvqoetsoKIk0zNdsai+ZjVjVN4ZiMLFNYm5xnCUfr83usp9kawhQ==", - "requires": {} + "integrity": "sha512-6ie3YtnOliIzER4JtVh0T8HQl3Z2gwTBoCOvqoetsoKIk0zNdsai+ZjVjVN4ZiMLFNYm5xnCUfr83usp9kawhQ==" }, "@tiptap/extension-text": { "version": "2.0.0-beta.217", "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.0.0-beta.217.tgz", - "integrity": "sha512-l5PkVhZzVnxbGyMiCMqpLU3ZomJWcZXhuBD0aajry+l85DWc6TK0mhLEAVEU1LoTL8hXFmICoW1KJ1m20Va9dg==", - "requires": {} + "integrity": "sha512-l5PkVhZzVnxbGyMiCMqpLU3ZomJWcZXhuBD0aajry+l85DWc6TK0mhLEAVEU1LoTL8hXFmICoW1KJ1m20Va9dg==" }, "@tiptap/extension-text-align": { "version": "2.0.0-beta.217", "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.0.0-beta.217.tgz", - "integrity": "sha512-oFr1xkUFJKHGJkFA0CDcfkEA4z3jr1umFRkr6qejKiYqwAUdnn5YrtfdGICJzBEa6/1wOCZz0ODzCnv9XmGDkA==", - "requires": {} + "integrity": "sha512-oFr1xkUFJKHGJkFA0CDcfkEA4z3jr1umFRkr6qejKiYqwAUdnn5YrtfdGICJzBEa6/1wOCZz0ODzCnv9XmGDkA==" }, "@tiptap/extension-typography": { "version": "2.0.0-beta.217", "resolved": "https://registry.npmjs.org/@tiptap/extension-typography/-/extension-typography-2.0.0-beta.217.tgz", - "integrity": "sha512-xr/KBgdcI3qbTshbY0FCN8BiSRW++MH7yQwvAKlhk47Qt0UcVsRIGc/FfXFD2MhfoZ4vaVrIn5daU4P4p3d+wA==", - "requires": {} + "integrity": "sha512-xr/KBgdcI3qbTshbY0FCN8BiSRW++MH7yQwvAKlhk47Qt0UcVsRIGc/FfXFD2MhfoZ4vaVrIn5daU4P4p3d+wA==" }, "@tiptap/pm": { "version": "2.0.0-beta.217", @@ -15465,8 +3512,7 @@ "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "requires": {} + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==" }, "adminjs": { "version": "6.8.7", @@ -15538,8 +3584,7 @@ "ajv-errors": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", - "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", - "requires": {} + "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==" }, "ajv-formats": { "version": "2.1.1", @@ -16044,7 +4089,6 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "devOptional": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -16382,6 +4426,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, "requires": { "esutils": "^2.0.2" } @@ -16433,8 +4478,7 @@ "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==", - "devOptional": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "enabled": { "version": "2.0.0", @@ -16580,6 +4624,7 @@ "version": "8.36.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", + "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", @@ -16627,6 +4672,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -16638,6 +4684,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -16646,6 +4693,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -16655,6 +4703,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -16662,17 +4711,20 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true }, "globals": { "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, "requires": { "type-fest": "^0.20.2" } @@ -16680,17 +4732,20 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -16698,15 +4753,15 @@ "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true } } }, "eslint-config-prettier": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.7.0.tgz", - "integrity": "sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA==", - "requires": {} + "integrity": "sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA==" }, "eslint-plugin-mocha": { "version": "10.1.0", @@ -16722,6 +4777,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -16769,6 +4825,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, "requires": { "estraverse": "^5.1.0" } @@ -16777,6 +4834,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "requires": { "estraverse": "^5.2.0" } @@ -16886,8 +4944,7 @@ "express-rate-limit": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz", - "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==", - "requires": {} + "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==" }, "express-session": { "version": "1.17.3", @@ -16947,7 +5004,8 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "fast-levenshtein": { "version": "2.0.6", @@ -16979,6 +5037,7 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, "requires": { "reusify": "^1.0.4" } @@ -17000,6 +5059,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, "requires": { "flat-cache": "^3.0.4" } @@ -17069,6 +5129,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "requires": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -17158,6 +5219,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, "requires": { "flatted": "^3.1.0", "rimraf": "^3.0.2" @@ -17166,7 +5228,8 @@ "flatted": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true }, "fn.name": { "version": "1.1.0", @@ -17268,8 +5331,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "devOptional": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-func-name": { "version": "2.0.0", @@ -17304,6 +5366,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "requires": { "is-glob": "^4.0.3" } @@ -17396,7 +5459,8 @@ "grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true }, "gtoken": { "version": "6.1.2", @@ -17537,7 +5601,8 @@ "ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==" + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true }, "ignore-by-default": { "version": "1.0.1", @@ -17557,7 +5622,8 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true }, "inflight": { "version": "1.0.6", @@ -17638,13 +5704,13 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true }, "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==", - "devOptional": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-generator-function": { "version": "1.0.10", @@ -17658,6 +5724,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -17681,7 +5748,8 @@ "is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true }, "is-plain-obj": { "version": "2.1.0", @@ -17786,7 +5854,8 @@ "js-sdsl": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==" + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "dev": true }, "js-tokens": { "version": "4.0.0", @@ -17797,6 +5866,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "requires": { "argparse": "^2.0.1" } @@ -17896,7 +5966,8 @@ "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, "json5": { "version": "2.2.3", @@ -18021,6 +6092,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "requires": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -18053,6 +6125,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "requires": { "p-locate": "^5.0.0" } @@ -18116,7 +6189,8 @@ "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true }, "lodash.once": { "version": "4.1.1", @@ -18301,8 +6375,7 @@ "version": "8.6.7", "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", - "optional": true, - "requires": {} + "optional": true }, "marked": { "version": "4.2.12", @@ -18572,13 +6645,22 @@ "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, + "node-cron": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz", + "integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==", + "requires": { + "uuid": "8.3.2" + } + }, "node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -18774,6 +6856,7 @@ "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, "requires": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -18861,6 +6944,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "requires": { "p-limit": "^3.0.2" } @@ -18897,7 +6981,8 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true }, "path-is-absolute": { "version": "1.0.1", @@ -19014,7 +7099,8 @@ "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true }, "prop-types": { "version": "15.8.1", @@ -19414,7 +7500,8 @@ "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true }, "raf-schd": { "version": "4.0.3", @@ -19467,8 +7554,7 @@ "react-currency-input-field": { "version": "3.6.10", "resolved": "https://registry.npmjs.org/react-currency-input-field/-/react-currency-input-field-3.6.10.tgz", - "integrity": "sha512-KRAJJaLujarBTLlEVbznsUxQ56+Qyqwoe5w9DnGxmsGnHv4ycQRpRkuuCDfF9BcXHmegzsOXesfIGpW7Cw9mTQ==", - "requires": {} + "integrity": "sha512-KRAJJaLujarBTLlEVbznsUxQ56+Qyqwoe5w9DnGxmsGnHv4ycQRpRkuuCDfF9BcXHmegzsOXesfIGpW7Cw9mTQ==" }, "react-datepicker": { "version": "4.10.0", @@ -19521,8 +7607,7 @@ "react-onclickoutside": { "version": "6.12.2", "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.12.2.tgz", - "integrity": "sha512-NMXGa223OnsrGVp5dJHkuKxQ4czdLmXSp5jSV9OqiCky9LOpPATn3vLldc+q5fK3gKbEHvr7J1u0yhBh/xYkpA==", - "requires": {} + "integrity": "sha512-NMXGa223OnsrGVp5dJHkuKxQ4czdLmXSp5jSV9OqiCky9LOpPATn3vLldc+q5fK3gKbEHvr7J1u0yhBh/xYkpA==" }, "react-phone-input-2": { "version": "2.15.1", @@ -19708,8 +7793,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "devOptional": true + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" }, "require-from-string": { "version": "2.0.2", @@ -19784,7 +7868,8 @@ "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true }, "rimraf": { "version": "3.0.2", @@ -19832,6 +7917,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "requires": { "queue-microtask": "^1.2.2" } @@ -20130,25 +8216,24 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "optional": true }, - "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==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, "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==", - "devOptional": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, + "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==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -20378,7 +8463,8 @@ "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "throttle-debounce": { "version": "3.0.1", @@ -20457,6 +8543,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "requires": { "prelude-ls": "^1.2.1" } @@ -20582,20 +8669,17 @@ "use-isomorphic-layout-effect": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", - "requires": {} + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==" }, "use-memo-one": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", - "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", - "requires": {} + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==" }, "use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "requires": {} + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" }, "util": { "version": "0.12.5", @@ -20771,7 +8855,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "devOptional": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -20782,7 +8865,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, "requires": { "color-convert": "^2.0.1" } @@ -20791,7 +8873,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, "requires": { "color-name": "~1.1.4" } @@ -20799,8 +8880,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" } } }, @@ -20812,8 +8892,7 @@ "ws": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "requires": {} + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==" }, "xml2js": { "version": "0.4.19", @@ -20854,8 +8933,7 @@ "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "devOptional": true + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yallist": { "version": "3.1.1", @@ -20871,7 +8949,6 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "devOptional": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -20885,8 +8962,7 @@ "yargs-parser": { "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "devOptional": true + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" }, "yargs-unparser": { "version": "2.0.0", diff --git a/package.json b/package.json index 9c7d1fd9..889e1ce4 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "firebase-admin": "^11.4.1", "jsonwebtoken": "^8.5.1", "mongoose": "^6.5.2", + "node-cron": "3.0.2", "node-mocks-http": "^1.12.1", "querystring": "^0.2.1", "redis": "^4.2.0", diff --git a/src/modules/socket.js b/src/modules/socket.js index c16e7856..1c8a6f19 100644 --- a/src/modules/socket.js +++ b/src/modules/socket.js @@ -81,6 +81,8 @@ const getMessageBody = (type, nickname, content) => { ? " 님이 결제를 완료하였습니다" : " 님이 정산을 완료하였습니다"; return `${nickname} ${suffix}`; + } else if (type === "department") { + return "출발 15분전 입니다"; } }; @@ -102,7 +104,7 @@ const emitChatEvent = async (io, chat) => { // chat can contain time or not. const { roomId, type, content, authorId } = chat; - if (!io || !roomId || !type || !content || !authorId) { + if (!io || !roomId || !type || !content) { throw new IllegalArgumentsException(); } diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index f9b49e84..fd8c9182 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -121,7 +121,16 @@ const chatSchema = Schema({ roomId: { type: Schema.Types.ObjectId, ref: "Room", required: true }, type: { type: String, - enum: ["text", "in", "out", "s3img", "payment", "settlement", "account"], + enum: [ + "text", + "in", + "out", + "s3img", + "payment", + "settlement", + "account", + "department", + ], }, // 메시지 종류 authorId: { type: Schema.Types.ObjectId, ref: "User", required: true }, // 작성자 id content: { type: String, default: "" }, diff --git a/src/schedules/notifyBeforeDepart.js b/src/schedules/notifyBeforeDepart.js index c486b064..85f713ed 100644 --- a/src/schedules/notifyBeforeDepart.js +++ b/src/schedules/notifyBeforeDepart.js @@ -1,7 +1,9 @@ -const { chatModel, userModel, roomModel } = require("../modules/stores/mongo"); +const { roomModel } = require("../modules/stores/mongo"); +const { roomPopulateOption } = require("../modules/populates/rooms"); const { emitChatEvent } = require("../modules/socket"); -const expression = "*/5 * * * *"; +// const expression = "*/5 * * * *"; +const expression = "* * * * *"; const MS_PER_MINUTE = 60000; /** @@ -10,36 +12,33 @@ const MS_PER_MINUTE = 60000; * @return {Promise} 알림 전송에 실패한 기기 수를 반환합니다. */ -const sendReminder = async (req, res) => { - const currentDate = new Date(Date.now() - 15 * MS_PER_MINUTE).toISOString(); +const sendReminder = async (io) => { + const departDate = new Date(Date.now() + 15 * MS_PER_MINUTE).toISOString(); + const currentDate = new Date(Date.now()).toISOString(); - const roomIds = await roomModel - .find({ time: { $gte: currentDate } }) - .populate("ongoingRoom"); - console.log(roomIds); + const roomObjects = await roomModel + .find({ + $and: [{ time: { $lt: departDate } }, { time: { $gte: currentDate } }], + }) + .lean() + .populate(roomPopulateOption); - if (!roomIds) { + if (!roomObjects) { return res.status(404).json({ error: "NotifyBeforeDepartment: cannot find room", }); } - // alert to each room object - await emitChatEvent(req.app.get("io"), { - roomId: roomModel._id, - type: "department", - content: user.id, + const roomIds = roomObjects.map((room) => room._id); + const authorIds = roomObjects.map((room) => room.part[0].user._id); + roomIds.map(async (roomId, index) => { + await emitChatEvent(io, { + roomId: roomId, + type: "department", + content: roomId, + authorId: authorIds[index], + }); }); - // department 채팅 메세지 유형 추가해야함 - - roomIds.map( - async (roomId) => - await emitChatEvent(req.app.get("io"), { - roomId: roomId, - type: "department", - content: user.id, - }) - ); }; module.exports = { From 89bcf6909b205fab6b716880e6b2e6d6b6e52e32 Mon Sep 17 00:00:00 2001 From: ptptsw Date: Fri, 30 Jun 2023 23:31:41 +0900 Subject: [PATCH 007/261] Refactor: condition --- src/modules/socket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/socket.js b/src/modules/socket.js index 1c8a6f19..cf02bd90 100644 --- a/src/modules/socket.js +++ b/src/modules/socket.js @@ -104,7 +104,7 @@ const emitChatEvent = async (io, chat) => { // chat can contain time or not. const { roomId, type, content, authorId } = chat; - if (!io || !roomId || !type || !content) { + if (!io || !roomId || !type || !content || !authorId) { throw new IllegalArgumentsException(); } From d02e76bc8a35d7f08e9ce9b9ed9c6187b2c2af59 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Fri, 11 Aug 2023 20:13:44 +0900 Subject: [PATCH 008/261] Fix: auth.js and auth.replace.js --- loadenv.js | 1 - src/middlewares/authAdmin.js | 11 ++- src/middlewares/information.js | 11 ++- src/services/auth.js | 135 +++++++++++++++++---------------- src/services/auth.replace.js | 74 +++--------------- src/views/loginReplacePage.js | 3 +- 6 files changed, 96 insertions(+), 139 deletions(-) diff --git a/loadenv.js b/loadenv.js index 8935a0ba..cfa801cf 100644 --- a/loadenv.js +++ b/loadenv.js @@ -29,7 +29,6 @@ module.exports = { TOKEN_EXPIRED: -3, TOKEN_INVALID: -2, }, - appUriScheme: process.env.APP_URI_SCHEME, // FIXME: 사용하지 않음 googleApplicationCredentials: process.env.GOOGLE_APPLICATION_CREDENTIALS && JSON.parse(process.env.GOOGLE_APPLICATION_CREDENTIALS), diff --git a/src/middlewares/authAdmin.js b/src/middlewares/authAdmin.js index 218f724d..8db1f2a0 100644 --- a/src/middlewares/authAdmin.js +++ b/src/middlewares/authAdmin.js @@ -1,31 +1,30 @@ // 관리자 유무를 확인하기 위한 미들웨어입니다. const { isLogin, getLoginInfo } = require("../modules/auths/login"); -const { frontUrl } = require("../../loadenv"); const { userModel, adminIPWhitelistModel } = require("../modules/stores/mongo"); const authAdminMiddleware = async (req, res, next) => { try { // 로그인 여부를 확인 - if (!isLogin(req)) return res.redirect(frontUrl); + if (!isLogin(req)) return res.redirect(req.origin); // 관리자 유무를 확인 const { id } = getLoginInfo(req); const user = await userModel.findOne({ id }); - if (!user.isAdmin) return res.redirect(frontUrl); + if (!user.isAdmin) return res.redirect(req.origin); // 접속한 IP가 화이트리스트에 있는지 확인 const ipWhitelist = await adminIPWhitelistModel.find({}); - if (!req.clientIP) return res.redirect(frontUrl); + if (!req.clientIP) return res.redirect(req.origin); if ( ipWhitelist.length > 0 && ipWhitelist.map((x) => x.ip).indexOf(req.clientIP) < 0 ) - return res.redirect(frontUrl); + return res.redirect(req.origin); next(); } catch (e) { - res.redirect(frontUrl); + res.redirect(req.origin); } }; diff --git a/src/middlewares/information.js b/src/middlewares/information.js index f5d5b014..f90bb6ba 100644 --- a/src/middlewares/information.js +++ b/src/middlewares/information.js @@ -1,5 +1,14 @@ -module.exports = (req, _, next) => { +module.exports = (req, res, next) => { req.clientIP = req.headers["x-forwarded-for"] || req.connection.remoteAddress; req.timestamp = Date.now(); + req.origin = + req.headers.origin || + req.headers.referer || + req.session?.loginAfterState?.redirectOrigin; + if (!req.origin) { + return res.status(412).json({ + error: "Precondition Failed : request must have origin in header", + }); + } next(); }; diff --git a/src/services/auth.js b/src/services/auth.js index d001e2f7..324f82d7 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -1,6 +1,5 @@ const { sparcssso: sparcsssoEnv, - frontUrl, nodeEnv, testAccounts, } = require("../../loadenv"); @@ -15,6 +14,7 @@ const { getFullUsername, } = require("../modules/modifyProfile"); const jwt = require("../modules/auths/jwt"); +const logger = require("../modules/logger"); // SPARCS SSO const Client = require("../modules/auths/sparcssso"); @@ -39,8 +39,7 @@ const transUserData = (userData) => { return info; }; -// 가입시키기 -const joinus = (req, res, userData) => { +const joinus = async (userData) => { const newUser = new userModel({ id: userData.id, name: userData.name, @@ -55,54 +54,49 @@ const joinus = (req, res, userData) => { }, email: userData.email, }); - newUser.save((err) => { - if (err) { - loginFail(req, res); - return; - } - loginDone(req, res, userData); - }); + await newUser.save(); }; -const update = async (req, res, userData) => { +const update = async (userData) => { const updateInfo = { name: userData.name }; await userModel.updateOne({ id: userData.id }, updateInfo); - loginDone(req, res, userData); }; -const loginDone = (req, res, userData) => { - userModel.findOne( - { id: userData.id }, - "_id name id withdraw ban", - async (err, result) => { - if (err) loginFail(req, res); - else if (!result) joinus(req, res, userData); - else if (result.name != userData.name) update(req, res, userData); - else { - if (req.session.isApp) { - const { token: accessToken } = await jwt.sign({ - id: result._id, - type: "access", - }); - const { token: refreshToken } = await jwt.sign({ - id: result._id, - type: "refresh", - }); - req.session.accessToken = accessToken; - req.session.refreshToken = refreshToken; - } - - const redirectPath = req.session?.state_redirectPath || "/"; - req.session.state_redirectPath = undefined; - login(req, userData.sid, result.id, result._id, result.name); - res.redirect(frontUrl + redirectPath); - } +const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { + try { + const user = await userModel.findOne( + { id: userData.id }, + "_id name id withdraw ban" + ); + if (!user) { + await joinus(userData); + return tryLogin(req, res, userData, redirectOrigin, redirectPath); + } + if (user.name != userData.name) { + await update(userData); + return tryLogin(req, res, userData, redirectOrigin, redirectPath); + } + + if (req.session.isApp) { + const { token: accessToken } = await jwt.sign({ + id: user._id, + type: "access", + }); + const { token: refreshToken } = await jwt.sign({ + id: user._id, + type: "refresh", + }); + req.session.accessToken = accessToken; + req.session.refreshToken = refreshToken; } - ); -}; -const loginFail = (req, res, redirectUrl = "") => { - res.redirect(redirectUrl || frontUrl + "/login/fail"); + login(req, userData.sid, user.id, user._id, user.name); + res.redirect(new URL(redirectPath, redirectOrigin).href); + } catch (err) { + logger.error(err); + const redirectUrl = new URL("/login/fail", redirectOrigin).href; + res.redirect(ssoLogoutUrl); + } }; const sparcsssoHandler = (req, res) => { @@ -110,33 +104,43 @@ const sparcsssoHandler = (req, res) => { const isApp = !!req.query.isApp; const { url, state } = client.getLoginParams(); - req.session.state = state; - req.session.state_redirectPath = redirectPath; + req.session.loginAfterState = { + state: state, + redirectOrigin: req.origin, + redirectPath: redirectPath, + }; req.session.isApp = isApp; res.redirect(url + "&social_enabled=0&show_disabled_button=0"); }; const sparcsssoCallbackHandler = (req, res) => { - const state1 = req.session.state; - const state2 = req.body.state || req.query.state; - - if (state1 !== state2) loginFail(req, res); - else { - const code = req.body.code || req.query.code; - client.getUserInfo(code).then((userDataBefore) => { - const userData = transUserData(userDataBefore); - const isTestAccount = testAccounts?.includes(userData.email); - if (userData.isEligible || nodeEnv !== "production" || isTestAccount) { - loginDone(req, res, userData); - } else { - // 카이스트 구성원이 아닌 경우, SSO 로그아웃 이후, 로그인 실패 URI 로 이동합니다 - const { sid } = userData; - const redirectUrl = frontUrl + "/login/fail"; - const ssoLogoutUrl = client.getLogoutUrl(sid, redirectUrl); - loginFail(req, res, ssoLogoutUrl); - } - }); + const { state, redirectOrigin, redirectPath } = req.session?.loginAfterState; + const stateForCmp = req.body.state || req.query.state; + + req.session.loginAfterState = undefined; + if (!state || !redirectOrigin || !redirectPath) { + return res.status(400).send("SparcsssoCallbackHandler : invalid request"); + } + + if (state !== stateForCmp) { + const redirectUrl = new URL("/login/fail", redirectOrigin).href; + return res.redirect(redirectUrl); } + + const code = req.body.code || req.query.code; + client.getUserInfo(code).then((userDataBefore) => { + const userData = transUserData(userDataBefore); + const isTestAccount = testAccounts?.includes(userData.email); + if (userData.isEligible || nodeEnv !== "production" || isTestAccount) { + tryLogin(req, res, userData, redirectOrigin, redirectPath); + } else { + // 카이스트 구성원이 아닌 경우, SSO 로그아웃 이후, 로그인 실패 URI 로 이동합니다 + const { sid } = userData; + const redirectUrl = new URL("/login/fail", redirectOrigin).href; + const ssoLogoutUrl = client.getLogoutUrl(sid, redirectUrl); + res.redirect(ssoLogoutUrl); + } + }); }; const loginReplaceHandler = (req, res) => { @@ -158,7 +162,7 @@ const logoutHandler = async (req, res) => { } // 로그아웃 URL을 생성 및 반환 - const redirectUrl = frontUrl + redirectPath; + const redirectUrl = new URL(redirectPath, req.origin).href; const ssoLogoutUrl = client.getLogoutUrl(sid, redirectUrl); logout(req, res); res.json({ ssoLogoutUrl }); @@ -168,6 +172,7 @@ const logoutHandler = async (req, res) => { }; module.exports = { + tryLogin, sparcsssoHandler, sparcsssoCallbackHandler, loginReplaceHandler, diff --git a/src/services/auth.replace.js b/src/services/auth.replace.js index d6470680..cde54b53 100644 --- a/src/services/auth.replace.js +++ b/src/services/auth.replace.js @@ -1,4 +1,3 @@ -const { frontUrl } = require("../../loadenv"); const { userModel } = require("../modules/stores/mongo"); const { logout, login } = require("../modules/auths/login"); @@ -10,10 +9,10 @@ const { const logger = require("../modules/logger"); const jwt = require("../modules/auths/jwt"); -const { registerDeviceTokenHandler } = require("../services/auth"); +const { registerDeviceTokenHandler, tryLogin } = require("../services/auth"); const loginReplacePage = require("../views/loginReplacePage"); -const makeInfo = (id) => { +const createUserData = (id) => { const info = { id: id, sid: id + "-sid", @@ -29,75 +28,22 @@ const makeInfo = (id) => { return info; }; -// 새로운 유저 만들기 -// 이거 왜 이름이 joinus? -const joinus = (req, res, userData, redirectPath = "/") => { - const newUser = new userModel({ - id: userData.id, - name: userData.name, - nickname: userData.nickname, - profileImageUrl: userData.profileImageUrl, - joinat: req.timestamp, - subinfo: { - kaist: userData.kaist, - sparcs: userData.sparcs, - facebook: userData.facebook, - twitter: userData.twitter, - }, - email: userData.email, - }); - newUser.save((err) => { - if (err) { - logger.error(err); - return; - } - loginDone(req, res, userData, redirectPath); - }); -}; - -// 주어진 데이터로 DB 검색 -// 만약 없으면 새로운 유저 만들기 -// 있으면 로그인 진행 후 리다이렉트 -const loginDone = (req, res, userData, redirectPath = "/") => { - userModel.findOne( - { id: userData.id }, - "_id name id withdraw ban", - async (err, result) => { - if (err) logger.error(logger.error(err)); - else if (!result) joinus(req, res, userData, redirectPath); - else { - if (req.session.isApp) { - const { token: accessToken } = await jwt.sign({ - id: result._id, - type: "access", - }); - const { token: refreshToken } = await jwt.sign({ - id: result._id, - type: "refresh", - }); - req.session.accessToken = accessToken; - req.session.refreshToken = refreshToken; - } - - login(req, userData.sid, result.id, result._id, result.name); - res.redirect(frontUrl + redirectPath); - } - } - ); -}; - const loginReplaceHandler = (req, res) => { const { id } = req.body; - const redirectPath = decodeURIComponent(req.body?.redirect || "%2F"); - loginDone(req, res, makeInfo(id), redirectPath); + const { redirectOrigin, redirectPath } = req.session?.loginAfterState; + tryLogin(req, res, createUserData(id), redirectOrigin, redirectPath); }; const sparcsssoHandler = (req, res) => { const redirectPath = decodeURIComponent(req.query?.redirect || "%2F"); const isApp = !!req.query.isApp; + req.session.loginAfterState = { + redirectOrigin: req.origin, + redirectPath: redirectPath, + }; req.session.isApp = isApp; - res.end(loginReplacePage(redirectPath)); + res.end(loginReplacePage); }; const logoutHandler = async (req, res) => { @@ -111,7 +57,7 @@ const logoutHandler = async (req, res) => { } // sparcs-sso 로그아웃 URL을 생성 및 반환 - const ssoLogoutUrl = frontUrl + redirectPath; + const ssoLogoutUrl = new URL(redirectPath, req.origin).href; logout(req, res); res.json({ ssoLogoutUrl }); } catch (e) { diff --git a/src/views/loginReplacePage.js b/src/views/loginReplacePage.js index c41c6025..209e0bb0 100644 --- a/src/views/loginReplacePage.js +++ b/src/views/loginReplacePage.js @@ -1,4 +1,4 @@ -module.exports = (redirectPath) => ` +module.exports = ` @@ -26,7 +26,6 @@ module.exports = (redirectPath) => ` const value = document.getElementById("input-id").value; if(value) post('/auth/login/replace', { id: value, - redirect: "${encodeURIComponent(redirectPath)}", }); } const enterHandler = (e) => { From 31be2eb771f4f83317b401bc104c681bac428a72 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Sat, 12 Aug 2023 00:48:20 +0900 Subject: [PATCH 009/261] Add: corsWhiteList --- app.js | 4 +--- loadenv.js | 2 ++ src/middlewares/cors.js | 8 ++++++++ src/middlewares/information.js | 3 ++- src/modules/socket.js | 4 ++-- 5 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 src/middlewares/cors.js diff --git a/app.js b/app.js index 3e936950..2e917d5e 100644 --- a/app.js +++ b/app.js @@ -16,9 +16,7 @@ app.use(express.urlencoded({ extended: false })); app.use(express.json()); // [Middleware] CORS 설정 -app.use( - require("cors")({ origin: true, credentials: true, exposedHeaders: ["Date"] }) -); +app.use(require("./src/middlewares/cors")); // [Middleware] 세션 및 쿠키 const session = require("./src/middlewares/session"); diff --git a/loadenv.js b/loadenv.js index cfa801cf..eb922e76 100644 --- a/loadenv.js +++ b/loadenv.js @@ -11,6 +11,8 @@ module.exports = { key: process.env.SPARCSSSO_CLIENT_KEY || "", // optional }, port: process.env.PORT || 80, // optional (default = 80) + corsWhiteList: (process.env.CORS_WHITELIST && + JSON.parse(process.env.CORS_WHITELIST)) || [true], // optional (default = [true]) frontUrl: process.env.FRONT_URL || "http://localhost:3000", // optional (default = "http://localhost:3000") aws: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, // required diff --git a/src/middlewares/cors.js b/src/middlewares/cors.js new file mode 100644 index 00000000..0b644243 --- /dev/null +++ b/src/middlewares/cors.js @@ -0,0 +1,8 @@ +var cors = require("cors"); +const { corsWhiteList } = require("../../loadenv"); + +module.exports = cors({ + origin: corsWhiteList, + credentials: true, + exposedHeaders: ["Date"], +}); diff --git a/src/middlewares/information.js b/src/middlewares/information.js index f90bb6ba..a1c0ed4c 100644 --- a/src/middlewares/information.js +++ b/src/middlewares/information.js @@ -4,7 +4,8 @@ module.exports = (req, res, next) => { req.origin = req.headers.origin || req.headers.referer || - req.session?.loginAfterState?.redirectOrigin; + req.session?.loginAfterState?.redirectOrigin; // sparcssso/callback 요청은 헤더에 origin이 없음 + if (!req.origin) { return res.status(412).json({ error: "Precondition Failed : request must have origin in header", diff --git a/src/modules/socket.js b/src/modules/socket.js index 0dc5fa1a..637d3a85 100644 --- a/src/modules/socket.js +++ b/src/modules/socket.js @@ -7,7 +7,7 @@ const { roomModel, userModel, chatModel } = require("./stores/mongo"); const { getS3Url } = require("./stores/aws"); const { getTokensOfUsers, sendMessageByTokens } = require("./fcm"); -const { frontUrl } = require("../../loadenv"); +const { corsWhiteList } = require("../../loadenv"); const { chatPopulateOption } = require("./populates/chats"); /** @@ -209,7 +209,7 @@ const startSocketServer = (server) => { }); }, cors: { - origin: [frontUrl], + origin: corsWhiteList, methods: ["GET", "POST"], credentials: true, }, From a41458aa748da24ac16354fb3629cbfac508fa3c Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Sat, 12 Aug 2023 01:03:22 +0900 Subject: [PATCH 010/261] Add: latitude, longitude --- src/modules/stores/mongo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 244241f0..18a570f6 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -114,8 +114,8 @@ const locationSchema = Schema({ koName: { type: String, required: true }, priority: { type: Number, default: 0 }, isValid: { type: Boolean, default: true }, - // latitude: { type: Number, required: true }, - // longitude: { type: Number, required: true } + latitude: { type: Number }, // 이후 required: true 로 수정 필요 + longitude: { type: Number }, // 이후 required: true 로 수정 필요 }); const chatSchema = Schema({ roomId: { type: Schema.Types.ObjectId, ref: "Room", required: true }, From 4c0f0a95ac824ecb5758080bc6019c9c34b624c0 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Sat, 12 Aug 2023 02:01:02 +0900 Subject: [PATCH 011/261] Remove: frontUrl in env --- loadenv.js | 1 - src/services/reports.js | 3 ++- src/views/emailNoSettlementPage.js | 11 +++++++---- test/auth.replace.js | 8 +++----- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/loadenv.js b/loadenv.js index eb922e76..35a55f5c 100644 --- a/loadenv.js +++ b/loadenv.js @@ -13,7 +13,6 @@ module.exports = { port: process.env.PORT || 80, // optional (default = 80) corsWhiteList: (process.env.CORS_WHITELIST && JSON.parse(process.env.CORS_WHITELIST)) || [true], // optional (default = [true]) - frontUrl: process.env.FRONT_URL || "http://localhost:3000", // optional (default = "http://localhost:3000") aws: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, // required secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, // required diff --git a/src/services/reports.js b/src/services/reports.js index 98ff110e..0451b0cc 100644 --- a/src/services/reports.js +++ b/src/services/reports.js @@ -35,7 +35,7 @@ const createHandler = async (req, res) => { type, etcDetail, time, - roomId + roomId, }); await report.save(); @@ -46,6 +46,7 @@ const createHandler = async (req, res) => { const emailRoomName = room ? room.name : ""; const emailRoomId = room ? room._id : ""; const emailHtml = emailPage( + req.origin, reported.name, reported.nickname, emailRoomName, diff --git a/src/views/emailNoSettlementPage.js b/src/views/emailNoSettlementPage.js index 8949c27f..5143c279 100644 --- a/src/views/emailNoSettlementPage.js +++ b/src/views/emailNoSettlementPage.js @@ -1,7 +1,6 @@ -const { frontUrl } = require("../../loadenv"); const emailPage = require("./emailPage"); -module.exports = (name, nickname, roomName, payer, roomId) => +module.exports = (origin, name, nickname, roomName, payer, roomId) => emailPage( "미정산 내역 관련 안내", `${name} (${nickname}) 님께

@@ -19,12 +18,16 @@ module.exports = (name, nickname, roomName, payer, roomId) =>
위 방에서 채팅을 확인하실 수 있으며, 결제하신 분께 해당 금액을 정산해주시기를 부탁드립니다.
미정산이 반복되는 경우 Taxi 서비스 이용이 제한될 수 있음을 알려드립니다.
- 문의가 필요하신 경우, 택시 서비스 내부의 "채널톡 문의하기" 혹은 메일 회신 주시면 됩니다.

+ 문의가 필요하신 경우, 택시 서비스 내부의 "채널톡 문의하기" 혹은 메일 회신 주시면 됩니다.

감사합니다.
SPARCS Taxi팀 드림. ` diff --git a/test/auth.replace.js b/test/auth.replace.js index 330d610d..322d13ef 100644 --- a/test/auth.replace.js +++ b/test/auth.replace.js @@ -2,10 +2,9 @@ const request = require("supertest"); const authHandlers = require("../src/services/auth.replace"); const { userModel } = require("../src/modules/stores/mongo"); -const { frontUrl } = require("../loadenv"); // auth.replace.js 관련 1개의 handler을 테스트 -// 1. dev 환경에서 front의 URL로 잘 redirect 되는지 확인 +// 1. dev 환경에서 로그인이 성공적으로 이루어지는지 확인 describe("[auth.replace] 1.sparcsssoHandler", () => { const removeTestUser = async () => { // drop all collections @@ -14,14 +13,13 @@ describe("[auth.replace] 1.sparcsssoHandler", () => { before(removeTestUser); - it("should redirect to frontUrl after successful user creation", () => { + it("should redirect after successful user creation", () => { request(authHandlers.sparcsssoHandler) .post("/auth/sparcssso") .send({ id: "test", }) - .expect(302) - .expect("Location", frontUrl); + .expect(302); }); after(removeTestUser); From 286c6e494cc81c72f851622ae5c816c6c3bed16c Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Sat, 12 Aug 2023 02:14:16 +0900 Subject: [PATCH 012/261] Add: .env.hard.example --- .env.easy.example | 6 ++++++ .env.example | 14 -------------- .env.hard.example | 15 +++++++++++++++ loadenv.js | 4 ++-- 4 files changed, 23 insertions(+), 16 deletions(-) create mode 100644 .env.easy.example delete mode 100644 .env.example create mode 100644 .env.hard.example diff --git a/.env.easy.example b/.env.easy.example new file mode 100644 index 00000000..18984fc7 --- /dev/null +++ b/.env.easy.example @@ -0,0 +1,6 @@ +PORT=[back의 port(e.g. 80)] +DB_PATH=[mongoDB path(e.g. mongodb://localhost:27017/local)] +AWS_ACCESS_KEY_ID=[AWS Access key ID] +AWS_SECRET_ACCESS_KEY=[AWS Secret access key] +AWS_S3_BUCKET_NAME=[AWS S3 Bucket name] +AWS_S3_URL=[AWS S3 url(e.g. https://.s3..amazonaws.com)] diff --git a/.env.example b/.env.example deleted file mode 100644 index 303ffb31..00000000 --- a/.env.example +++ /dev/null @@ -1,14 +0,0 @@ -PORT=[back의 port(e.g. 80)] -DB_PATH=[mongoDB path(e.g. mongodb://localhost:27017/local)] -REDIS_PATH=[redis path(e.g. redis://127.0.0.1:6379)] -SESSION_KEY=[세션 비밀번호(임의 값 아무거나 설정하면 된다)] -SPARCSSSO_CLIENT_ID=[스팍스SSO ID] -SPARCSSSO_CLIENT_KEY=[스팍스SSO PW] -FRONT_URL=[front url(e.g. http://localhost:3000)] -AWS_ACCESS_KEY_ID=[AWS Access key ID] -AWS_SECRET_ACCESS_KEY=[AWS Secret access key] -AWS_S3_BUCKET_NAME=[AWS S3 Buck name] -AWS_S3_URL=[AWS S3 url(e.g. https://.s3..amazonaws.com)] -JWT_SECRET_KEY=[JWT SERCRET KEY] -APP_URI_SCHEME=[APP_URI_SCHEME] -GOOGLE_APPLICATION_CREDENTIALS=[GOOGLE APPLICATION CREDENTIALS JSON] diff --git a/.env.hard.example b/.env.hard.example new file mode 100644 index 00000000..3533dc3f --- /dev/null +++ b/.env.hard.example @@ -0,0 +1,15 @@ +PORT=[back의 port(e.g. 80)] +DB_PATH=[mongoDB path(e.g. mongodb://localhost:27017/local)] +REDIS_PATH=[redis path(e.g. redis://127.0.0.1:6379)] +SESSION_KEY=[세션 관리에 사용되는 키(임의 값 아무거나 설정하면 된다)] +JWT_SECRET_KEY=[JWT 관리에 사용되는 키(임의 값 아무거나 설정하면 된다)] +SPARCSSSO_CLIENT_ID=[스팍스SSO ID] +SPARCSSSO_CLIENT_KEY=[스팍스SSO PW] +CORS_WHITELIST=[CORS 정책에서 허용하는 도메인의 목록(e.g. ["http://localhost:3000"])] +AWS_ACCESS_KEY_ID=[AWS Access key ID] +AWS_SECRET_ACCESS_KEY=[AWS Secret access key] +AWS_S3_BUCKET_NAME=[AWS S3 Bucket name] +AWS_S3_URL=[AWS S3 url(e.g. https://.s3..amazonaws.com)] +GOOGLE_APPLICATION_CREDENTIALS=[GOOGLE_APPLICATION_CREDENTIALS JSON] +TEST_ACCOUNTS=[스팍스SSO로 로그인시 무조건 테스트로 로그인이 가능한 허용 아이디 목록] +SLACK_REPORT_WEBHOOK_URL=[Slack 웹훅 URL들이 담긴 JSON] diff --git a/loadenv.js b/loadenv.js index 35a55f5c..62c50d7f 100644 --- a/loadenv.js +++ b/loadenv.js @@ -32,9 +32,9 @@ module.exports = { }, googleApplicationCredentials: process.env.GOOGLE_APPLICATION_CREDENTIALS && - JSON.parse(process.env.GOOGLE_APPLICATION_CREDENTIALS), + JSON.parse(process.env.GOOGLE_APPLICATION_CREDENTIALS), // optional testAccounts: - process.env.TEST_ACCOUNTS && JSON.parse(process.env.TEST_ACCOUNTS), + (process.env.TEST_ACCOUNTS && JSON.parse(process.env.TEST_ACCOUNTS)) || [], // optional slackWebhookUrl: { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, From dff75752a7a2777248d0ff224f024e52b2e151a4 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Sat, 12 Aug 2023 02:38:45 +0900 Subject: [PATCH 013/261] Docs: README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0af3da05..f84b68da 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ $ npm install --save ### Set Environment Configuration See [notion page](https://www.notion.so/sparcs/Environment-Variables-1b404bd385fa495bac6d5517b57d72bf). -Refer to [.env.example](.env.example) and write your own `.env.development` and `.env.test`. +Refer to [.env.easy.example](.env.easy.example) or [.env.hard.example](.env.hard.example) and write your own `.env`. ## Backend Route Information See [Backend Route Documentation](src/routes/docs/README.md) From e18823d54b13c1acb799cb426b64ea9c6969e694 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Wed, 16 Aug 2023 22:36:53 +0900 Subject: [PATCH 014/261] Fix: .env.example --- .env.easy.example | 6 ------ .env.hard.example => .env.example | 11 +++++++---- README.md | 2 +- 3 files changed, 8 insertions(+), 11 deletions(-) delete mode 100644 .env.easy.example rename .env.hard.example => .env.example (93%) diff --git a/.env.easy.example b/.env.easy.example deleted file mode 100644 index 18984fc7..00000000 --- a/.env.easy.example +++ /dev/null @@ -1,6 +0,0 @@ -PORT=[back의 port(e.g. 80)] -DB_PATH=[mongoDB path(e.g. mongodb://localhost:27017/local)] -AWS_ACCESS_KEY_ID=[AWS Access key ID] -AWS_SECRET_ACCESS_KEY=[AWS Secret access key] -AWS_S3_BUCKET_NAME=[AWS S3 Bucket name] -AWS_S3_URL=[AWS S3 url(e.g. https://.s3..amazonaws.com)] diff --git a/.env.hard.example b/.env.example similarity index 93% rename from .env.hard.example rename to .env.example index 3533dc3f..769e0fa8 100644 --- a/.env.hard.example +++ b/.env.example @@ -1,15 +1,18 @@ +# required environment variables PORT=[back의 port(e.g. 80)] DB_PATH=[mongoDB path(e.g. mongodb://localhost:27017/local)] +AWS_ACCESS_KEY_ID=[AWS Access key ID] +AWS_SECRET_ACCESS_KEY=[AWS Secret access key] +AWS_S3_BUCKET_NAME=[AWS S3 Bucket name] +AWS_S3_URL=[AWS S3 url(e.g. https://.s3..amazonaws.com)] + +# optional environment variables REDIS_PATH=[redis path(e.g. redis://127.0.0.1:6379)] SESSION_KEY=[세션 관리에 사용되는 키(임의 값 아무거나 설정하면 된다)] JWT_SECRET_KEY=[JWT 관리에 사용되는 키(임의 값 아무거나 설정하면 된다)] SPARCSSSO_CLIENT_ID=[스팍스SSO ID] SPARCSSSO_CLIENT_KEY=[스팍스SSO PW] CORS_WHITELIST=[CORS 정책에서 허용하는 도메인의 목록(e.g. ["http://localhost:3000"])] -AWS_ACCESS_KEY_ID=[AWS Access key ID] -AWS_SECRET_ACCESS_KEY=[AWS Secret access key] -AWS_S3_BUCKET_NAME=[AWS S3 Bucket name] -AWS_S3_URL=[AWS S3 url(e.g. https://.s3..amazonaws.com)] GOOGLE_APPLICATION_CREDENTIALS=[GOOGLE_APPLICATION_CREDENTIALS JSON] TEST_ACCOUNTS=[스팍스SSO로 로그인시 무조건 테스트로 로그인이 가능한 허용 아이디 목록] SLACK_REPORT_WEBHOOK_URL=[Slack 웹훅 URL들이 담긴 JSON] diff --git a/README.md b/README.md index f84b68da..fbd01b8a 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ $ npm install --save ### Set Environment Configuration See [notion page](https://www.notion.so/sparcs/Environment-Variables-1b404bd385fa495bac6d5517b57d72bf). -Refer to [.env.easy.example](.env.easy.example) or [.env.hard.example](.env.hard.example) and write your own `.env`. +Refer to [.env.example](.env.example) and write your own `.env`. ## Backend Route Information See [Backend Route Documentation](src/routes/docs/README.md) From 30902e74a94f6537a77e2289fa101f2fbd432bb5 Mon Sep 17 00:00:00 2001 From: withsang Date: Wed, 16 Aug 2023 23:46:19 +0900 Subject: [PATCH 015/261] Fix: sync lockfile with package.json --- pnpm-lock.yaml | 182 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7bf55d4e..2249f8ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,6 +77,9 @@ dependencies: mongoose: specifier: ^6.11.3 version: 6.11.5 + node-cron: + specifier: 3.0.2 + version: 3.0.2 node-mocks-http: specifier: ^1.12.1 version: 1.12.2 @@ -217,6 +220,7 @@ packages: /@aws-crypto/crc32@3.0.0: resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==} + requiresBuild: true dependencies: '@aws-crypto/util': 3.0.0 '@aws-sdk/types': 3.378.0 @@ -226,6 +230,7 @@ packages: /@aws-crypto/ie11-detection@3.0.0: resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} + requiresBuild: true dependencies: tslib: 1.14.1 dev: false @@ -233,6 +238,7 @@ packages: /@aws-crypto/sha256-browser@3.0.0: resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==} + requiresBuild: true dependencies: '@aws-crypto/ie11-detection': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 @@ -247,6 +253,7 @@ packages: /@aws-crypto/sha256-js@3.0.0: resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} + requiresBuild: true dependencies: '@aws-crypto/util': 3.0.0 '@aws-sdk/types': 3.378.0 @@ -256,6 +263,7 @@ packages: /@aws-crypto/supports-web-crypto@3.0.0: resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==} + requiresBuild: true dependencies: tslib: 1.14.1 dev: false @@ -263,6 +271,7 @@ packages: /@aws-crypto/util@3.0.0: resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} + requiresBuild: true dependencies: '@aws-sdk/types': 3.378.0 '@aws-sdk/util-utf8-browser': 3.259.0 @@ -273,6 +282,7 @@ packages: /@aws-sdk/client-cognito-identity@3.385.0: resolution: {integrity: sha512-fRXZhxvBBeK/Jxb+sLPhyQmcduNSugSKJDz474A/wLK5UIuDOnKhDTjsa0OXMpY5DkqwdYLwDcGZtxUbEZ8DCQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 @@ -318,6 +328,7 @@ packages: /@aws-sdk/client-sso@3.382.0: resolution: {integrity: sha512-ge11t4hJllOF8pBNF0p1X52lLqUsLGAoey24fvk3fyvvczeLpegGYh2kdLG0iwFTDgRxaUqK+kboH5Wy9ux/pw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 @@ -360,6 +371,7 @@ packages: /@aws-sdk/client-sts@3.385.0: resolution: {integrity: sha512-VdSDwICW2cBttbdj1izu6VYflJbZZKu3/FSaJGuGu8SgTvRsa56g6E5xfbUfR/SCstuETObKLusSfQZ6yxUnzA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 @@ -406,6 +418,7 @@ packages: /@aws-sdk/credential-provider-cognito-identity@3.385.0: resolution: {integrity: sha512-NeWJgI2XdfO0ZM25KsfNx9CDmLByY3ymVc0ae4Os+bd8pJsFeo1rX3NSkyw8XGryEbOlVJ3Jz5W5huhjo4LvqQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/client-cognito-identity': 3.385.0 '@aws-sdk/types': 3.378.0 @@ -420,6 +433,7 @@ packages: /@aws-sdk/credential-provider-env@3.378.0: resolution: {integrity: sha512-B2OVdO9kBClDwGgWTBLAQwFV8qYTYGyVujg++1FZFSFMt8ORFdZ5fNpErvJtiSjYiOOQMzyBeSNhKyYNXCiJjQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.378.0 '@smithy/property-provider': 2.0.1 @@ -431,6 +445,7 @@ packages: /@aws-sdk/credential-provider-ini@3.385.0: resolution: {integrity: sha512-WBIR5GdfUzCGzynQYX/TuCXw3KJCkHBk6bVAsO1YmfR68XKVAxWmJPKovlK/rR6LIuV+iwUMNludO+SkmG0efg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/credential-provider-env': 3.378.0 '@aws-sdk/credential-provider-process': 3.378.0 @@ -450,6 +465,7 @@ packages: /@aws-sdk/credential-provider-node@3.385.0: resolution: {integrity: sha512-Lk8uu6jm/8OkbLX4Qnss8o5bnt0yQa0Tb7Azbh5/5otju5kStVAD2E+zMGrMP++NriGyZV87crduh0J8l4JUTA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/credential-provider-env': 3.378.0 '@aws-sdk/credential-provider-ini': 3.385.0 @@ -470,6 +486,7 @@ packages: /@aws-sdk/credential-provider-process@3.378.0: resolution: {integrity: sha512-KFTIy7u+wXj3eDua4rgS0tODzMnXtXhAm1RxzCW9FL5JLBBrd82ymCj1Dp72217Sw5Do6NjCnDTTNkCHZMA77w==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.378.0 '@smithy/property-provider': 2.0.1 @@ -482,6 +499,7 @@ packages: /@aws-sdk/credential-provider-sso@3.385.0: resolution: {integrity: sha512-ETFnS+4ZKTAgT8boVpIpRuXA9wWGpNqOcI1RXtjsaIgQ9s8uNn2JPa8l71gZh861mzBC8Hadp1EpNu+43w4lkg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/client-sso': 3.382.0 '@aws-sdk/token-providers': 3.385.0 @@ -498,6 +516,7 @@ packages: /@aws-sdk/credential-provider-web-identity@3.378.0: resolution: {integrity: sha512-GWjydOszhc4xDF8xuPtBvboglXQr0gwCW1oHAvmLcOT38+Hd6qnKywnMSeoXYRPgoKfF9TkWQgW1jxplzCG0UA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.378.0 '@smithy/property-provider': 2.0.1 @@ -534,6 +553,7 @@ packages: /@aws-sdk/middleware-host-header@3.379.1: resolution: {integrity: sha512-LI4KpAFWNWVr2aH2vRVblr0Y8tvDz23lj8LOmbDmCrzd5M21nxuocI/8nEAQj55LiTIf9Zs+dHCdsyegnFXdrA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.378.0 '@smithy/protocol-http': 2.0.1 @@ -545,6 +565,7 @@ packages: /@aws-sdk/middleware-logger@3.378.0: resolution: {integrity: sha512-l1DyaDLm3KeBMNMuANI3scWh8Xvu248x+vw6Z7ExWOhGXFmQ1MW7YvASg/SdxWkhlF9HmkkTif1LdMB22x6QDA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.378.0 '@smithy/types': 2.0.2 @@ -555,6 +576,7 @@ packages: /@aws-sdk/middleware-recursion-detection@3.378.0: resolution: {integrity: sha512-mUMfHAz0oGNIWiTZHTVJb+I515Hqs2zx1j36Le4MMiiaMkPW1SRUF1FIwGuc1wh6E8jB5q+XfEMriDjRi4TZRA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.378.0 '@smithy/protocol-http': 2.0.1 @@ -566,6 +588,7 @@ packages: /@aws-sdk/middleware-sdk-sts@3.379.1: resolution: {integrity: sha512-SK3gSyT0XbLiY12+AjLFYL9YngxOXHnZF3Z33Cdd4a+AUYrVBV7JBEEGD1Nlwrcmko+3XgaKlmgUaR5s91MYvg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/middleware-signing': 3.379.1 '@aws-sdk/types': 3.378.0 @@ -577,6 +600,7 @@ packages: /@aws-sdk/middleware-signing@3.379.1: resolution: {integrity: sha512-kBk2ZUvR84EM4fICjr8K+Ykpf8SI1UzzPp2/UVYZ0X+4H/ZCjfSqohGRwHykMqeplne9qHSL7/rGJs1H3l3gPg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.378.0 '@smithy/property-provider': 2.0.1 @@ -591,6 +615,7 @@ packages: /@aws-sdk/middleware-user-agent@3.382.0: resolution: {integrity: sha512-LFRW1jmXOrOAd3911ktn6oaYmuurNnulbdRMOUdwz99GGdLVFipQhOi9idKswb8IOhPa4jEVQt25Kcv7ctvu0A==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.378.0 '@aws-sdk/util-endpoints': 3.382.0 @@ -603,6 +628,7 @@ packages: /@aws-sdk/token-providers@3.385.0: resolution: {integrity: sha512-2A2Y7/bU5EaxQwLwLy7ojs+Wy5VOBkIlGPH7ZcpPaoQ1Hscwn3Wvx/DZmOvbyYfZ1CbIFutoHJlVxh6KZldUDw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.378.0 '@smithy/property-provider': 2.0.1 @@ -615,6 +641,7 @@ packages: /@aws-sdk/types@3.378.0: resolution: {integrity: sha512-qP0CvR/ItgktmN8YXpGQglzzR/6s0nrsQ4zIfx3HMwpsBTwuouYahcCtF1Vr82P4NFcoDA412EJahJ2pIqEd+w==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.0.2 tslib: 2.6.1 @@ -624,6 +651,7 @@ packages: /@aws-sdk/util-endpoints@3.382.0: resolution: {integrity: sha512-flajPyjmjNG67fXk7l4GoTB/7J11VBqtFZXuuAZKhKU07Ia3IQupsFqNf5lV8D44ZgjnKH0fTGnv3dUALjW7Wg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.378.0 tslib: 2.6.1 @@ -633,6 +661,7 @@ packages: /@aws-sdk/util-locate-window@3.310.0: resolution: {integrity: sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.1 dev: false @@ -640,6 +669,7 @@ packages: /@aws-sdk/util-user-agent-browser@3.378.0: resolution: {integrity: sha512-FSCpagzftK1W+m7Ar6lpX7/Gr9y5P56nhFYz8U4EYQ4PkufS6czWX9YW+/FA5OYV0vlQ/SvPqMnzoHIPUNhZrQ==} + requiresBuild: true dependencies: '@aws-sdk/types': 3.378.0 '@smithy/types': 2.0.2 @@ -651,6 +681,7 @@ packages: /@aws-sdk/util-user-agent-node@3.378.0: resolution: {integrity: sha512-IdwVJV0E96MkJeFte4dlWqvB+oiqCiZ5lOlheY3W9NynTuuX0GGYNC8Y9yIsV8Oava1+ujpJq0ww6qXdYxmO4A==} engines: {node: '>=14.0.0'} + requiresBuild: true peerDependencies: aws-crt: '>=1.0.0' peerDependenciesMeta: @@ -666,6 +697,7 @@ packages: /@aws-sdk/util-utf8-browser@3.259.0: resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} + requiresBuild: true dependencies: tslib: 2.6.1 dev: false @@ -2255,6 +2287,7 @@ packages: /@google-cloud/paginator@3.0.7: resolution: {integrity: sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==} engines: {node: '>=10'} + requiresBuild: true dependencies: arrify: 2.0.1 extend: 3.0.2 @@ -2264,12 +2297,14 @@ packages: /@google-cloud/projectify@3.0.0: resolution: {integrity: sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==} engines: {node: '>=12.0.0'} + requiresBuild: true dev: false optional: true /@google-cloud/promisify@3.0.1: resolution: {integrity: sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==} engines: {node: '>=12'} + requiresBuild: true dev: false optional: true @@ -2305,6 +2340,7 @@ packages: /@grpc/grpc-js@1.8.21: resolution: {integrity: sha512-KeyQeZpxeEBSqFVTi3q2K7PiPXmgBfECc4updA1ejCLjYmoAlvvM3ZMp5ztTDUCUQmoY3CpDxvchjO1+rFkoHg==} engines: {node: ^8.13.0 || >=10.10.0} + requiresBuild: true dependencies: '@grpc/proto-loader': 0.7.8 '@types/node': 20.4.7 @@ -2315,6 +2351,7 @@ packages: resolution: {integrity: sha512-GU12e2c8dmdXb7XUlOgYWZ2o2i+z9/VeACkxTA/zzAe2IjclC5PnVL0lpgjhrqfpDYHzM8B1TF6pqWegMYAzlA==} engines: {node: '>=6'} hasBin: true + requiresBuild: true dependencies: '@types/long': 4.0.2 lodash.camelcase: 4.3.0 @@ -2417,6 +2454,7 @@ packages: /@jsdoc/salty@0.2.5: resolution: {integrity: sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==} engines: {node: '>=v12.0.0'} + requiresBuild: true dependencies: lodash: 4.17.21 dev: false @@ -2446,26 +2484,31 @@ packages: /@protobufjs/aspromise@1.1.2: resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + requiresBuild: true dev: false optional: true /@protobufjs/base64@1.1.2: resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + requiresBuild: true dev: false optional: true /@protobufjs/codegen@2.0.4: resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + requiresBuild: true dev: false optional: true /@protobufjs/eventemitter@1.1.0: resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + requiresBuild: true dev: false optional: true /@protobufjs/fetch@1.1.0: resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + requiresBuild: true dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/inquire': 1.1.0 @@ -2474,26 +2517,31 @@ packages: /@protobufjs/float@1.0.2: resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + requiresBuild: true dev: false optional: true /@protobufjs/inquire@1.1.0: resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + requiresBuild: true dev: false optional: true /@protobufjs/path@1.1.2: resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + requiresBuild: true dev: false optional: true /@protobufjs/pool@1.1.0: resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + requiresBuild: true dev: false optional: true /@protobufjs/utf8@1.1.0: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + requiresBuild: true dev: false optional: true @@ -2661,6 +2709,7 @@ packages: /@smithy/abort-controller@2.0.1: resolution: {integrity: sha512-0s7XjIbsTwZyUW9OwXQ8J6x1UiA1TNCh60Vaw56nHahL7kUZsLhmTlWiaxfLkFtO2Utkj8YewcpHTYpxaTzO+w==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.0.2 tslib: 2.6.1 @@ -2670,6 +2719,7 @@ packages: /@smithy/config-resolver@2.0.1: resolution: {integrity: sha512-l83Pm7hV+8CBQOCmBRopWDtF+CURUJol7NsuPYvimiDhkC2F8Ba9T1imSFE+pD1UIJ9jlsDPAnZfPJT5cjnuEw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.0.2 '@smithy/util-config-provider': 2.0.0 @@ -2681,6 +2731,7 @@ packages: /@smithy/credential-provider-imds@2.0.1: resolution: {integrity: sha512-8VxriuRINNEfVZjEFKBY75y9ZWAx73DZ5K/u+3LmB6r8WR2h3NaFxFKMlwlq0uzNdGhD1ouKBn9XWEGYHKiPLw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/node-config-provider': 2.0.1 '@smithy/property-provider': 2.0.1 @@ -2692,6 +2743,7 @@ packages: /@smithy/eventstream-codec@2.0.1: resolution: {integrity: sha512-/IiNB7gQM2y2ZC/GAWOWDa8+iXfhr1g9Xe5979cQEOdCWDISvrAiv18cn3OtIQUhbYOR3gm7QtCpkq1to2takQ==} + requiresBuild: true dependencies: '@aws-crypto/crc32': 3.0.0 '@smithy/types': 2.0.2 @@ -2702,6 +2754,7 @@ packages: /@smithy/fetch-http-handler@2.0.1: resolution: {integrity: sha512-/SoU/ClazgcdOxgE4zA7RX8euiELwpsrKCSvulVQvu9zpmqJRyEJn8ZTWYFV17/eHOBdHTs9kqodhNhsNT+cUw==} + requiresBuild: true dependencies: '@smithy/protocol-http': 2.0.1 '@smithy/querystring-builder': 2.0.1 @@ -2714,6 +2767,7 @@ packages: /@smithy/hash-node@2.0.1: resolution: {integrity: sha512-oTKYimQdF4psX54ZonpcIE+MXjMUWFxLCNosjPkJPFQ9whRX0K/PFX/+JZGRQh3zO9RlEOEUIbhy9NO+Wha6hw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.0.2 '@smithy/util-buffer-from': 2.0.0 @@ -2724,6 +2778,7 @@ packages: /@smithy/invalid-dependency@2.0.1: resolution: {integrity: sha512-2q/Eb0AE662zwyMV+z+TL7deBwcHCgaZZGc0RItamBE8kak3MzCi/EZCNoFWoBfxgQ4jfR12wm8KKsSXhJzJtQ==} + requiresBuild: true dependencies: '@smithy/types': 2.0.2 tslib: 2.6.1 @@ -2733,6 +2788,7 @@ packages: /@smithy/is-array-buffer@2.0.0: resolution: {integrity: sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.1 dev: false @@ -2741,6 +2797,7 @@ packages: /@smithy/middleware-content-length@2.0.1: resolution: {integrity: sha512-IZhRSk5GkVBcrKaqPXddBS2uKhaqwBgaSgbBb1OJyGsKe7SxRFbclWS0LqOR9fKUkDl+3lL8E2ffpo6EQg0igw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/protocol-http': 2.0.1 '@smithy/types': 2.0.2 @@ -2751,6 +2808,7 @@ packages: /@smithy/middleware-endpoint@2.0.1: resolution: {integrity: sha512-uz/KI1MBd9WHrrkVFZO4L4Wyv24raf0oR4EsOYEeG5jPJO5U+C7MZGLcMxX8gWERDn1sycBDqmGv8fjUMLxT6w==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/middleware-serde': 2.0.1 '@smithy/types': 2.0.2 @@ -2763,6 +2821,7 @@ packages: /@smithy/middleware-retry@2.0.1: resolution: {integrity: sha512-NKHF4i0gjSyjO6C0ZyjEpNqzGgIu7s8HOK6oT/1Jqws2Q1GynR1xV8XTUs1gKXeaNRzbzKQRewHHmfPwZjOtHA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/protocol-http': 2.0.1 '@smithy/service-error-classification': 2.0.0 @@ -2777,6 +2836,7 @@ packages: /@smithy/middleware-serde@2.0.1: resolution: {integrity: sha512-uKxPaC6ItH9ZXdpdqNtf8sda7GcU4SPMp0tomq/5lUg9oiMa/Q7+kD35MUrpKaX3IVXVrwEtkjCU9dogZ/RAUA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.0.2 tslib: 2.6.1 @@ -2786,6 +2846,7 @@ packages: /@smithy/middleware-stack@2.0.0: resolution: {integrity: sha512-31XC1xNF65nlbc16yuh3wwTudmqs6qy4EseQUGF8A/p2m/5wdd/cnXJqpniy/XvXVwkHPz/GwV36HqzHtIKATQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.1 dev: false @@ -2794,6 +2855,7 @@ packages: /@smithy/node-config-provider@2.0.1: resolution: {integrity: sha512-Zoel4CPkKRTQ2XxmozZUfqBYqjPKL53/SvTDhJHj+VBSiJy6MXRav1iDCyFPS92t40Uh+Yi+Km5Ch3hQ+c/zSA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/property-provider': 2.0.1 '@smithy/shared-ini-file-loader': 2.0.1 @@ -2805,6 +2867,7 @@ packages: /@smithy/node-http-handler@2.0.1: resolution: {integrity: sha512-Zv3fxk3p9tsmPT2CKMsbuwbbxnq2gzLDIulxv+yI6aE+02WPYorObbbe9gh7SW3weadMODL1vTfOoJ9yFypDzg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/abort-controller': 2.0.1 '@smithy/protocol-http': 2.0.1 @@ -2817,6 +2880,7 @@ packages: /@smithy/property-provider@2.0.1: resolution: {integrity: sha512-pmJRyY9SF6sutWIktIhe+bUdSQDxv/qZ4mYr3/u+u45riTPN7nmRxPo+e4sjWVoM0caKFjRSlj3tf5teRFy0Vg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.0.2 tslib: 2.6.1 @@ -2826,6 +2890,7 @@ packages: /@smithy/protocol-http@2.0.1: resolution: {integrity: sha512-mrkMAp0wtaDEIkgRObWYxI1Kun1tm6Iu6rK+X4utb6Ah7Uc3Kk4VIWwK/rBHdYGReiLIrxFCB1rq4a2gyZnSgg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.0.2 tslib: 2.6.1 @@ -2835,6 +2900,7 @@ packages: /@smithy/querystring-builder@2.0.1: resolution: {integrity: sha512-bp+93WFzx1FojVEIeFPtG0A1pKsFdCUcZvVdZdRlmNooOUrz9Mm9bneRd8hDwAQ37pxiZkCOxopSXXRQN10mYw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.0.2 '@smithy/util-uri-escape': 2.0.0 @@ -2845,6 +2911,7 @@ packages: /@smithy/querystring-parser@2.0.1: resolution: {integrity: sha512-h+e7k1z+IvI2sSbUBG9Aq46JsgLl4UqIUl6aigAlRBj+P6ocNXpM6Yn1vMBw5ijtXeZbYpd1YvCxwDgdw3jhmg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.0.2 tslib: 2.6.1 @@ -2854,12 +2921,14 @@ packages: /@smithy/service-error-classification@2.0.0: resolution: {integrity: sha512-2z5Nafy1O0cTf69wKyNjGW/sNVMiqDnb4jgwfMG8ye8KnFJ5qmJpDccwIbJNhXIfbsxTg9SEec2oe1cexhMJvw==} engines: {node: '>=14.0.0'} + requiresBuild: true dev: false optional: true /@smithy/shared-ini-file-loader@2.0.1: resolution: {integrity: sha512-a463YiZrPGvM+F336rIF8pLfQsHAdCRAn/BiI/EWzg5xLoxbC7GSxIgliDDXrOu0z8gT3nhVsif85eU6jyct3A==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.0.2 tslib: 2.6.1 @@ -2869,6 +2938,7 @@ packages: /@smithy/signature-v4@2.0.1: resolution: {integrity: sha512-jztv5Mirca42ilxmMDjzLdXcoAmRhZskGafGL49sRo5u7swEZcToEFrq6vtX5YMbSyTVrE9Teog5EFexY5Ff2Q==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/eventstream-codec': 2.0.1 '@smithy/is-array-buffer': 2.0.0 @@ -2884,6 +2954,7 @@ packages: /@smithy/smithy-client@2.0.1: resolution: {integrity: sha512-LHC5m6tYpEu1iNbONfvMbwtErboyTZJfEIPoD78Ei5MVr36vZQCaCla5mvo36+q/a2NAk2//fA5Rx3I1Kf7+lQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/middleware-stack': 2.0.0 '@smithy/types': 2.0.2 @@ -2895,6 +2966,7 @@ packages: /@smithy/types@2.0.2: resolution: {integrity: sha512-wcymEjIXQ9+NEfE5Yt5TInAqe1o4n+Nh+rh00AwoazppmUt8tdo6URhc5gkDcOYrcvlDVAZE7uG69nDpEGUKxw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.1 dev: false @@ -2902,6 +2974,7 @@ packages: /@smithy/url-parser@2.0.1: resolution: {integrity: sha512-NpHVOAwddo+OyyIoujDL9zGL96piHWrTNXqltWmBvlUoWgt1HPyBuKs6oHjioyFnNZXUqveTOkEEq0U5w6Uv8A==} + requiresBuild: true dependencies: '@smithy/querystring-parser': 2.0.1 '@smithy/types': 2.0.2 @@ -2912,6 +2985,7 @@ packages: /@smithy/util-base64@2.0.0: resolution: {integrity: sha512-Zb1E4xx+m5Lud8bbeYi5FkcMJMnn+1WUnJF3qD7rAdXpaL7UjkFQLdmW5fHadoKbdHpwH9vSR8EyTJFHJs++tA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/util-buffer-from': 2.0.0 tslib: 2.6.1 @@ -2920,6 +2994,7 @@ packages: /@smithy/util-body-length-browser@2.0.0: resolution: {integrity: sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==} + requiresBuild: true dependencies: tslib: 2.6.1 dev: false @@ -2928,6 +3003,7 @@ packages: /@smithy/util-body-length-node@2.0.0: resolution: {integrity: sha512-ZV7Z/WHTMxHJe/xL/56qZwSUcl63/5aaPAGjkfynJm4poILjdD4GmFI+V+YWabh2WJIjwTKZ5PNsuvPQKt93Mg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.1 dev: false @@ -2936,6 +3012,7 @@ packages: /@smithy/util-buffer-from@2.0.0: resolution: {integrity: sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/is-array-buffer': 2.0.0 tslib: 2.6.1 @@ -2945,6 +3022,7 @@ packages: /@smithy/util-config-provider@2.0.0: resolution: {integrity: sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.1 dev: false @@ -2953,6 +3031,7 @@ packages: /@smithy/util-defaults-mode-browser@2.0.1: resolution: {integrity: sha512-w72Qwsb+IaEYEFtYICn0Do42eFju78hTaBzzJfT107lFOPdbjWjKnFutV+6GL/nZd5HWXY7ccAKka++C3NrjHw==} engines: {node: '>= 10.0.0'} + requiresBuild: true dependencies: '@smithy/property-provider': 2.0.1 '@smithy/types': 2.0.2 @@ -2964,6 +3043,7 @@ packages: /@smithy/util-defaults-mode-node@2.0.1: resolution: {integrity: sha512-dNF45caelEBambo0SgkzQ0v76m4YM+aFKZNTtSafy7P5dVF8TbjZuR2UX1A5gJABD9XK6lzN+v/9Yfzj/EDgGg==} engines: {node: '>= 10.0.0'} + requiresBuild: true dependencies: '@smithy/config-resolver': 2.0.1 '@smithy/credential-provider-imds': 2.0.1 @@ -2977,6 +3057,7 @@ packages: /@smithy/util-hex-encoding@2.0.0: resolution: {integrity: sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.1 dev: false @@ -2985,6 +3066,7 @@ packages: /@smithy/util-middleware@2.0.0: resolution: {integrity: sha512-eCWX4ECuDHn1wuyyDdGdUWnT4OGyIzV0LN1xRttBFMPI9Ff/4heSHVxneyiMtOB//zpXWCha1/SWHJOZstG7kA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.1 dev: false @@ -2993,6 +3075,7 @@ packages: /@smithy/util-retry@2.0.0: resolution: {integrity: sha512-/dvJ8afrElasuiiIttRJeoS2sy8YXpksQwiM/TcepqdRVp7u4ejd9C4IQURHNjlfPUT7Y6lCDSa2zQJbdHhVTg==} engines: {node: '>= 14.0.0'} + requiresBuild: true dependencies: '@smithy/service-error-classification': 2.0.0 tslib: 2.6.1 @@ -3002,6 +3085,7 @@ packages: /@smithy/util-stream@2.0.1: resolution: {integrity: sha512-2a0IOtwIKC46EEo7E7cxDN8u2jwOiYYJqcFKA6rd5rdXqKakHT2Gc+AqHWngr0IEHUfW92zX12wRQKwyoqZf2Q==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/fetch-http-handler': 2.0.1 '@smithy/node-http-handler': 2.0.1 @@ -3017,6 +3101,7 @@ packages: /@smithy/util-uri-escape@2.0.0: resolution: {integrity: sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.1 dev: false @@ -3025,6 +3110,7 @@ packages: /@smithy/util-utf8@2.0.0: resolution: {integrity: sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/util-buffer-from': 2.0.0 tslib: 2.6.1 @@ -3487,6 +3573,7 @@ packages: /@tootallnate/once@2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} + requiresBuild: true dev: false optional: true @@ -3576,6 +3663,7 @@ packages: /@types/glob@8.1.0: resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==} + requiresBuild: true dependencies: '@types/minimatch': 5.1.2 '@types/node': 20.4.7 @@ -3601,16 +3689,19 @@ packages: /@types/linkify-it@3.0.2: resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==} + requiresBuild: true dev: false optional: true /@types/long@4.0.2: resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} + requiresBuild: true dev: false optional: true /@types/markdown-it@12.2.3: resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==} + requiresBuild: true dependencies: '@types/linkify-it': 3.0.2 '@types/mdurl': 1.0.2 @@ -3619,6 +3710,7 @@ packages: /@types/mdurl@1.0.2: resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} + requiresBuild: true dev: false optional: true @@ -3628,6 +3720,7 @@ packages: /@types/minimatch@5.1.2: resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} + requiresBuild: true dev: false optional: true @@ -3681,6 +3774,7 @@ packages: /@types/rimraf@3.0.2: resolution: {integrity: sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==} + requiresBuild: true dependencies: '@types/glob': 8.1.0 '@types/node': 20.4.7 @@ -3736,6 +3830,7 @@ packages: /abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + requiresBuild: true dependencies: event-target-shim: 5.0.1 dev: false @@ -3816,6 +3911,7 @@ packages: /agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} + requiresBuild: true dependencies: debug: 4.3.4 transitivePeerDependencies: @@ -3903,6 +3999,7 @@ packages: /arrify@2.0.1: resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} engines: {node: '>=8'} + requiresBuild: true dev: false optional: true @@ -3925,6 +4022,7 @@ packages: /async-retry@1.3.3: resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} + requiresBuild: true dependencies: retry: 0.13.1 dev: false @@ -4042,6 +4140,7 @@ packages: /bignumber.js@9.1.1: resolution: {integrity: sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==} + requiresBuild: true dev: false optional: true @@ -4060,6 +4159,7 @@ packages: /bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + requiresBuild: true dev: false optional: true @@ -4089,6 +4189,7 @@ packages: /bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + requiresBuild: true dev: false optional: true @@ -4195,6 +4296,7 @@ packages: /catharsis@0.9.0: resolution: {integrity: sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==} engines: {node: '>= 10'} + requiresBuild: true dependencies: lodash: 4.17.21 dev: false @@ -4280,6 +4382,7 @@ packages: /cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + requiresBuild: true dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 @@ -4372,6 +4475,7 @@ packages: /compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} + requiresBuild: true dependencies: mime-db: 1.52.0 dev: false @@ -4684,6 +4788,7 @@ packages: /duplexify@4.1.2: resolution: {integrity: sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==} + requiresBuild: true dependencies: end-of-stream: 1.4.4 inherits: 2.0.4 @@ -4720,6 +4825,7 @@ packages: /end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + requiresBuild: true dependencies: once: 1.4.0 dev: false @@ -4752,11 +4858,13 @@ packages: /ent@2.2.0: resolution: {integrity: sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==} + requiresBuild: true dev: false optional: true /entities@2.1.0: resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==} + requiresBuild: true dev: false optional: true @@ -4791,6 +4899,7 @@ packages: /escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} + requiresBuild: true dev: false optional: true @@ -4802,6 +4911,7 @@ packages: resolution: {integrity: sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==} engines: {node: '>=4.0'} hasBin: true + requiresBuild: true dependencies: esprima: 4.0.1 estraverse: 4.3.0 @@ -4915,6 +5025,7 @@ packages: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true + requiresBuild: true dev: false optional: true @@ -4933,6 +5044,7 @@ packages: /estraverse@4.3.0: resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} engines: {node: '>=4.0'} + requiresBuild: true dev: false optional: true @@ -4960,6 +5072,7 @@ packages: /event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} + requiresBuild: true dev: false optional: true @@ -5049,6 +5162,7 @@ packages: /extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + requiresBuild: true dev: false optional: true @@ -5077,12 +5191,14 @@ packages: /fast-text-encoding@1.0.6: resolution: {integrity: sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==} + requiresBuild: true dev: false optional: true /fast-xml-parser@4.2.5: resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} hasBin: true + requiresBuild: true dependencies: strnum: 1.0.5 dev: false @@ -5091,6 +5207,7 @@ packages: /fast-xml-parser@4.2.7: resolution: {integrity: sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig==} hasBin: true + requiresBuild: true dependencies: strnum: 1.0.5 dev: false @@ -5277,6 +5394,7 @@ packages: /gaxios@5.1.3: resolution: {integrity: sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==} engines: {node: '>=12'} + requiresBuild: true dependencies: extend: 3.0.2 https-proxy-agent: 5.0.1 @@ -5291,6 +5409,7 @@ packages: /gcp-metadata@5.3.0: resolution: {integrity: sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==} engines: {node: '>=12'} + requiresBuild: true dependencies: gaxios: 5.1.3 json-bigint: 1.0.0 @@ -5362,6 +5481,7 @@ packages: /glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} + requiresBuild: true dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -5396,6 +5516,7 @@ packages: /google-auth-library@8.9.0: resolution: {integrity: sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==} engines: {node: '>=12'} + requiresBuild: true dependencies: arrify: 2.0.1 base64-js: 1.5.1 @@ -5416,6 +5537,7 @@ packages: resolution: {integrity: sha512-g/lcUjGcB6DSw2HxgEmCDOrI/CByOwqRvsuUvNalHUK2iPPPlmAIpbMbl62u0YufGMr8zgE3JL7th6dCb1Ry+w==} engines: {node: '>=12'} hasBin: true + requiresBuild: true dependencies: '@grpc/grpc-js': 1.8.21 '@grpc/proto-loader': 0.7.8 @@ -5442,6 +5564,7 @@ packages: resolution: {integrity: sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==} engines: {node: '>=12.0.0'} hasBin: true + requiresBuild: true dependencies: node-forge: 1.3.1 dev: false @@ -5455,6 +5578,7 @@ packages: /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + requiresBuild: true dev: false optional: true @@ -5464,6 +5588,7 @@ packages: /gtoken@6.1.2: resolution: {integrity: sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==} engines: {node: '>=12.0.0'} + requiresBuild: true dependencies: gaxios: 5.1.3 google-p12-pem: 4.0.1 @@ -5547,6 +5672,7 @@ packages: /http-proxy-agent@5.0.0: resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} engines: {node: '>= 6'} + requiresBuild: true dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 @@ -5559,6 +5685,7 @@ packages: /https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} + requiresBuild: true dependencies: agent-base: 6.0.2 debug: 4.3.4 @@ -5723,6 +5850,7 @@ packages: /is-stream-ended@0.1.4: resolution: {integrity: sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==} + requiresBuild: true dev: false optional: true @@ -5784,6 +5912,7 @@ packages: /js2xmlparser@4.0.2: resolution: {integrity: sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==} + requiresBuild: true dependencies: xmlcreate: 2.0.4 dev: false @@ -5793,6 +5922,7 @@ packages: resolution: {integrity: sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==} engines: {node: '>=12.0.0'} hasBin: true + requiresBuild: true dependencies: '@babel/parser': 7.22.7 '@jsdoc/salty': 0.2.5 @@ -5825,6 +5955,7 @@ packages: /json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + requiresBuild: true dependencies: bignumber.js: 9.1.1 dev: false @@ -5890,6 +6021,7 @@ packages: /jwa@2.0.0: resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} + requiresBuild: true dependencies: buffer-equal-constant-time: 1.0.1 ecdsa-sig-formatter: 1.0.11 @@ -5920,6 +6052,7 @@ packages: /jws@4.0.0: resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} + requiresBuild: true dependencies: jwa: 2.0.0 safe-buffer: 5.2.1 @@ -5938,6 +6071,7 @@ packages: /klaw@3.0.0: resolution: {integrity: sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==} + requiresBuild: true dependencies: graceful-fs: 4.2.11 dev: false @@ -5957,6 +6091,7 @@ packages: /levn@0.3.0: resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} engines: {node: '>= 0.8.0'} + requiresBuild: true dependencies: prelude-ls: 1.1.2 type-check: 0.3.2 @@ -5980,6 +6115,7 @@ packages: /linkify-it@3.0.3: resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==} + requiresBuild: true dependencies: uc.micro: 1.0.6 dev: false @@ -6011,6 +6147,7 @@ packages: /lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + requiresBuild: true dev: false optional: true @@ -6089,11 +6226,13 @@ packages: /long@4.0.0: resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} + requiresBuild: true dev: false optional: true /long@5.2.3: resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + requiresBuild: true dev: false optional: true @@ -6156,6 +6295,7 @@ packages: /markdown-it-anchor@8.6.7(@types/markdown-it@12.2.3)(markdown-it@12.3.2): resolution: {integrity: sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==} + requiresBuild: true peerDependencies: '@types/markdown-it': '*' markdown-it: '*' @@ -6168,6 +6308,7 @@ packages: /markdown-it@12.3.2: resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==} hasBin: true + requiresBuild: true dependencies: argparse: 2.0.1 entities: 2.1.0 @@ -6192,6 +6333,7 @@ packages: resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} engines: {node: '>= 12'} hasBin: true + requiresBuild: true dev: false optional: true @@ -6210,6 +6352,7 @@ packages: /memory-pager@1.5.0: resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + requiresBuild: true dev: false optional: true @@ -6262,6 +6405,7 @@ packages: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} hasBin: true + requiresBuild: true dev: false optional: true @@ -6289,6 +6433,7 @@ packages: /minimatch@5.1.6: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} + requiresBuild: true dependencies: brace-expansion: 2.0.1 dev: false @@ -6296,6 +6441,7 @@ packages: /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + requiresBuild: true dev: false optional: true @@ -6303,6 +6449,7 @@ packages: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true + requiresBuild: true dev: false optional: true @@ -6413,9 +6560,17 @@ packages: engines: {node: '>= 0.6'} dev: false + /node-cron@3.0.2: + resolution: {integrity: sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==} + engines: {node: '>=6.0.0'} + dependencies: + uuid: 8.3.2 + dev: false + /node-fetch@2.6.12: resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} engines: {node: 4.x || >=6.0.0} + requiresBuild: true peerDependencies: encoding: ^0.1.0 peerDependenciesMeta: @@ -6493,6 +6648,7 @@ packages: /object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + requiresBuild: true dev: false optional: true @@ -6546,6 +6702,7 @@ packages: /optionator@0.8.3: resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} engines: {node: '>= 0.8.0'} + requiresBuild: true dependencies: deep-is: 0.1.4 fast-levenshtein: 2.0.6 @@ -6714,6 +6871,7 @@ packages: /prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} + requiresBuild: true dev: false optional: true @@ -6875,6 +7033,7 @@ packages: /proto3-json-serializer@1.1.1: resolution: {integrity: sha512-AwAuY4g9nxx0u52DnSMkqqgyLHaW/XaPLtaAo3y/ZCfeaQB/g4YDH4kb8Wc/mWzWvu0YjOznVnfn373MVZZrgw==} engines: {node: '>=12.0.0'} + requiresBuild: true dependencies: protobufjs: 7.2.4 dev: false @@ -6884,6 +7043,7 @@ packages: resolution: {integrity: sha512-VPWMgIcRNyQwWUv8OLPyGQ/0lQY/QTQAVN5fh+XzfDwsVw1FZ2L3DM/bcBf8WPiRz2tNpaov9lPZfNcmNo6LXA==} engines: {node: '>=12.0.0'} hasBin: true + requiresBuild: true peerDependencies: protobufjs: ^7.0.0 dependencies: @@ -7300,6 +7460,7 @@ packages: /requizzle@0.2.4: resolution: {integrity: sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==} + requiresBuild: true dependencies: lodash: 4.17.21 dev: false @@ -7337,6 +7498,7 @@ packages: /retry-request@5.0.2: resolution: {integrity: sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==} engines: {node: '>=12'} + requiresBuild: true dependencies: debug: 4.3.4 extend: 3.0.2 @@ -7348,6 +7510,7 @@ packages: /retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} + requiresBuild: true dev: false optional: true @@ -7618,6 +7781,7 @@ packages: /sparse-bitfield@3.0.3: resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + requiresBuild: true dependencies: memory-pager: 1.5.0 dev: false @@ -7634,6 +7798,7 @@ packages: /stream-events@1.0.5: resolution: {integrity: sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==} + requiresBuild: true dependencies: stubs: 3.0.0 dev: false @@ -7641,6 +7806,7 @@ packages: /stream-shift@1.0.1: resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==} + requiresBuild: true dev: false optional: true @@ -7670,11 +7836,13 @@ packages: /strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + requiresBuild: true dev: false optional: true /stubs@3.0.0: resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==} + requiresBuild: true dev: false optional: true @@ -7792,6 +7960,7 @@ packages: /teeny-request@8.0.3: resolution: {integrity: sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww==} engines: {node: '>=12'} + requiresBuild: true dependencies: http-proxy-agent: 5.0.0 https-proxy-agent: 5.0.1 @@ -7848,6 +8017,7 @@ packages: /tmp@0.2.1: resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} engines: {node: '>=8.17.0'} + requiresBuild: true dependencies: rimraf: 3.0.2 dev: false @@ -7878,6 +8048,7 @@ packages: /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + requiresBuild: true dev: false optional: true @@ -7895,6 +8066,7 @@ packages: /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + requiresBuild: true dev: false optional: true @@ -7905,6 +8077,7 @@ packages: /type-check@0.3.2: resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} engines: {node: '>= 0.8.0'} + requiresBuild: true dependencies: prelude-ls: 1.1.2 dev: false @@ -7946,6 +8119,7 @@ packages: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} engines: {node: '>=0.8.0'} hasBin: true + requiresBuild: true dev: false optional: true @@ -7962,6 +8136,7 @@ packages: /underscore@1.13.6: resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} + requiresBuild: true dev: false optional: true @@ -8115,6 +8290,7 @@ packages: /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + requiresBuild: true dev: false optional: true @@ -8147,6 +8323,7 @@ packages: /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + requiresBuild: true dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 @@ -8213,6 +8390,7 @@ packages: /word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + requiresBuild: true dev: false optional: true @@ -8259,6 +8437,7 @@ packages: /xmlcreate@2.0.4: resolution: {integrity: sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==} + requiresBuild: true dev: false optional: true @@ -8299,6 +8478,7 @@ packages: /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + requiresBuild: true dev: false optional: true @@ -8328,6 +8508,7 @@ packages: /yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + requiresBuild: true dependencies: cliui: 8.0.1 escalade: 3.1.1 @@ -8342,3 +8523,4 @@ packages: /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + requiresBuild: true From b51ada8303e2884360ed8c915f95f222bc90ed11 Mon Sep 17 00:00:00 2001 From: Geon Kim Date: Fri, 18 Aug 2023 06:16:15 +0900 Subject: [PATCH 016/261] Fix: response code for deceptive request routing --- src/middlewares/information.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/middlewares/information.js b/src/middlewares/information.js index a1c0ed4c..95b105aa 100644 --- a/src/middlewares/information.js +++ b/src/middlewares/information.js @@ -7,8 +7,8 @@ module.exports = (req, res, next) => { req.session?.loginAfterState?.redirectOrigin; // sparcssso/callback 요청은 헤더에 origin이 없음 if (!req.origin) { - return res.status(412).json({ - error: "Precondition Failed : request must have origin in header", + return res.status(400).json({ + error: "Bad Request : request must have origin in header", }); } next(); From 4b1b359fabc15c805b116a9dde21ff11dc02c483 Mon Sep 17 00:00:00 2001 From: withsang Date: Tue, 22 Aug 2023 04:29:25 +0900 Subject: [PATCH 017/261] Fix: add missing req argument in joinus function --- src/services/auth.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/auth.js b/src/services/auth.js index 324f82d7..ea45be69 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -39,7 +39,7 @@ const transUserData = (userData) => { return info; }; -const joinus = async (userData) => { +const joinus = async (req, userData) => { const newUser = new userModel({ id: userData.id, name: userData.name, @@ -69,7 +69,7 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { "_id name id withdraw ban" ); if (!user) { - await joinus(userData); + await joinus(req, userData); return tryLogin(req, res, userData, redirectOrigin, redirectPath); } if (user.name != userData.name) { @@ -95,7 +95,7 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { } catch (err) { logger.error(err); const redirectUrl = new URL("/login/fail", redirectOrigin).href; - res.redirect(ssoLogoutUrl); + res.redirect(redirectUrl); } }; From 7794f71ce633e42fe642aa5297ef376ec3f874ac Mon Sep 17 00:00:00 2001 From: withsang Date: Tue, 22 Aug 2023 04:38:01 +0900 Subject: [PATCH 018/261] Add: register global error handler --- app.js | 3 +++ src/middlewares/errorHandler.js | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/middlewares/errorHandler.js diff --git a/app.js b/app.js index 2e917d5e..4e53e1fb 100644 --- a/app.js +++ b/app.js @@ -48,6 +48,9 @@ app.use("/locations", require("./src/routes/locations")); app.use("/reports", require("./src/routes/reports")); app.use("/notifications", require("./src/routes/notifications")); +// [Middleware] 전역 에러 핸들러. 에러 핸들러는 router들보다 아래에 등록되어야 합니다. +app.use(require("./src/middlewares/errorHandler")); + // express 서버 시작 const serverHttp = http .createServer(app) diff --git a/src/middlewares/errorHandler.js b/src/middlewares/errorHandler.js new file mode 100644 index 00000000..36d55144 --- /dev/null +++ b/src/middlewares/errorHandler.js @@ -0,0 +1,25 @@ +const logger = require("../modules/logger"); + +/** + * Express app에서 사용할 custom global error handler를 정의합니다. + * @summary Express 핸들러에서 발생한 uncaught exception은 이 핸들러를 통해 처리됩니다. + * Express에서 제공하는 기본 global error handler는 클라이언트에 오류 발생 call stack을 그대로 반환합니다. + * 이 때문에 클라이언트에게 잠재적으로 보안 취약점을 노출할 수 있으므로, call stack을 반환하지 않는 error handler를 정의합니다. + * @param {Error} err - 오류 객체 + * @param {Express.Request} req - 요청 객체 + * @param {Express.Response} res - 응답 객체 + * @param {Function} next - 다음 미들웨어 함수. Express에서는 next 함수에 err 인자를 넘겨주면 기본 global error handler가 호출됩니다. + * @return {ReturnValueDataTypeHere} Brief description of the returning value here. + */ +const errorHandler = (err, req, res, next) => { + // 이미 클라이언트에 HTTP 응답 헤더를 전송한 경우, 응답 헤더를 다시 전송하지 않아야 합니다. + // 클라이언트에게 스트리밍 형태로 응답을 전송하는 도중 오류가 발생하는 경우가 여기에 해당합니다. + // 이럴 때 기본 global error handler를 호출하면 기본 global error handler가 클라이언트와의 연결을 종료시켜 줍니다. + logger.error(err); + if (res.headersSent) { + return next(err); + } + res.status(500).send("internal server error"); +}; + +module.exports = errorHandler; From 0fb2e7905ccb537eea44763060d56f37a60d29b8 Mon Sep 17 00:00:00 2001 From: withsang Date: Tue, 22 Aug 2023 04:47:37 +0900 Subject: [PATCH 019/261] Fix: validate loginAfterState on callback handler --- src/services/auth.js | 5 ++++- src/services/auth.replace.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/services/auth.js b/src/services/auth.js index ea45be69..1c62a817 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -114,7 +114,10 @@ const sparcsssoHandler = (req, res) => { }; const sparcsssoCallbackHandler = (req, res) => { - const { state, redirectOrigin, redirectPath } = req.session?.loginAfterState; + const loginAfterState = req.session?.loginAfterState; + if (!loginAfterState) + return res.status(400).send("SparcsssoCallbackHandler : invalid request"); + const { state, redirectOrigin, redirectPath } = loginAfterState; const stateForCmp = req.body.state || req.query.state; req.session.loginAfterState = undefined; diff --git a/src/services/auth.replace.js b/src/services/auth.replace.js index cde54b53..90befeb0 100644 --- a/src/services/auth.replace.js +++ b/src/services/auth.replace.js @@ -30,7 +30,10 @@ const createUserData = (id) => { const loginReplaceHandler = (req, res) => { const { id } = req.body; - const { redirectOrigin, redirectPath } = req.session?.loginAfterState; + const loginAfterState = req.session?.loginAfterState; + if (!loginAfterState) + return res.status(400).send("SparcsssoCallbackHandler : invalid request"); + const { redirectOrigin, redirectPath } = loginAfterState; tryLogin(req, res, createUserData(id), redirectOrigin, redirectPath); }; From d43683ce6c2d41dc159bd03e0aec326dbd638276 Mon Sep 17 00:00:00 2001 From: withsang Date: Tue, 22 Aug 2023 05:09:40 +0900 Subject: [PATCH 020/261] Refactor: expand emitChatEvent to cover department --- src/modules/socket.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/modules/socket.js b/src/modules/socket.js index cfb63a9c..c9112c57 100644 --- a/src/modules/socket.js +++ b/src/modules/socket.js @@ -106,9 +106,10 @@ const getMessageBody = (type, nickname, content) => { * @param {string} chat.content - 채팅 메시지의 본문입니다. chat.type이 "s3img"인 경우에는 채팅의 objectId입니다. chat.type이 "in"이거나 "out"인 경우 입퇴장한 사용자의 id(!==ObjectId)입니다. * @param {string} chat.authorId - 채팅을 보낸 사용자의 ObjectId입니다. * @param {Date?} chat.time - optional. 채팅 메시지 전송 시각입니다. + * @param {Boolean} excludeAuthor - optional. 메시지 알림을 보내는 대상으로부터 authorId를 제외할 지 여부입니다. 기본값은 true입니다. * @return {Promise} 채팅 및 알림 전송에 성공하면 true, 중간에 오류가 발생하면 false를 반환합니다. */ -const emitChatEvent = async (io, chat) => { +const emitChatEvent = async (io, chat, excludeAuthor = true) => { try { // chat must contain type, content and authorId // chat can contain time or not. @@ -158,9 +159,12 @@ const emitChatEvent = async (io, chat) => { const userIdsExceptAuthor = part .map((participant) => participant.user) .filter((userId) => userId.toString() !== authorId.toString()); - const deviceTokens = await getTokensOfUsers(userIdsExceptAuthor, { - chatting: true, - }); + const deviceTokens = await getTokensOfUsers( + excludeAuthor ? userIdsExceptAuthor : userIds, + { + chatting: true, + } + ); // 방의 모든 사용자에게 socket 메세지 수신 이벤트를 발생시킵니다. const chatsForRoom = await transformChatsForRoom([chatDocument]); From 91f8b894f7ac3d848bcb32f473ebd93d2481b2f1 Mon Sep 17 00:00:00 2001 From: withsang Date: Tue, 22 Aug 2023 05:39:05 +0900 Subject: [PATCH 021/261] Fix: remove sample comment --- src/middlewares/errorHandler.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/middlewares/errorHandler.js b/src/middlewares/errorHandler.js index 36d55144..369391c6 100644 --- a/src/middlewares/errorHandler.js +++ b/src/middlewares/errorHandler.js @@ -9,7 +9,6 @@ const logger = require("../modules/logger"); * @param {Express.Request} req - 요청 객체 * @param {Express.Response} res - 응답 객체 * @param {Function} next - 다음 미들웨어 함수. Express에서는 next 함수에 err 인자를 넘겨주면 기본 global error handler가 호출됩니다. - * @return {ReturnValueDataTypeHere} Brief description of the returning value here. */ const errorHandler = (err, req, res, next) => { // 이미 클라이언트에 HTTP 응답 헤더를 전송한 경우, 응답 헤더를 다시 전송하지 않아야 합니다. From 9fe21555900633757e00ec0afab143b48f60a7b1 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Sat, 26 Aug 2023 09:57:52 +0700 Subject: [PATCH 022/261] Fix: regExp of account --- src/modules/patterns.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/patterns.js b/src/modules/patterns.js index c39706ad..7111ec1a 100644 --- a/src/modules/patterns.js +++ b/src/modules/patterns.js @@ -10,7 +10,7 @@ module.exports = { nickname: RegExp("^[A-Za-z가-힣ㄱ-ㅎㅏ-ㅣ0-9-_ ]{3,25}$"), allowedEmployeeTypes: RegExp("^([PEUR]|[SA]|[PEUR][SA])$"), profileImgType: RegExp("^(image/png|image/jpg|image/jpeg)$"), - account: RegExp("^[A-Za-z가-힣]{2,7} ([0-9]{10,14}|)$"), + account: RegExp("^[A-Za-z가-힣]{2,7} [0-9]{10,14}$|^$"), }, chat: { chatImgType: RegExp("^(image/png|image/jpg|image/jpeg)$"), From 8ad7603976e0f2ce0dfb23e25d516b7e935d0f38 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Thu, 31 Aug 2023 18:46:31 +0900 Subject: [PATCH 023/261] Add: originValidator --- app.js | 3 +++ src/middlewares/information.js | 10 ---------- src/middlewares/originValidator.js | 13 +++++++++++++ src/services/auth.replace.js | 1 + 4 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 src/middlewares/originValidator.js diff --git a/app.js b/app.js index 4e53e1fb..db40affb 100644 --- a/app.js +++ b/app.js @@ -38,6 +38,9 @@ app.use(require("./src/middlewares/limitRate")); // [Router] Swagger (API 문서) app.use("/docs", require("./src/routes/docs")); +// [Middleware] 모든 API 요청에 대하여 origin 검증 +app.use(require("./src/middlewares/originValidator")); + // [Router] APIs app.use("/auth", require("./src/routes/auth")); app.use("/logininfo", require("./src/routes/logininfo")); diff --git a/src/middlewares/information.js b/src/middlewares/information.js index 95b105aa..f7197d1f 100644 --- a/src/middlewares/information.js +++ b/src/middlewares/information.js @@ -1,15 +1,5 @@ module.exports = (req, res, next) => { req.clientIP = req.headers["x-forwarded-for"] || req.connection.remoteAddress; req.timestamp = Date.now(); - req.origin = - req.headers.origin || - req.headers.referer || - req.session?.loginAfterState?.redirectOrigin; // sparcssso/callback 요청은 헤더에 origin이 없음 - - if (!req.origin) { - return res.status(400).json({ - error: "Bad Request : request must have origin in header", - }); - } next(); }; diff --git a/src/middlewares/originValidator.js b/src/middlewares/originValidator.js new file mode 100644 index 00000000..2aec851d --- /dev/null +++ b/src/middlewares/originValidator.js @@ -0,0 +1,13 @@ +module.exports = (req, res, next) => { + req.origin = + req.headers.origin || + req.headers.referer || + req.session?.loginAfterState?.redirectOrigin; // sparcssso/callback 요청은 헤더에 origin이 없음 + + if (!req.origin) { + return res.status(400).json({ + error: "Bad Request : request must have origin in header", + }); + } + next(); +}; diff --git a/src/services/auth.replace.js b/src/services/auth.replace.js index 90befeb0..a433e6f6 100644 --- a/src/services/auth.replace.js +++ b/src/services/auth.replace.js @@ -34,6 +34,7 @@ const loginReplaceHandler = (req, res) => { if (!loginAfterState) return res.status(400).send("SparcsssoCallbackHandler : invalid request"); const { redirectOrigin, redirectPath } = loginAfterState; + req.session.loginAfterState = undefined; tryLogin(req, res, createUserData(id), redirectOrigin, redirectPath); }; From 0bd3175b4babd87391848feb9f89678f0dc71bcc Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Mon, 4 Sep 2023 03:43:44 +0900 Subject: [PATCH 024/261] Fix: registerSchedules --- app.js | 5 +- src/modules/socket.js | 81 +++++++++++++++-------------- src/modules/stores/mongo.js | 5 +- src/schedules/index.js | 5 +- src/schedules/notifyBeforeDepart.js | 12 ++--- 5 files changed, 55 insertions(+), 53 deletions(-) diff --git a/app.js b/app.js index 4d9155a6..6049e736 100644 --- a/app.js +++ b/app.js @@ -61,7 +61,8 @@ const serverHttp = http logger.info(`Express 서버가 ${httpPort}번 포트에서 시작됨.`) ); -// socket.io 서버 시작 및 app 인스턴스에 저장 +// socket.io 서버 시작 app.set("io", startSocketServer(serverHttp)); -require("./src/schedules/index")(app.get("io")); +// [Schedule] 스케줄러 시작 +require("./src/schedules")(app); diff --git a/src/modules/socket.js b/src/modules/socket.js index c9112c57..1d23cb51 100644 --- a/src/modules/socket.js +++ b/src/modules/socket.js @@ -62,37 +62,46 @@ const transformChatsForRoom = async (chats) => { * 예를 들어, type이 "text"인 경우 `${nickname}: ${content}`를 보냅니다. */ const getMessageBody = (type, nickname, content) => { + // 닉네임이 9글자를 넘어가면 "..."으로 표시합니다. + const ellipsisedNickname = + nickname.length > 9 ? nickname.slice(0, 7) + "..." : nickname; + // TODO: 채팅 메시지 유형에 따라 Body를 다르게 표시합니다. - if (type === "text") { - // 채팅 메시지 유형이 텍스트인 경우 본문은 "${nickname}: ${content}"가 됩니다. - return `${nickname}: ${content}`; - } else if (type === "s3img") { - // 채팅 유형이 이미지인 경우 본문은 "${nickname} 님이 이미지를 전송하였습니다"가 됩니다. - // TODO: 사용자 언어를 가져올 수 있으면 개선할 수 있다고 생각합니다. - const suffix = " 님이 이미지를 전송하였습니다"; - return `${nickname} ${suffix}`; - } else if (type === "in" || type === "out") { - // 채팅 메시지 type이 "in"이거나 "out"인 경우 본문은 "${nickname} 님이 입장하였습니다" 또는 "${nickname} 님이 퇴장하였습니다"가 됩니다. - // TODO: 사용자 언어를 가져올 수 있으면 개선할 수 있다고 생각합니다. - const suffix = - type === "in" ? " 님이 입장하였습니다" : "님이 퇴장하였습니다"; - return `${nickname} ${suffix}`; - } else if (type === "payment" || type === "settlement") { - // 채팅 메시지 type이 "in"이거나 "out"인 경우 본문은 "${nickname} 님이 결제를 완료하였습니다" 또는 "${nickname} 님이 정산을 완료하였습니다"가 됩니다. - // TODO: 사용자 언어를 가져올 수 있다면 개선할 수 있다고 생각합니다. - const suffix = - type === "payment" - ? " 님이 결제를 완료하였습니다" - : " 님이 정산을 완료하였습니다"; - return `${nickname} ${suffix}`; - } else if (type === "account") { - const suffix = " 님이 계좌번호를 전송하였습니다"; - return `${nickname} ${suffix}`; - } else if (type === "department") { - return "출발 15분전 입니다"; - } else { - // 정의되지 않은 type의 경우에는 nickname만 반환합니다. - return nickname; + // TODO: 사용자 언어를 가져올 수 있으면 개선할 수 있다고 생각합니다. + switch (type) { + case "text": + return `${ellipsisedNickname}: ${content}`; + case "s3img": { + const suffix = "님이 이미지를 전송하였습니다"; + return `${ellipsisedNickname} ${suffix}`; + } + case "in": { + const suffix = "님이 입장하였습니다"; + return `${ellipsisedNickname} ${suffix}`; + } + case "out": { + const suffix = "님이 퇴장하였습니다"; + return `${ellipsisedNickname} ${suffix}`; + } + case "payment": { + const suffix = "님이 정산을 시작하였습니다"; + return `${ellipsisedNickname} ${suffix}`; + } + case "settlement": { + const suffix = "님이 송금을 완료하였습니다"; + return `${ellipsisedNickname} ${suffix}`; + } + case type === "account": { + const suffix = "님이 계좌번호를 전송하였습니다"; + return `${ellipsisedNickname} ${suffix}`; + } + case "departure": + return `출발 ${content}분전 입니다`; + case "arrival": + return "아직 정산 시작을 하지 않았거나 송금을 완료하지 않은 사용자가 있습니다"; + default: + // 정의되지 않은 type의 경우에는 nickname만 반환합니다. + return ellipsisedNickname; } }; @@ -106,10 +115,9 @@ const getMessageBody = (type, nickname, content) => { * @param {string} chat.content - 채팅 메시지의 본문입니다. chat.type이 "s3img"인 경우에는 채팅의 objectId입니다. chat.type이 "in"이거나 "out"인 경우 입퇴장한 사용자의 id(!==ObjectId)입니다. * @param {string} chat.authorId - 채팅을 보낸 사용자의 ObjectId입니다. * @param {Date?} chat.time - optional. 채팅 메시지 전송 시각입니다. - * @param {Boolean} excludeAuthor - optional. 메시지 알림을 보내는 대상으로부터 authorId를 제외할 지 여부입니다. 기본값은 true입니다. * @return {Promise} 채팅 및 알림 전송에 성공하면 true, 중간에 오류가 발생하면 false를 반환합니다. */ -const emitChatEvent = async (io, chat, excludeAuthor = true) => { +const emitChatEvent = async (io, chat) => { try { // chat must contain type, content and authorId // chat can contain time or not. @@ -159,12 +167,9 @@ const emitChatEvent = async (io, chat, excludeAuthor = true) => { const userIdsExceptAuthor = part .map((participant) => participant.user) .filter((userId) => userId.toString() !== authorId.toString()); - const deviceTokens = await getTokensOfUsers( - excludeAuthor ? userIdsExceptAuthor : userIds, - { - chatting: true, - } - ); + const deviceTokens = await getTokensOfUsers(userIdsExceptAuthor, { + chatting: true, + }); // 방의 모든 사용자에게 socket 메세지 수신 이벤트를 발생시킵니다. const chatsForRoom = await transformChatsForRoom([chatDocument]); diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index b25d0db4..6b4c1146 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -129,10 +129,11 @@ const chatSchema = Schema({ "payment", "settlement", "account", - "department", + "departure", // 출발 15분 전 알림 + "arrival", // 출발 (1|25)시간 이후 알림 - 정산/송금 권유 ], }, // 메시지 종류 - authorId: { type: Schema.Types.ObjectId, ref: "User", required: true }, // 작성자 id + authorId: { type: Schema.Types.ObjectId, ref: "User" }, // 작성자 id content: { type: String, default: "" }, time: { type: Date, required: true }, isValid: { type: Boolean, default: true }, diff --git a/src/schedules/index.js b/src/schedules/index.js index c4ba74bf..5547ebc9 100644 --- a/src/schedules/index.js +++ b/src/schedules/index.js @@ -4,8 +4,9 @@ const { sendReminder, } = require("./notifyBeforeDepart"); -const registerSchedules = () => { - cron.schedule(sendReminderExpression, sendReminder); +const registerSchedules = (app) => { + // cron.schedule("*/5 * * * *", require("./notifyBeforeDepart")(app)); + cron.schedule("* * * * *", require("./notifyBeforeDepart")(app)); }; module.exports = registerSchedules; diff --git a/src/schedules/notifyBeforeDepart.js b/src/schedules/notifyBeforeDepart.js index 85f713ed..6c5e9e05 100644 --- a/src/schedules/notifyBeforeDepart.js +++ b/src/schedules/notifyBeforeDepart.js @@ -2,8 +2,6 @@ const { roomModel } = require("../modules/stores/mongo"); const { roomPopulateOption } = require("../modules/populates/rooms"); const { emitChatEvent } = require("../modules/socket"); -// const expression = "*/5 * * * *"; -const expression = "* * * * *"; const MS_PER_MINUTE = 60000; /** @@ -12,7 +10,8 @@ const MS_PER_MINUTE = 60000; * @return {Promise} 알림 전송에 실패한 기기 수를 반환합니다. */ -const sendReminder = async (io) => { +module.exports = (app) => async () => { + const io = app.get("io"); const departDate = new Date(Date.now() + 15 * MS_PER_MINUTE).toISOString(); const currentDate = new Date(Date.now()).toISOString(); @@ -34,14 +33,9 @@ const sendReminder = async (io) => { roomIds.map(async (roomId, index) => { await emitChatEvent(io, { roomId: roomId, - type: "department", + type: "departure", content: roomId, authorId: authorIds[index], }); }); }; - -module.exports = { - expression, - sendReminder, -}; From 20046762313c45368f5e4c7069235f06f469c8c0 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Mon, 4 Sep 2023 17:21:00 +0900 Subject: [PATCH 025/261] Fix: notifyBeforeDepart --- src/modules/socket.js | 58 ++++++++++++++++------------- src/schedules/notifyBeforeDepart.js | 58 ++++++++++++++++------------- src/services/chats.js | 7 ++++ 3 files changed, 72 insertions(+), 51 deletions(-) diff --git a/src/modules/socket.js b/src/modules/socket.js index 1d23cb51..3da4370e 100644 --- a/src/modules/socket.js +++ b/src/modules/socket.js @@ -44,9 +44,9 @@ const transformChatsForRoom = async (chats) => { chatsToSend.push({ roomId: chat.roomId, type: chat.type, - authorId: chat.authorId._id, - authorName: chat.authorId.nickname, - authorProfileUrl: chat.authorId.profileImageUrl, + authorId: chat.authorId?._id, + authorName: chat.authorId?.nickname, + authorProfileUrl: chat.authorId?.profileImageUrl, content: chat.content, time: chat.time, isValid: chat.isValid, @@ -61,7 +61,7 @@ const transformChatsForRoom = async (chats) => { * FCM 알림으로 보내는 content는 채팅 type에 따라 달라집니다. * 예를 들어, type이 "text"인 경우 `${nickname}: ${content}`를 보냅니다. */ -const getMessageBody = (type, nickname, content) => { +const getMessageBody = (type, nickname = "", content = "") => { // 닉네임이 9글자를 넘어가면 "..."으로 표시합니다. const ellipsisedNickname = nickname.length > 9 ? nickname.slice(0, 7) + "..." : nickname; @@ -96,7 +96,7 @@ const getMessageBody = (type, nickname, content) => { return `${ellipsisedNickname} ${suffix}`; } case "departure": - return `출발 ${content}분전 입니다`; + return `택시 출발 ${content}분 전 입니다`; case "arrival": return "아직 정산 시작을 하지 않았거나 송금을 완료하지 않은 사용자가 있습니다"; default: @@ -111,30 +111,35 @@ const getMessageBody = (type, nickname, content) => { * @param {Server} io - Socket.io 서버 인스턴스입니다. req.app.get("io")를 통해 접근할 수 있습니다. * @param {Object} chat - 채팅 메시지 내용입니다. * @param {string} chat.roomId - 채팅 및 채팅 알림을 보낼 방의 ObjectId입니다. - * @param {string} chat.type - 채팅 메시지의 유형입니다. "text" | "s3img" | "in" | "out" | "payment" | "settlement" 입니다. + * @param {string} chat.type - 채팅 메시지의 유형입니다. "text" | "s3img" | "in" | "out" | "payment" | "settlement" | "account" | "departure" | "arrival" 입니다. * @param {string} chat.content - 채팅 메시지의 본문입니다. chat.type이 "s3img"인 경우에는 채팅의 objectId입니다. chat.type이 "in"이거나 "out"인 경우 입퇴장한 사용자의 id(!==ObjectId)입니다. - * @param {string} chat.authorId - 채팅을 보낸 사용자의 ObjectId입니다. + * @param {string} chat.authorId - optional. 채팅을 보낸 사용자의 ObjectId입니다. * @param {Date?} chat.time - optional. 채팅 메시지 전송 시각입니다. * @return {Promise} 채팅 및 알림 전송에 성공하면 true, 중간에 오류가 발생하면 false를 반환합니다. */ const emitChatEvent = async (io, chat) => { try { - // chat must contain type, content and authorId - // chat can contain time or not. const { roomId, type, content, authorId } = chat; - if (!io || !roomId || !type || !content || !authorId) { + // chat must contains roomId, type, and content + if (!io || !roomId || !type || !content) { throw new IllegalArgumentsException(); } + // chat optionally contains time const time = chat?.time || Date.now(); + + // roomId must be valid const { name, part } = await roomModel.findById(roomId, "name part"); - const { nickname, profileImageUrl } = await userModel.findById( - authorId, - "nickname profileImageUrl" - ); + if (!name || !part) { + throw new IllegalArgumentsException(); + } - if (!nickname || !profileImageUrl || !name || !part) { + // chat optionally contains authorId + const { nickname, profileImageUrl } = authorId + ? await userModel.findById(authorId, "nickname profileImageUrl") + : {}; + if (authorId && (!nickname || !profileImageUrl)) { throw new IllegalArgumentsException(); } @@ -162,14 +167,12 @@ const emitChatEvent = async (io, chat) => { chatDocument.authorName = nickname; chatDocument.authorProfileUrl = profileImageUrl; - const urlOnClick = `/myroom/${roomId}`; const userIds = part.map((participant) => participant.user); - const userIdsExceptAuthor = part - .map((participant) => participant.user) - .filter((userId) => userId.toString() !== authorId.toString()); - const deviceTokens = await getTokensOfUsers(userIdsExceptAuthor, { - chatting: true, - }); + const userIdsExceptAuthor = authorId + ? part + .map((participant) => participant.user) + .filter((userId) => userId.toString() !== authorId.toString()) + : userIds; // 방의 모든 사용자에게 socket 메세지 수신 이벤트를 발생시킵니다. const chatsForRoom = await transformChatsForRoom([chatDocument]); @@ -182,15 +185,20 @@ const emitChatEvent = async (io, chat) => { ) ); - // 해당 방에 참여중인 사용자들에게 알림을 전송합니다. + // 방의 작성자를 제외한 참여중인 사용자들에게 푸시 알림을 전송합니다. + const deviceTokensExceptAuthor = await getTokensOfUsers( + userIdsExceptAuthor, + { chatting: true } + ); await sendMessageByTokens( - deviceTokens, + deviceTokensExceptAuthor, type, name, getMessageBody(type, nickname, content), getS3Url(`/profile-img/${profileImageUrl}`), - urlOnClick + `/myroom/${roomId}` ); + return true; } catch (err) { logger.error(err); diff --git a/src/schedules/notifyBeforeDepart.js b/src/schedules/notifyBeforeDepart.js index 6c5e9e05..b2171b2c 100644 --- a/src/schedules/notifyBeforeDepart.js +++ b/src/schedules/notifyBeforeDepart.js @@ -1,6 +1,7 @@ -const { roomModel } = require("../modules/stores/mongo"); -const { roomPopulateOption } = require("../modules/populates/rooms"); +const { roomModel, chatModel } = require("../modules/stores/mongo"); +// const { roomPopulateOption } = require("../modules/populates/rooms"); const { emitChatEvent } = require("../modules/socket"); +const logger = require("../modules/logger"); const MS_PER_MINUTE = 60000; @@ -11,31 +12,36 @@ const MS_PER_MINUTE = 60000; */ module.exports = (app) => async () => { - const io = app.get("io"); - const departDate = new Date(Date.now() + 15 * MS_PER_MINUTE).toISOString(); - const currentDate = new Date(Date.now()).toISOString(); + try { + const io = app.get("io"); + const departDate = new Date(Date.now() + 15 * MS_PER_MINUTE).toISOString(); + const currentDate = new Date(Date.now()).toISOString(); - const roomObjects = await roomModel - .find({ - $and: [{ time: { $lt: departDate } }, { time: { $gte: currentDate } }], - }) - .lean() - .populate(roomPopulateOption); - - if (!roomObjects) { - return res.status(404).json({ - error: "NotifyBeforeDepartment: cannot find room", + const candidatesRooms = await roomModel.find({ + $and: [ + { time: { $lte: departDate } }, + { time: { $gte: currentDate } }, + { "part.1": { $exists: true } }, + ], }); - } - const roomIds = roomObjects.map((room) => room._id); - const authorIds = roomObjects.map((room) => room.part[0].user._id); - roomIds.map(async (roomId, index) => { - await emitChatEvent(io, { - roomId: roomId, - type: "departure", - content: roomId, - authorId: authorIds[index], - }); - }); + await Promise.all( + candidatesRooms.map(async ({ _id: roomId, time }) => { + const countDepartureChat = await chatModel.countDocuments({ + roomId, + type: "departure", + }); + if (countDepartureChat > 0) return; + const minuteDiff = Math.ceil((time - Date.now()) / MS_PER_MINUTE); + if (minuteDiff <= 0) return; + await emitChatEvent(io, { + roomId: roomId, + type: "departure", + content: minuteDiff.toString(), + }); + }) + ); + } catch (err) { + logger.error(err); + } }; diff --git a/src/services/chats.js b/src/services/chats.js index 14063e0e..4a76b1a4 100644 --- a/src/services/chats.js +++ b/src/services/chats.js @@ -2,6 +2,7 @@ const { chatModel, userModel, roomModel } = require("../modules/stores/mongo"); const { chatPopulateOption } = require("../modules/populates/chats"); const aws = require("../modules/stores/aws"); const { transformChatsForRoom, emitChatEvent } = require("../modules/socket"); +const logger = require("../modules/logger"); const chatCount = 60; @@ -42,6 +43,7 @@ const loadRecentChatHandler = async (req, res) => { res.status(500).send("Chat/ : internal server error"); } } catch (e) { + logger.error(e); res.status(500).send("Chat/ : internal server error"); } }; @@ -85,6 +87,7 @@ const loadBeforeChatHandler = async (req, res) => { res.status(500).send("Chat/load/before : internal server error"); } } catch (e) { + logger.error(e); res.status(500).send("Chat/load/before : internal server error"); } }; @@ -125,6 +128,7 @@ const loadAfterChatHandler = async (req, res) => { res.status(500).send("Chat/load/after : internal server error"); } } catch (e) { + logger.error(e); res.status(500).send("Chat/load/after : internal server error"); } }; @@ -161,6 +165,7 @@ const sendChatHandler = async (req, res) => { res.json({ result: true }); else res.status(500).send("Chat/send : internal server error"); } catch (e) { + logger.error(e); res.status(500).send("Chat/send : internal server error"); } }; @@ -203,6 +208,7 @@ const uploadChatImgGetPUrlHandler = async (req, res) => { }); }); } catch (e) { + logger.error(e); res.status(500).send("Chat/uploadChatImg/getPUrl : internal server error"); } }; @@ -249,6 +255,7 @@ const uploadChatImgDoneHandler = async (req, res) => { }); }); } catch (e) { + logger.error(e); res.status(500).send("Chat/uploadChatImg/done : internal server error"); } }; From c372e0795093a3b8ab06fc37d9392838dba9973e Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Mon, 4 Sep 2023 17:24:34 +0900 Subject: [PATCH 026/261] Add: enter on package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a084f737..cdb57f4f 100644 --- a/package.json +++ b/package.json @@ -58,4 +58,4 @@ }, "author": "sparcs/taxi", "license": "MIT" -} \ No newline at end of file +} From d79c32ceb2ad7e694ba0baaecb4c5a12449aaab1 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Mon, 4 Sep 2023 22:48:03 +0900 Subject: [PATCH 027/261] Add: notifyAfterArrival --- src/modules/stores/mongo.js | 2 +- src/schedules/index.js | 9 ++--- src/schedules/notifyAfterArrival.js | 51 +++++++++++++++++++++++++++++ src/schedules/notifyBeforeDepart.js | 5 ++- 4 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 src/schedules/notifyAfterArrival.js diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 6b4c1146..bfa94da9 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -130,7 +130,7 @@ const chatSchema = Schema({ "settlement", "account", "departure", // 출발 15분 전 알림 - "arrival", // 출발 (1|25)시간 이후 알림 - 정산/송금 권유 + "arrival", // 출발 (1|24)시간 이후 알림 - 정산/송금 권유 ], }, // 메시지 종류 authorId: { type: Schema.Types.ObjectId, ref: "User" }, // 작성자 id diff --git a/src/schedules/index.js b/src/schedules/index.js index 5547ebc9..23bc6664 100644 --- a/src/schedules/index.js +++ b/src/schedules/index.js @@ -1,12 +1,9 @@ const cron = require("node-cron"); -const { - expression: sendReminderExpression, - sendReminder, -} = require("./notifyBeforeDepart"); const registerSchedules = (app) => { - // cron.schedule("*/5 * * * *", require("./notifyBeforeDepart")(app)); - cron.schedule("* * * * *", require("./notifyBeforeDepart")(app)); + cron.schedule("*/5 * * * *", require("./notifyBeforeDepart")(app)); + cron.schedule("*/10 * * * *", require("./notifyAfterArrival")(app)); + require("./notifyAfterArrival")(app)(); }; module.exports = registerSchedules; diff --git a/src/schedules/notifyAfterArrival.js b/src/schedules/notifyAfterArrival.js new file mode 100644 index 00000000..37ad2fbc --- /dev/null +++ b/src/schedules/notifyAfterArrival.js @@ -0,0 +1,51 @@ +const { roomModel, chatModel } = require("../modules/stores/mongo"); +// const { roomPopulateOption } = require("../modules/populates/rooms"); +const { emitChatEvent } = require("../modules/socket"); +const logger = require("../modules/logger"); + +const MS_PER_MINUTE = 60000; + +/** + * 출발 한시간 이후 정산/송금하기를 완료하지 않은 사용자가 있다면 알림을 전송합니다. + */ + +module.exports = (app) => async () => { + try { + const io = app.get("io"); + const expiredDate = new Date( + Date.now() - 200 * MS_PER_MINUTE // @fixme + ).toISOString(); + const arrivalDate = new Date(Date.now() - 60 * MS_PER_MINUTE).toISOString(); + + const candidateRooms = await roomModel.find({ + $and: [ + { time: { $gte: expiredDate } }, + { time: { $lte: arrivalDate } }, + { + part: { + $elemMatch: { settlementStatus: { $nin: ["paid", "sent"] } }, + }, + }, + ], + }); + + await Promise.all( + candidateRooms.map(async ({ _id: roomId, time }) => { + const countArrivalChat = await chatModel.countDocuments({ + roomId, + type: "arrival", + }); + if (countArrivalChat > 0) return; + const minuteDiff = Math.floor((Date.now() - time) / MS_PER_MINUTE); + if (minuteDiff <= 0) return; + await emitChatEvent(io, { + roomId: roomId, + type: "arrival", + content: minuteDiff.toString(), + }); + }) + ); + } catch (err) { + logger.error(err); + } +}; diff --git a/src/schedules/notifyBeforeDepart.js b/src/schedules/notifyBeforeDepart.js index b2171b2c..5687827c 100644 --- a/src/schedules/notifyBeforeDepart.js +++ b/src/schedules/notifyBeforeDepart.js @@ -1,5 +1,4 @@ const { roomModel, chatModel } = require("../modules/stores/mongo"); -// const { roomPopulateOption } = require("../modules/populates/rooms"); const { emitChatEvent } = require("../modules/socket"); const logger = require("../modules/logger"); @@ -14,13 +13,13 @@ const MS_PER_MINUTE = 60000; module.exports = (app) => async () => { try { const io = app.get("io"); - const departDate = new Date(Date.now() + 15 * MS_PER_MINUTE).toISOString(); const currentDate = new Date(Date.now()).toISOString(); + const departDate = new Date(Date.now() + 15 * MS_PER_MINUTE).toISOString(); const candidatesRooms = await roomModel.find({ $and: [ - { time: { $lte: departDate } }, { time: { $gte: currentDate } }, + { time: { $lte: departDate } }, { "part.1": { $exists: true } }, ], }); From fcf5099ee0d2ed1ab9abbd212ed32933a016f8cc Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Mon, 4 Sep 2023 22:53:39 +0900 Subject: [PATCH 028/261] Remove: test code --- src/schedules/index.js | 1 - src/schedules/notifyAfterArrival.js | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/schedules/index.js b/src/schedules/index.js index 23bc6664..97818b92 100644 --- a/src/schedules/index.js +++ b/src/schedules/index.js @@ -3,7 +3,6 @@ const cron = require("node-cron"); const registerSchedules = (app) => { cron.schedule("*/5 * * * *", require("./notifyBeforeDepart")(app)); cron.schedule("*/10 * * * *", require("./notifyAfterArrival")(app)); - require("./notifyAfterArrival")(app)(); }; module.exports = registerSchedules; diff --git a/src/schedules/notifyAfterArrival.js b/src/schedules/notifyAfterArrival.js index 37ad2fbc..af4caa79 100644 --- a/src/schedules/notifyAfterArrival.js +++ b/src/schedules/notifyAfterArrival.js @@ -12,9 +12,7 @@ const MS_PER_MINUTE = 60000; module.exports = (app) => async () => { try { const io = app.get("io"); - const expiredDate = new Date( - Date.now() - 200 * MS_PER_MINUTE // @fixme - ).toISOString(); + const expiredDate = new Date(Date.now() - 90 * MS_PER_MINUTE).toISOString(); const arrivalDate = new Date(Date.now() - 60 * MS_PER_MINUTE).toISOString(); const candidateRooms = await roomModel.find({ From f81a2f94f7521f1e5c99e369de8bc8829ef158d1 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Mon, 4 Sep 2023 16:37:21 +0000 Subject: [PATCH 029/261] Refactor: apply prettier --- src/modules/socket.js | 4 ++-- src/modules/stores/mongo.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/socket.js b/src/modules/socket.js index 66233f4c..1f2990b8 100644 --- a/src/modules/socket.js +++ b/src/modules/socket.js @@ -227,7 +227,7 @@ const startSocketServer = (server) => { setHeader(key, values) { req.cookieHolder = values[0]; }, - writeHead() {}, + writeHead() { }, }; sessionMiddleware(req, fakeRes, () => { if (req.session) { @@ -264,7 +264,7 @@ const startSocketServer = (server) => { socket.join(`user-${userOid}`); }); - socket.on("disconnect", () => {}); + socket.on("disconnect", () => { }); } catch (err) { logger.error(err); } diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 0de36090..53548965 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -118,6 +118,7 @@ const locationSchema = Schema({ latitude: { type: Number }, // 이후 required: true 로 수정 필요 longitude: { type: Number }, // 이후 required: true 로 수정 필요 }); + const chatSchema = Schema({ roomId: { type: Schema.Types.ObjectId, ref: "Room", required: true }, type: { From 52b5747e60b96f73c7d7d9f58d2f6fe0a4c3620f Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Mon, 4 Sep 2023 16:38:13 +0000 Subject: [PATCH 030/261] Add: partInfoHandler --- src/modules/populates/rooms.js | 2 +- src/routes/rooms.js | 9 +++++++++ src/services/chats.js | 3 ++- src/services/rooms.js | 25 ++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/modules/populates/rooms.js b/src/modules/populates/rooms.js index 18cbedf9..d051a1f3 100644 --- a/src/modules/populates/rooms.js +++ b/src/modules/populates/rooms.js @@ -7,7 +7,7 @@ const roomPopulateOption = [ { path: "to", select: "_id koName enName" }, { path: "part", - select: "-_id user settlementStatus", + select: "-_id user settlementStatus readAt", populate: { path: "user", select: "_id id name nickname profileImageUrl" }, }, ]; diff --git a/src/routes/rooms.js b/src/routes/rooms.js index be1c3f59..f2bdb9c0 100644 --- a/src/routes/rooms.js +++ b/src/routes/rooms.js @@ -41,6 +41,15 @@ router.get( roomHandlers.infoHandler ); +// 특정 id 방 part 정보 보기 +router.get( + "/info/part", + query("id").isMongoId(), + validator, + roomHandlers.partInfoHandler +); + + // JSON으로 받은 정보로 방을 생성한다. router.post( "/create", diff --git a/src/services/chats.js b/src/services/chats.js index 89bc34eb..4a15fe65 100644 --- a/src/services/chats.js +++ b/src/services/chats.js @@ -213,12 +213,13 @@ const updateChatHandler = async (req, res) => { ) .lean() .populate(roomPopulateOption); + if (!roomObject) { return res.status(404).send("Chat/update : cannot find room info"); } /* TODO: Return Formatting */ - if (await emitUpdateEvent(io)) res.json({ part: roomObject.part }); + if (await emitUpdateEvent(io, roomId, userId)) res.json({ part: roomObject.part }); else res.status(500).send("Chat/update : internal server error"); } catch (e) { res.status(500).send("Chat/update : internal server error"); diff --git a/src/services/rooms.js b/src/services/rooms.js index 0456d534..5fab1dfa 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -136,6 +136,28 @@ const infoHandler = async (req, res) => { } }; +const partInfoHandler = async (req, res) => { + try { + const user = await userModel.findOne({ id: req.userId }); + const roomObject = await roomModel + .findOne({ _id: req.query.id, "part.user": user._id }) + .lean() + .populate(roomPopulateOption); + if (roomObject) { + res.send(roomObject.part.filter((p) => p.user._id.equals(user._id))[0]); + } else { + res.status(404).json({ + error: "Rooms/info : id does not exist", + }); + } + } catch (err) { + logger.error(err); + res.status(500).json({ + error: "Rooms/info : internal server error", + }); + } +} + const joinHandler = async (req, res) => { try { const user = await userModel @@ -403,7 +425,7 @@ const searchByUserHandler = async (req, res) => { populate: roomPopulateOption, }) .lean(); - + console.log(user); // 정산완료여부 기준으로 진행중인 방과 완료된 방을 분리해서 응답을 전송합니다. const response = {}; response.ongoing = user.ongoingRoom.map((room) => @@ -642,6 +664,7 @@ const settlementHandler = async (req, res) => { module.exports = { publicInfoHandler, infoHandler, + partInfoHandler, createHandler, joinHandler, abortHandler, From b41425bbca02a9f4111933d48f5aaaa8de5766ff Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Tue, 5 Sep 2023 14:22:06 +0000 Subject: [PATCH 031/261] Refactor: change updateChatHandler to readChatHandler --- src/routes/chats.js | 6 +++--- src/services/chats.js | 19 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/routes/chats.js b/src/routes/chats.js index 56febcfd..77fdf925 100644 --- a/src/routes/chats.js +++ b/src/routes/chats.js @@ -55,15 +55,15 @@ router.post( ); /** - * 채팅 정보를 업데이트 한 후, + * 채팅 읽은 시각 업데이트 요청을 처리합니다. * 같은 방에 있는 user들에게 업데이트를 요청합니다. */ router.post( - "/update", + "/readat", body("roomId").isMongoId(), body("lastMsgDate").isISO8601(), validator, - chatsHandlers.updateChatHandler + chatsHandlers.readChatHandler ); // 채팅 이미지를 업로드할 수 있는 Presigned-url을 발급합니다. diff --git a/src/services/chats.js b/src/services/chats.js index 4a15fe65..51d8efdd 100644 --- a/src/services/chats.js +++ b/src/services/chats.js @@ -173,7 +173,7 @@ const sendChatHandler = async (req, res) => { } }; -const updateChatHandler = async (req, res) => { +const readChatHandler = async (req, res) => { try { const io = req.app.get("io"); const { userId } = req; @@ -181,16 +181,16 @@ const updateChatHandler = async (req, res) => { const user = await userModel.findOne({ id: userId }); if (!userId || !user) { - return res.status(500).send("Chat/update : internal server error"); + return res.status(500).send("Chat/read : internal server error"); } if (!io) { - return res.status(403).send("Chat/update : socket did not connected"); + return res.status(403).send("Chat/read : socket did not connected"); } const isPart = await isUserInRoom(userId, roomId); if (!isPart) { return res .status(403) - .send("Chat/update : user did not participated in the room"); + .send("Chat/read : user did not participated in the room"); } const roomObject = await roomModel @@ -215,14 +215,13 @@ const updateChatHandler = async (req, res) => { .populate(roomPopulateOption); if (!roomObject) { - return res.status(404).send("Chat/update : cannot find room info"); + return res.status(404).send("Chat/read : cannot find room info"); } - /* TODO: Return Formatting */ - if (await emitUpdateEvent(io, roomId, userId)) res.json({ part: roomObject.part }); - else res.status(500).send("Chat/update : internal server error"); + if (await emitUpdateEvent(io, roomId, userId)) res.json({ result: true }); + else res.status(500).send("Chat/read : failed to emit socket events"); } catch (e) { - res.status(500).send("Chat/update : internal server error"); + res.status(500).send("Chat/read : internal server error"); } }; @@ -338,5 +337,5 @@ module.exports = { sendChatHandler, uploadChatImgGetPUrlHandler, uploadChatImgDoneHandler, - updateChatHandler, + readChatHandler, }; From 92cab6a9de317ec81da68d53ded507a85766dbe4 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Tue, 5 Sep 2023 14:23:13 +0000 Subject: [PATCH 032/261] Remove: partInfoHandler --- src/routes/rooms.js | 9 --------- src/services/rooms.js | 25 +------------------------ 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/src/routes/rooms.js b/src/routes/rooms.js index f2bdb9c0..be1c3f59 100644 --- a/src/routes/rooms.js +++ b/src/routes/rooms.js @@ -41,15 +41,6 @@ router.get( roomHandlers.infoHandler ); -// 특정 id 방 part 정보 보기 -router.get( - "/info/part", - query("id").isMongoId(), - validator, - roomHandlers.partInfoHandler -); - - // JSON으로 받은 정보로 방을 생성한다. router.post( "/create", diff --git a/src/services/rooms.js b/src/services/rooms.js index 5fab1dfa..0456d534 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -136,28 +136,6 @@ const infoHandler = async (req, res) => { } }; -const partInfoHandler = async (req, res) => { - try { - const user = await userModel.findOne({ id: req.userId }); - const roomObject = await roomModel - .findOne({ _id: req.query.id, "part.user": user._id }) - .lean() - .populate(roomPopulateOption); - if (roomObject) { - res.send(roomObject.part.filter((p) => p.user._id.equals(user._id))[0]); - } else { - res.status(404).json({ - error: "Rooms/info : id does not exist", - }); - } - } catch (err) { - logger.error(err); - res.status(500).json({ - error: "Rooms/info : internal server error", - }); - } -} - const joinHandler = async (req, res) => { try { const user = await userModel @@ -425,7 +403,7 @@ const searchByUserHandler = async (req, res) => { populate: roomPopulateOption, }) .lean(); - console.log(user); + // 정산완료여부 기준으로 진행중인 방과 완료된 방을 분리해서 응답을 전송합니다. const response = {}; response.ongoing = user.ongoingRoom.map((room) => @@ -664,7 +642,6 @@ const settlementHandler = async (req, res) => { module.exports = { publicInfoHandler, infoHandler, - partInfoHandler, createHandler, joinHandler, abortHandler, From 3737777c2321ef113565bd25f56ce35c00b178b3 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Tue, 5 Sep 2023 14:39:05 +0000 Subject: [PATCH 033/261] Remove: parameter userId in emitUpdateEvent --- src/modules/socket.js | 4 ++-- src/services/chats.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/socket.js b/src/modules/socket.js index 1f2990b8..b2ad4966 100644 --- a/src/modules/socket.js +++ b/src/modules/socket.js @@ -187,10 +187,10 @@ const emitChatEvent = async (io, chat) => { } }; -const emitUpdateEvent = async (io, roomId, userId) => { +const emitUpdateEvent = async (io, roomId) => { try { // 방의 모든 사용자에게 socket 메세지 업데이트 이벤트를 발생시킵니다. - if (!io || !roomId || !userId) { + if (!io || !roomId) { throw new IllegalArgumentsException(); } diff --git a/src/services/chats.js b/src/services/chats.js index 51d8efdd..3035ed0c 100644 --- a/src/services/chats.js +++ b/src/services/chats.js @@ -218,7 +218,7 @@ const readChatHandler = async (req, res) => { return res.status(404).send("Chat/read : cannot find room info"); } - if (await emitUpdateEvent(io, roomId, userId)) res.json({ result: true }); + if (await emitUpdateEvent(io, roomId)) res.json({ result: true }); else res.status(500).send("Chat/read : failed to emit socket events"); } catch (e) { res.status(500).send("Chat/read : internal server error"); From 455497ade466daf56b78ebb853636faf7c30fbdc Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Tue, 5 Sep 2023 14:46:20 +0000 Subject: [PATCH 034/261] Fix: disconnectSocket channel --- src/modules/auths/login.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/auths/login.js b/src/modules/auths/login.js index f8e4308c..8181c991 100644 --- a/src/modules/auths/login.js +++ b/src/modules/auths/login.js @@ -27,7 +27,7 @@ const login = (req, sid, id, oid, name) => { const logout = (req) => { // 로그아웃 전 socket.io 소켓들 연결부터 끊기 const io = req.app.get("io"); - if (io) io.in(req.session.id).disconnectSockets(true); + if (io) io.in(`session-${req.session.id}`).disconnectSockets(true); req.session.destroy((err) => { if (err) logger.error(err); From 985991671771628024b4eb3c31f2a9fe22489141 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Tue, 5 Sep 2023 14:47:55 +0000 Subject: [PATCH 035/261] Fix: change endpoint of readChatHandler --- src/routes/chats.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/chats.js b/src/routes/chats.js index 77fdf925..9c0bb56b 100644 --- a/src/routes/chats.js +++ b/src/routes/chats.js @@ -59,7 +59,7 @@ router.post( * 같은 방에 있는 user들에게 업데이트를 요청합니다. */ router.post( - "/readat", + "/read", body("roomId").isMongoId(), body("lastMsgDate").isISO8601(), validator, From 0d1a745ead2478ba05d8a38c69aff91a7b3ca92b Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Tue, 5 Sep 2023 14:49:18 +0000 Subject: [PATCH 036/261] Remove: import formatSettlement --- src/services/chats.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/chats.js b/src/services/chats.js index 3035ed0c..71017bdd 100644 --- a/src/services/chats.js +++ b/src/services/chats.js @@ -8,7 +8,6 @@ const { } = require("../modules/socket"); const { roomPopulateOption, - formatSettlement, } = require("../modules/populates/rooms"); const chatCount = 60; From b6c26e9045f9fb572eb54f307abd0b4889af2842 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Fri, 8 Sep 2023 14:14:26 +0900 Subject: [PATCH 037/261] Add: comment --- src/schedules/notifyAfterArrival.js | 10 ++++++++++ src/schedules/notifyBeforeDepart.js | 12 ++++++++++-- src/services/auth.js | 7 ++++--- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/schedules/notifyAfterArrival.js b/src/schedules/notifyAfterArrival.js index af4caa79..3202af08 100644 --- a/src/schedules/notifyAfterArrival.js +++ b/src/schedules/notifyAfterArrival.js @@ -15,6 +15,11 @@ module.exports = (app) => async () => { const expiredDate = new Date(Date.now() - 90 * MS_PER_MINUTE).toISOString(); const arrivalDate = new Date(Date.now() - 60 * MS_PER_MINUTE).toISOString(); + /** + * 알림을 전송하는 방의 첫 번째 조건은 다음과 같습니다. + * - 출발한 지 60분 이상 90분 이하가 지나야 합니다. + * - 결제를 진행한 사용자가 없거나, 결제가 진행된 후 정산을 하지 않은 사용자가 있어야 합니다. + */ const candidateRooms = await roomModel.find({ $and: [ { time: { $gte: expiredDate } }, @@ -29,6 +34,11 @@ module.exports = (app) => async () => { await Promise.all( candidateRooms.map(async ({ _id: roomId, time }) => { + /** + * 알림을 전송하는 방의 두 번째 조건은 다음과 같습니다. + * - '출발 후 알림'이 아직 전송되지 않았어야 합니다. + * 모든 조건에 만족이 되면 해당 방에 참여 중인 모든 사용자에게 알림이 전송됩니다. + */ const countArrivalChat = await chatModel.countDocuments({ roomId, type: "arrival", diff --git a/src/schedules/notifyBeforeDepart.js b/src/schedules/notifyBeforeDepart.js index 5687827c..ffe66386 100644 --- a/src/schedules/notifyBeforeDepart.js +++ b/src/schedules/notifyBeforeDepart.js @@ -6,8 +6,6 @@ const MS_PER_MINUTE = 60000; /** * 출발까지 15분 남은 방들에 참여하고 있는 사용자들에게 리마인더 알림을 전송합니다. - * @summary 메시지는 보내지 않습니다. ㅎㅎ - * @return {Promise} 알림 전송에 실패한 기기 수를 반환합니다. */ module.exports = (app) => async () => { @@ -16,6 +14,11 @@ module.exports = (app) => async () => { const currentDate = new Date(Date.now()).toISOString(); const departDate = new Date(Date.now() + 15 * MS_PER_MINUTE).toISOString(); + /** + * 알림을 전송하는 방의 첫 번째 조건은 다음과 같습니다. + * - 출출발까지 15분 이하가 남아있어야 합니다. + * - 2명 이상이 방에 참여 중이어야 합니다. + */ const candidatesRooms = await roomModel.find({ $and: [ { time: { $gte: currentDate } }, @@ -26,6 +29,11 @@ module.exports = (app) => async () => { await Promise.all( candidatesRooms.map(async ({ _id: roomId, time }) => { + /** + * 알림을 전송하는 방의 두 번째 조건은 다음과 같습니다. + * - '출발 후 알림'이 아직 전송되지 않았어야 합니다. + * 모든 조건에 만족이 되면 해당 방에 참여 중인 모든 사용자에게 알림이 전송됩니다. + */ const countDepartureChat = await chatModel.countDocuments({ roomId, type: "departure", diff --git a/src/services/auth.js b/src/services/auth.js index 1c62a817..bab3fc71 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -115,12 +115,14 @@ const sparcsssoHandler = (req, res) => { const sparcsssoCallbackHandler = (req, res) => { const loginAfterState = req.session?.loginAfterState; + const { state: stateForCmp, code } = req.query; + if (!loginAfterState) return res.status(400).send("SparcsssoCallbackHandler : invalid request"); - const { state, redirectOrigin, redirectPath } = loginAfterState; - const stateForCmp = req.body.state || req.query.state; + const { state, redirectOrigin, redirectPath } = loginAfterState; req.session.loginAfterState = undefined; + if (!state || !redirectOrigin || !redirectPath) { return res.status(400).send("SparcsssoCallbackHandler : invalid request"); } @@ -130,7 +132,6 @@ const sparcsssoCallbackHandler = (req, res) => { return res.redirect(redirectUrl); } - const code = req.body.code || req.query.code; client.getUserInfo(code).then((userDataBefore) => { const userData = transUserData(userDataBefore); const isTestAccount = testAccounts?.includes(userData.email); From 59a541735a23f435ae823063adee761a5a19d4da Mon Sep 17 00:00:00 2001 From: withsang Date: Fri, 8 Sep 2023 20:46:21 +0900 Subject: [PATCH 038/261] Refactor: separate database connection codes --- app.js | 4 ++++ src/modules/stores/mongo.js | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app.js b/app.js index 6049e736..252bdb18 100644 --- a/app.js +++ b/app.js @@ -3,6 +3,7 @@ const express = require("express"); const http = require("http"); const { port: httpPort } = require("./loadenv"); const logger = require("./src/modules/logger"); +const { connectDatabase } = require("./src/modules/stores/mongo"); const { startSocketServer } = require("./src/modules/socket"); // Firebase Admin 초기설정 @@ -11,6 +12,9 @@ require("./src/modules/fcm").initializeApp(); // 익스프레스 서버 생성 const app = express(); +// 데이터베이스 연결 +connectDatabase(); + // [Middleware] request body 파싱 app.use(express.urlencoded({ extended: false })); app.use(express.json()); diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index bfa94da9..c255d7b6 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -190,12 +190,14 @@ database.on("disconnected", function () { }, 5000); }); -mongoose.connect(mongoUrl, { - useNewUrlParser: true, - useUnifiedTopology: true, -}); +const connectDatabase = () => + mongoose.connect(mongoUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); module.exports = { + connectDatabase, userModel: mongoose.model("User", userSchema), deviceTokenModel: mongoose.model("DeviceToken", deviceTokenSchema), notificationOptionModel: mongoose.model( From 1521e7fc53dc46d4268035dcee59a7c2586487ae Mon Sep 17 00:00:00 2001 From: withsang Date: Fri, 8 Sep 2023 20:54:44 +0900 Subject: [PATCH 039/261] Add: write mongo.js --- src/lottery2023fall/index.js | 7 +++++++ src/lottery2023fall/modules/stores/mongo.js | 2 ++ 2 files changed, 9 insertions(+) create mode 100644 src/lottery2023fall/index.js create mode 100644 src/lottery2023fall/modules/stores/mongo.js diff --git a/src/lottery2023fall/index.js b/src/lottery2023fall/index.js new file mode 100644 index 00000000..9548dcc7 --- /dev/null +++ b/src/lottery2023fall/index.js @@ -0,0 +1,7 @@ +const express = require("express"); + +const checkReward = (req, res, next) => { + next(); +}; + +exports.default = { checkReward }; diff --git a/src/lottery2023fall/modules/stores/mongo.js b/src/lottery2023fall/modules/stores/mongo.js new file mode 100644 index 00000000..29896dd3 --- /dev/null +++ b/src/lottery2023fall/modules/stores/mongo.js @@ -0,0 +1,2 @@ +const mongoose = require("mongoose"); +const Schema = mongoose.Schema; From 80d2485e267d678345c5df63c9e214b08e7e0246 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 8 Sep 2023 22:15:08 +0900 Subject: [PATCH 040/261] Add: schemas for the event --- src/lottery2023fall/modules/stores/mongo.js | 117 ++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/src/lottery2023fall/modules/stores/mongo.js b/src/lottery2023fall/modules/stores/mongo.js index 29896dd3..8a45df02 100644 --- a/src/lottery2023fall/modules/stores/mongo.js +++ b/src/lottery2023fall/modules/stores/mongo.js @@ -1,2 +1,119 @@ const mongoose = require("mongoose"); const Schema = mongoose.Schema; + +const eventStatusSchema = Schema({ + userId: { + type: Schema.Types.ObjectId, + ref: "User", + required: true, + }, + eventList: { + type: [Schema.Types.ObjectId], + default: [], + ref: "Event", + }, + creditAmount: { + type: Number, + default: 0, + }, +}); + +const eventSchema = Schema({ + name: { + type: String, + required: true, + }, + rewardAmount: { + type: Number, + required: true, + }, + maxCount: { + type: Number, + default: 1, + }, + expireat: { + type: Date, + required: true, + }, + isDisabled: { + type: Boolean, + default: false, + }, +}); + +const itemSchema = Schema({ + name: { + type: String, + required: true, + }, + imageUrl: { + type: String, + required: true, + }, + price: { + type: Number, + required: true, + }, + description: { + type: String, + required: true, + }, + isDisabled: { + type: Boolean, + default: false, + }, + stock: { + type: Number, + required: true, + }, + itemType: { + type: Number, + enum: [0, 1, 2, 3], + required: true, + }, + isRandomItem: { + type: Boolean, + required: true, + }, + randomWeight: { + type: Number, + required: true, + }, +}); + +const transactionSchema = Schema({ + type: { + type: String, + enum: ["get", "use"], + required: true, + }, + amount: { + type: Number, + required: true, + }, + eventId: { + type: Schema.Types.ObjectId, + ref: "Event", + required: true, // null이 될 수도 있지만 대부분의 경우 null이 아닐 것이기에, 안전성을 위해 true로 설정합니다. + }, + itemId: { + type: Schema.Types.ObjectId, + ref: "Item", + default: null, + }, + comment: { + type: String, + required: true, + }, +}); +transactionSchema.set("timestamps", { + createdAt: "doneat", + updatedAt: false, +}); + +module.exports = { + eventStatusModel: mongoose.model("EventStatus", eventStatusSchema), + eventModel: mongoose.model("Event", eventSchema), + itemModel: mongoose.model("Item", itemSchema), + transactionModel: mongoose.model("Transaction", transactionSchema), +}; From aac87cb675f6a70811882aa0128030fa9d0ed93c Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 01:34:38 +0900 Subject: [PATCH 041/261] Add: /event/23-chuseok/transacations endpoint --- app.js | 3 +++ src/lottery2023fall/index.js | 9 ++++++++- src/lottery2023fall/modules/stores/mongo.js | 5 +++++ src/lottery2023fall/routes/transactions.js | 11 +++++++++++ src/lottery2023fall/services/transactions.js | 19 +++++++++++++++++++ src/middlewares/auth.js | 3 ++- 6 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/lottery2023fall/routes/transactions.js create mode 100644 src/lottery2023fall/services/transactions.js diff --git a/app.js b/app.js index 252bdb18..9ed4ef29 100644 --- a/app.js +++ b/app.js @@ -55,6 +55,9 @@ app.use("/locations", require("./src/routes/locations")); app.use("/reports", require("./src/routes/reports")); app.use("/notifications", require("./src/routes/notifications")); +// 2023 추석 이벤트 전용 라우터입니다. +app.use("/event/23-chuseok", require("./src/lottery2023fall").lotteryRouter); + // [Middleware] 전역 에러 핸들러. 에러 핸들러는 router들보다 아래에 등록되어야 합니다. app.use(require("./src/middlewares/errorHandler")); diff --git a/src/lottery2023fall/index.js b/src/lottery2023fall/index.js index 9548dcc7..042091fb 100644 --- a/src/lottery2023fall/index.js +++ b/src/lottery2023fall/index.js @@ -4,4 +4,11 @@ const checkReward = (req, res, next) => { next(); }; -exports.default = { checkReward }; +const lotteryRouter = express.Router(); + +lotteryRouter.use("/transactions", require("./routes/transactions")); + +module.exports = { + checkReward, + lotteryRouter, +}; diff --git a/src/lottery2023fall/modules/stores/mongo.js b/src/lottery2023fall/modules/stores/mongo.js index 8a45df02..3d2055d5 100644 --- a/src/lottery2023fall/modules/stores/mongo.js +++ b/src/lottery2023fall/modules/stores/mongo.js @@ -91,6 +91,11 @@ const transactionSchema = Schema({ type: Number, required: true, }, + userId: { + type: Schema.Types.ObjectId, + ref: "User", + require: true, + }, eventId: { type: Schema.Types.ObjectId, ref: "Event", diff --git a/src/lottery2023fall/routes/transactions.js b/src/lottery2023fall/routes/transactions.js new file mode 100644 index 00000000..391e0cf6 --- /dev/null +++ b/src/lottery2023fall/routes/transactions.js @@ -0,0 +1,11 @@ +const express = require("express"); + +const router = express.Router(); +const transactionsHandlers = require("../services/transactions"); + +// 라우터 접근 시 로그인 필요 +router.use(require("../../middlewares/auth")); + +router.get("/", transactionsHandlers.getUserTransactionsHandler); + +module.exports = router; diff --git a/src/lottery2023fall/services/transactions.js b/src/lottery2023fall/services/transactions.js new file mode 100644 index 00000000..90c14d75 --- /dev/null +++ b/src/lottery2023fall/services/transactions.js @@ -0,0 +1,19 @@ +const { transactionModel } = require("../modules/stores/mongo"); +const logger = require("../../modules/logger"); + +const getUserTransactionsHandler = async (req, res) => { + try { + const transactions = await transactionModel.find( + { userId: req.userOid }, + { userId: 0 } + ); + res.json({ transactions }); // userId는 이미 Frontend에서 알고 있고, 중복되는 값이므로 제외합니다. + } catch (err) { + logger.error(err); + res.status(500).json({ error: "Transactions/ : internal server error" }); + } +}; + +module.exports = { + getUserTransactionsHandler, +}; diff --git a/src/middlewares/auth.js b/src/middlewares/auth.js index 3e3e7f35..e521f9f4 100644 --- a/src/middlewares/auth.js +++ b/src/middlewares/auth.js @@ -8,8 +8,9 @@ const authMiddleware = (req, res, next) => { error: "not logged in", }); } else { - const { id } = getLoginInfo(req); + const { id, oid } = getLoginInfo(req); req.userId = id; + req.userOid = oid; next(); } }; From b9252f9c7707b30f3d52cc9e20364c209571c7c8 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 01:44:18 +0900 Subject: [PATCH 042/261] Add: .vscode to .gitignore file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8ffa241b..a7e86767 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.code-workspace *.swp /logs/*.log +.vscode # AdminJS 관련 디렉토리 .adminjs From 2e647b3a7e01f137209afdbe4581db8b2a0e95d9 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 02:01:05 +0900 Subject: [PATCH 043/261] Fix: fix error due to database connection during test --- test/utils.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/utils.js b/test/utils.js index 5fbfcd38..b3920e83 100644 --- a/test/utils.js +++ b/test/utils.js @@ -4,9 +4,12 @@ const { chatModel, locationModel, reportModel, + connectDatabase, } = require("../src/modules/stores/mongo"); const { generateProfileImageUrl } = require("../src/modules/modifyProfile"); +connectDatabase(); + // 테스트를 위한 유저 생성 함수 const userGenerator = async (username, testData) => { const testUser = new userModel({ From 872269e9ae05984bac679881aaecc88f4e6dee2f Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 02:23:17 +0900 Subject: [PATCH 044/261] Add: eventMode in loadenv.js --- app.js | 5 +++-- loadenv.js | 1 + src/{lottery2023fall => lottery}/index.js | 0 src/{lottery2023fall => lottery}/modules/stores/mongo.js | 0 src/{lottery2023fall => lottery}/routes/transactions.js | 0 src/{lottery2023fall => lottery}/services/transactions.js | 0 6 files changed, 4 insertions(+), 2 deletions(-) rename src/{lottery2023fall => lottery}/index.js (100%) rename src/{lottery2023fall => lottery}/modules/stores/mongo.js (100%) rename src/{lottery2023fall => lottery}/routes/transactions.js (100%) rename src/{lottery2023fall => lottery}/services/transactions.js (100%) diff --git a/app.js b/app.js index 9ed4ef29..fae5e09c 100644 --- a/app.js +++ b/app.js @@ -1,7 +1,7 @@ // 모듈 require const express = require("express"); const http = require("http"); -const { port: httpPort } = require("./loadenv"); +const { port: httpPort, eventMode } = require("./loadenv"); const logger = require("./src/modules/logger"); const { connectDatabase } = require("./src/modules/stores/mongo"); const { startSocketServer } = require("./src/modules/socket"); @@ -56,7 +56,8 @@ app.use("/reports", require("./src/routes/reports")); app.use("/notifications", require("./src/routes/notifications")); // 2023 추석 이벤트 전용 라우터입니다. -app.use("/event/23-chuseok", require("./src/lottery2023fall").lotteryRouter); +eventMode && + app.use(`/events/${eventMode}`, require("./src/lottery").lotteryRouter); // [Middleware] 전역 에러 핸들러. 에러 핸들러는 router들보다 아래에 등록되어야 합니다. app.use(require("./src/middlewares/errorHandler")); diff --git a/loadenv.js b/loadenv.js index 62c50d7f..2bce4fb9 100644 --- a/loadenv.js +++ b/loadenv.js @@ -38,4 +38,5 @@ module.exports = { slackWebhookUrl: { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, + eventMode: undefined, }; diff --git a/src/lottery2023fall/index.js b/src/lottery/index.js similarity index 100% rename from src/lottery2023fall/index.js rename to src/lottery/index.js diff --git a/src/lottery2023fall/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js similarity index 100% rename from src/lottery2023fall/modules/stores/mongo.js rename to src/lottery/modules/stores/mongo.js diff --git a/src/lottery2023fall/routes/transactions.js b/src/lottery/routes/transactions.js similarity index 100% rename from src/lottery2023fall/routes/transactions.js rename to src/lottery/routes/transactions.js diff --git a/src/lottery2023fall/services/transactions.js b/src/lottery/services/transactions.js similarity index 100% rename from src/lottery2023fall/services/transactions.js rename to src/lottery/services/transactions.js From a717876dfd2a456de6b4c209b0105ece53544bde Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 02:34:40 +0900 Subject: [PATCH 045/261] Fix: invalid schema definition --- src/lottery/modules/stores/mongo.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 3d2055d5..46888d15 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -99,12 +99,10 @@ const transactionSchema = Schema({ eventId: { type: Schema.Types.ObjectId, ref: "Event", - required: true, // null이 될 수도 있지만 대부분의 경우 null이 아닐 것이기에, 안전성을 위해 true로 설정합니다. }, itemId: { type: Schema.Types.ObjectId, ref: "Item", - default: null, }, comment: { type: String, From 0fd24f84c9da36a154c501a973ca515ed94457c9 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 02:44:17 +0900 Subject: [PATCH 046/261] Refactor: database query readability improved --- src/lottery/services/transactions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lottery/services/transactions.js b/src/lottery/services/transactions.js index 90c14d75..85f6a993 100644 --- a/src/lottery/services/transactions.js +++ b/src/lottery/services/transactions.js @@ -5,7 +5,7 @@ const getUserTransactionsHandler = async (req, res) => { try { const transactions = await transactionModel.find( { userId: req.userOid }, - { userId: 0 } + "-userId" ); res.json({ transactions }); // userId는 이미 Frontend에서 알고 있고, 중복되는 값이므로 제외합니다. } catch (err) { From 8bc0d4fc10441e1776a6171f32a637127f150145 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 02:46:36 +0900 Subject: [PATCH 047/261] Fix: a typo of schema --- src/lottery/modules/stores/mongo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 46888d15..9df79ca7 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -94,7 +94,7 @@ const transactionSchema = Schema({ userId: { type: Schema.Types.ObjectId, ref: "User", - require: true, + required: true, }, eventId: { type: Schema.Types.ObjectId, From 46db604c0bfa4ad6f8a6ee32c1fb43d54125ff78 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 02:58:53 +0900 Subject: [PATCH 048/261] Add: validator for integer fields --- src/lottery/modules/stores/mongo.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 9df79ca7..c6b38d03 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -1,6 +1,11 @@ const mongoose = require("mongoose"); const Schema = mongoose.Schema; +const integerValidator = { + validator: Number.isInteger, + message: "{VALUE} is not an integer value", +}; + const eventStatusSchema = Schema({ userId: { type: Schema.Types.ObjectId, @@ -15,6 +20,8 @@ const eventStatusSchema = Schema({ creditAmount: { type: Number, default: 0, + min: 0, + validate: integerValidator, }, }); @@ -26,10 +33,14 @@ const eventSchema = Schema({ rewardAmount: { type: Number, required: true, + min: 0, + validate: integerValidator, }, maxCount: { type: Number, default: 1, + min: 0, + validate: integerValidator, }, expireat: { type: Date, @@ -53,6 +64,8 @@ const itemSchema = Schema({ price: { type: Number, required: true, + min: 0, + validate: integerValidator, }, description: { type: String, @@ -65,6 +78,8 @@ const itemSchema = Schema({ stock: { type: Number, required: true, + min: 0, + validate: integerValidator, }, itemType: { type: Number, @@ -78,6 +93,8 @@ const itemSchema = Schema({ randomWeight: { type: Number, required: true, + min: 0, + validate: integerValidator, }, }); @@ -90,6 +107,8 @@ const transactionSchema = Schema({ amount: { type: Number, required: true, + min: 0, + validate: integerValidator, }, userId: { type: Schema.Types.ObjectId, From 0ada3ce687fc357ffceea9ea5812cbe2179018aa Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 04:34:11 +0900 Subject: [PATCH 049/261] Add: /events/2023fall/global-state endpoint --- src/lottery/index.js | 1 + src/lottery/routes/globalState.js | 11 +++++++ src/lottery/services/globalState.js | 46 +++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 src/lottery/routes/globalState.js create mode 100644 src/lottery/services/globalState.js diff --git a/src/lottery/index.js b/src/lottery/index.js index 042091fb..373d98c8 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -6,6 +6,7 @@ const checkReward = (req, res, next) => { const lotteryRouter = express.Router(); +lotteryRouter.use("/global-state", require("./routes/globalState")); lotteryRouter.use("/transactions", require("./routes/transactions")); module.exports = { diff --git a/src/lottery/routes/globalState.js b/src/lottery/routes/globalState.js new file mode 100644 index 00000000..f0f75406 --- /dev/null +++ b/src/lottery/routes/globalState.js @@ -0,0 +1,11 @@ +const express = require("express"); + +const router = express.Router(); +const globalStateHandlers = require("../services/globalState"); + +// 라우터 접근 시 로그인 필요 +router.use(require("../../middlewares/auth")); + +router.get("/", globalStateHandlers.getUserGlobalStateHandler); + +module.exports = router; diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js new file mode 100644 index 00000000..dcb31030 --- /dev/null +++ b/src/lottery/services/globalState.js @@ -0,0 +1,46 @@ +const { + eventStatusModel, + transactionModel, + itemModel, +} = require("../modules/stores/mongo"); +const logger = require("../../modules/logger"); + +const getUserGlobalStateHandler = async (req, res) => { + try { + const eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); + const itemPurchaseTransactions = await transactionModel.find({ + userId: req.userOid, + type: "use", + itemId: { + $exists: true, + $ne: null, + }, + }); + let ticket1Amount = 0; + let ticket2Amount = 0; + + for (const purchase of itemPurchaseTransactions) { + const item = await itemModel.findOne({ _id: purchase.itemId }); + + if (item.itemType == 1) { + ticket1Amount++; + } else if (item.itemType == 2) { + ticket2Amount++; + } + } + + res.json({ + creditAmount: eventStatus.creditAmount, + eventStatus: eventStatus.eventList.map((id) => id.toString()), + ticket1Amount, + ticket2Amount, + }); + } catch (err) { + logger.error(err); + res.status(500).json({ error: "GlobalState/ : internal server error" }); + } +}; + +module.exports = { + getUserGlobalStateHandler, +}; From 1551b5d4a97c4e6d0264bbef0ad3c04d4a1d1721 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 04:39:53 +0900 Subject: [PATCH 050/261] Add: getUserCreditAmount function in globalState.js --- src/lottery/services/globalState.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index dcb31030..d20d32d6 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -5,6 +5,18 @@ const { } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); +const getUserCreditAmount = async (req) => { + const eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); + + return { + creditAmount: eventStatus.creditAmount, + creditUpdate: async (delta) => { + eventStatus.creditAmount += delta; + await eventStatus.save(); + }, + }; +}; + const getUserGlobalStateHandler = async (req, res) => { try { const eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); @@ -42,5 +54,6 @@ const getUserGlobalStateHandler = async (req, res) => { }; module.exports = { + getUserCreditAmount, getUserGlobalStateHandler, }; From 6a85be9219e31bb568d46500b4b2da82aa6bb3f2 Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 9 Sep 2023 05:41:49 +0900 Subject: [PATCH 051/261] Add: implement event/items/list --- src/lottery/routes/items.js | 8 ++++++++ src/lottery/services/items.js | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/lottery/routes/items.js create mode 100644 src/lottery/services/items.js diff --git a/src/lottery/routes/items.js b/src/lottery/routes/items.js new file mode 100644 index 00000000..1ca0bc32 --- /dev/null +++ b/src/lottery/routes/items.js @@ -0,0 +1,8 @@ +const express = require("express"); + +const router = express.Router(); +const itemsHandlers = require("../services/items"); + +router.get("/list", itemsHandlers.listHandler); + +module.exports = router; diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js new file mode 100644 index 00000000..9246f353 --- /dev/null +++ b/src/lottery/services/items.js @@ -0,0 +1,19 @@ +const { itemModel } = require("../modules/stores/mongo"); +const logger = require("../../modules/logger"); + +const listHandler = async (_, res) => { + try { + const items = await itemModel.find( + {}, + "name imageUrl price description isDisabled stock" + ); + res.json({ items }); + } catch (err) { + logger.error(err); + res.status(500).json({ error: "items/list : internal server error" }); + } +}; + +module.exports = { + listHandler, +}; From 2191d12a1c41e63bd06bb0f34abec70b00c2de29 Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 9 Sep 2023 05:42:11 +0900 Subject: [PATCH 052/261] Docs: add event/items docs --- src/lottery/index.js | 6 +++ src/lottery/routes/docs/index.js | 22 ++++++++ src/lottery/routes/docs/items.js | 73 ++++++++++++++++++++++++++ src/lottery/routes/docs/swaggerDocs.js | 22 ++++++++ src/routes/docs/logininfo.js | 2 +- 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/lottery/routes/docs/index.js create mode 100644 src/lottery/routes/docs/items.js create mode 100644 src/lottery/routes/docs/swaggerDocs.js diff --git a/src/lottery/index.js b/src/lottery/index.js index 042091fb..9036da92 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -1,12 +1,18 @@ const express = require("express"); +// [Routes] 기존 docs 라우터의 docs extend +require("./routes/docs")(); + +// [Middleware] 목표 달성 여부 검증 const checkReward = (req, res, next) => { next(); }; const lotteryRouter = express.Router(); +// [Router] APIs lotteryRouter.use("/transactions", require("./routes/transactions")); +lotteryRouter.use("/items", require("./routes/items")); module.exports = { checkReward, diff --git a/src/lottery/routes/docs/index.js b/src/lottery/routes/docs/index.js new file mode 100644 index 00000000..fa845079 --- /dev/null +++ b/src/lottery/routes/docs/index.js @@ -0,0 +1,22 @@ +const swaggerUi = require("swagger-ui-express"); +const swaggerDocs = require("../../../routes/docs/swaggerDocs"); +const eventSwaggerDocs = require("./swaggerDocs"); + +swaggerDocs.tags = [...swaggerDocs.tags, ...eventSwaggerDocs.tags]; + +swaggerDocs.paths = { + ...swaggerDocs.paths, + ...eventSwaggerDocs.paths, +}; + +swaggerDocs.components.schemas = { + ...swaggerDocs.components.schemas, + ...eventSwaggerDocs.components.schemas, +}; + +/** 기존 docs 라우터에 이벤트 API docs를 추가합니다. */ +const appendEventDocs = () => { + swaggerUi.setup(swaggerDocs, { explorer: true }); +}; + +module.exports = appendEventDocs; diff --git a/src/lottery/routes/docs/items.js b/src/lottery/routes/docs/items.js new file mode 100644 index 00000000..ac061578 --- /dev/null +++ b/src/lottery/routes/docs/items.js @@ -0,0 +1,73 @@ +const { eventMode } = require("../../../../loadenv"); +const apiPrefix = `/events/${eventMode}/items`; + +const itemsDocs = {}; +itemsDocs[`${apiPrefix}/list`] = { + get: { + tags: [`${apiPrefix}`], + summary: "상점에서 판매하는 모든 상품의 목록 반환", + description: + "상점에서 판매하는 모든 상품의 목록을 가져옵니다. 매진된 상품도 가져옵니다.", + responses: { + 200: { + description: "", + content: { + "application/json": { + schema: { + type: "object", + properties: { + items: { + type: "array", + description: "Item의 배열", + items: { + type: "object", + properties: { + _id: { + type: "string", + description: "Item의 ObjectId", + example: "OBJECT ID", + }, + name: { + type: "string", + description: "상품의 이름", + example: "랜덤 상자", + }, + imageUrl: { + type: "string", + description: "이미지 썸네일 URL", + example: "THUMBNAIL URL", + }, + price: { + type: "number", + description: "상품의 가격. 0 이상입니다.", + example: 400, + }, + description: { + type: "string", + description: "상품의 설명", + example: + "랜덤으로 상품이 나오는 상자입니다. 확률은 다음과 같습니다: 진짜송편 100%, 치킨 0%, ...", + }, + isDisabled: { + type: "boolean", + description: "판매 중지 여부", + example: false, + }, + stock: { + type: "number", + description: "남은 상품 재고. 0 이상입니다.", + example: 10, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}; + +module.exports = itemsDocs; diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js new file mode 100644 index 00000000..bdbed6f3 --- /dev/null +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -0,0 +1,22 @@ +const { eventMode } = require("../../../../loadenv"); +const itemsDocs = require("./items"); + +const apiPrefix = `/events/${eventMode}`; + +const eventSwaggerDocs = { + tags: [ + { + // TODO: edit + name: `${apiPrefix}/items`, + description: "이벤트 - 아이템 관련 API", + }, + ], + paths: { + ...itemsDocs, + }, + components: { + schemas: {}, + }, +}; + +module.exports = eventSwaggerDocs; diff --git a/src/routes/docs/logininfo.js b/src/routes/docs/logininfo.js index ec424f63..b24c5fe6 100644 --- a/src/routes/docs/logininfo.js +++ b/src/routes/docs/logininfo.js @@ -40,7 +40,7 @@ const logininfoDocs = { agreeOnTermsOfService: { type: "boolean", }, - subinfio: { + subinfo: { type: "object", properties: { kaist: { From 8a820557021fb2ed7f9b6e99edca3637dd8ebe93 Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 9 Sep 2023 05:45:49 +0900 Subject: [PATCH 053/261] Fix: remove unnecessary comment --- src/lottery/routes/docs/swaggerDocs.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index bdbed6f3..3320eaf0 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -6,7 +6,6 @@ const apiPrefix = `/events/${eventMode}`; const eventSwaggerDocs = { tags: [ { - // TODO: edit name: `${apiPrefix}/items`, description: "이벤트 - 아이템 관련 API", }, From 04c3ed540f7567aac11fa503e0559a52b96fffd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeong=20Sang=20=28=EC=A0=95=EC=83=81=29?= Date: Sat, 9 Sep 2023 05:57:49 +0900 Subject: [PATCH 054/261] Refactor: change error message to Capital case thank you Co-authored-by: static --- src/lottery/services/items.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 9246f353..ca832f60 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -10,7 +10,7 @@ const listHandler = async (_, res) => { res.json({ items }); } catch (err) { logger.error(err); - res.status(500).json({ error: "items/list : internal server error" }); + res.status(500).json({ error: "Items/List : internal server error" }); } }; From 56e797ae271c53b5e1f64e614190ea097cc5672d Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 07:28:58 +0900 Subject: [PATCH 055/261] Add: docs/globalState.js --- src/lottery/routes/docs/globalState.js | 52 ++++++++++++++++++++++++++ src/lottery/routes/docs/swaggerDocs.js | 6 +++ 2 files changed, 58 insertions(+) create mode 100644 src/lottery/routes/docs/globalState.js diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js new file mode 100644 index 00000000..432c7846 --- /dev/null +++ b/src/lottery/routes/docs/globalState.js @@ -0,0 +1,52 @@ +const { eventMode } = require("../../../../loadenv"); +const apiPrefix = `/events/${eventMode}/global-state`; + +const globalStateDocs = {}; +globalStateDocs[`${apiPrefix}/`] = { + get: { + tags: [`${apiPrefix}`], + summary: "Frontend에서 Global state로 관리하는 정보 반환", + description: + "유저의 재화 개수, 이벤트 달성 상태, 추첨권 개수 등 Frontend에서 Global state로 관리할 정보를 가져옵니다.", + responses: { + 200: { + description: "", + content: { + "application/json": { + schema: { + type: "object", + properties: { + creditAmount: { + type: "number", + description: "재화 개수. 0 이상입니다.", + example: 10000, + }, + eventStatus: { + type: "array", + description: + "유저가 달성한 이벤트의 배열. 여러 번 달성할 수 있는 이벤트의 경우 배열 내에 같은 이벤트가 여러 번 포함될 수 있습니다.", + items: { + type: "string", + description: "Event의 ObjectId", + }, + }, + ticket1Amount: { + type: "number", + description: "추첨권 (1)의 개수. 0 이상입니다.", + example: 10, + }, + ticket2Amount: { + type: "number", + description: "추첨권 (2)의 개수. 0 이상입니다.", + example: 10, + }, + }, + }, + }, + }, + }, + }, + }, +}; + +module.exports = globalStateDocs; diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index 3320eaf0..4896f122 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -1,16 +1,22 @@ const { eventMode } = require("../../../../loadenv"); +const globalStateDocs = require("./globalState"); const itemsDocs = require("./items"); const apiPrefix = `/events/${eventMode}`; const eventSwaggerDocs = { tags: [ + { + name: `${apiPrefix}/global-state`, + description: "이벤트 - Global State 관련 API", + }, { name: `${apiPrefix}/items`, description: "이벤트 - 아이템 관련 API", }, ], paths: { + ...globalStateDocs, ...itemsDocs, }, components: { From de9a463cdef43ff01776c9fcd65fe47e23642e2b Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 11:07:35 +0900 Subject: [PATCH 056/261] Add: creating EventStatus document --- src/lottery/services/globalState.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index d20d32d6..e9d70bf1 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -19,7 +19,16 @@ const getUserCreditAmount = async (req) => { const getUserGlobalStateHandler = async (req, res) => { try { - const eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); + let eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); + if (!eventStatus) { + // User마다 EventStatus를 가져야 하고, 현재 Taxi에는 회원 탈퇴 시스템이 없으므로, EventStatus가 없으면 새롭게 생성하도록 구현합니다. + // EventStatus의 생성은 이곳에서만 이루어집니다!! + eventStatus = new eventStatusModel({ + userId: req.userOid, + }); + await eventStatus.save(); + } + const itemPurchaseTransactions = await transactionModel.find({ userId: req.userOid, type: "use", From 1abbb58a9d009c86a4d5da05e25848d6767ccc98 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 11:11:29 +0900 Subject: [PATCH 057/261] Docs: edit description of /events/2023fall/global-state endpoint --- src/lottery/routes/docs/globalState.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 432c7846..8499e8fb 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -7,7 +7,7 @@ globalStateDocs[`${apiPrefix}/`] = { tags: [`${apiPrefix}`], summary: "Frontend에서 Global state로 관리하는 정보 반환", description: - "유저의 재화 개수, 이벤트 달성 상태, 추첨권 개수 등 Frontend에서 Global state로 관리할 정보를 가져옵니다.", + "유저의 재화 개수, 이벤트 달성 상태, 추첨권 개수 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성하며, 유일한 생성 지점입니다.", responses: { 200: { description: "", From c1aeca213e8b7c16ebee517a704c57e5c2065690 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 11:22:28 +0900 Subject: [PATCH 058/261] Refactor: add null handling --- src/lottery/services/globalState.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index e9d70bf1..2dd4b6ac 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -7,6 +7,7 @@ const logger = require("../../modules/logger"); const getUserCreditAmount = async (req) => { const eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); + if (!eventStatus) return null; return { creditAmount: eventStatus.creditAmount, From 6b2d331e442fceff6a20559eaaf4d68010add42d Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 12:11:00 +0900 Subject: [PATCH 059/261] Refactor: use Promise.all instead of for ... of --- src/lottery/services/globalState.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 2dd4b6ac..9eab3184 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -30,6 +30,9 @@ const getUserGlobalStateHandler = async (req, res) => { await eventStatus.save(); } + let ticket1Amount = 0; + let ticket2Amount = 0; + const itemPurchaseTransactions = await transactionModel.find({ userId: req.userOid, type: "use", @@ -38,18 +41,17 @@ const getUserGlobalStateHandler = async (req, res) => { $ne: null, }, }); - let ticket1Amount = 0; - let ticket2Amount = 0; - - for (const purchase of itemPurchaseTransactions) { - const item = await itemModel.findOne({ _id: purchase.itemId }); + await Promise.all( + itemPurchaseTransactions.map(async (purchase) => { + const item = await itemModel.findOne({ _id: purchase.itemId }); - if (item.itemType == 1) { - ticket1Amount++; - } else if (item.itemType == 2) { - ticket2Amount++; - } - } + if (item.itemType === 1) { + ticket1Amount++; + } else if (item.itemType === 2) { + ticket2Amount++; + } + }) + ); res.json({ creditAmount: eventStatus.creditAmount, From 5a4f28ef54b17be9544e733c4d18013a3c942911 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 12:45:16 +0900 Subject: [PATCH 060/261] Add: modules/credit.js --- src/lottery/modules/credit.js | 18 ++++++++++++++++++ src/lottery/services/globalState.js | 13 ------------- 2 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 src/lottery/modules/credit.js diff --git a/src/lottery/modules/credit.js b/src/lottery/modules/credit.js new file mode 100644 index 00000000..10fabd94 --- /dev/null +++ b/src/lottery/modules/credit.js @@ -0,0 +1,18 @@ +const { eventStatusModel } = require("../modules/stores/mongo"); + +const getUserCreditAmount = async (req) => { + const eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); + if (!eventStatus) return null; + + return { + creditAmount: eventStatus.creditAmount, + creditUpdate: async (delta) => { + eventStatus.creditAmount += delta; + await eventStatus.save(); + }, + }; +}; + +module.exports = { + getUserCreditAmount, +}; diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 9eab3184..7993eb36 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -5,19 +5,6 @@ const { } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); -const getUserCreditAmount = async (req) => { - const eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); - if (!eventStatus) return null; - - return { - creditAmount: eventStatus.creditAmount, - creditUpdate: async (delta) => { - eventStatus.creditAmount += delta; - await eventStatus.save(); - }, - }; -}; - const getUserGlobalStateHandler = async (req, res) => { try { let eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); From aa1af16163dc6e0c55adb7656b13f411dd8acfea Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 14:16:19 +0900 Subject: [PATCH 061/261] Add: /events/2023fall/items/purchase/:itemId endpoint --- src/lottery/routes/items.js | 2 + src/lottery/services/globalState.js | 1 - src/lottery/services/items.js | 124 +++++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 2 deletions(-) diff --git a/src/lottery/routes/items.js b/src/lottery/routes/items.js index 1ca0bc32..539288bc 100644 --- a/src/lottery/routes/items.js +++ b/src/lottery/routes/items.js @@ -2,7 +2,9 @@ const express = require("express"); const router = express.Router(); const itemsHandlers = require("../services/items"); +const auth = require("../../middlewares/auth"); router.get("/list", itemsHandlers.listHandler); +router.post("/purchase/:itemId", auth, itemsHandlers.purchaseHandler); module.exports = router; diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 7993eb36..37e9731c 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -53,6 +53,5 @@ const getUserGlobalStateHandler = async (req, res) => { }; module.exports = { - getUserCreditAmount, getUserGlobalStateHandler, }; diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index ca832f60..2633f213 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -1,5 +1,54 @@ -const { itemModel } = require("../modules/stores/mongo"); +const { itemModel, transactionModel } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); +const { getUserCreditAmount } = require("../modules/credit"); + +const getRandomItem = async (req, depth) => { + if (depth === 10) return null; + + const items = await itemModel.find(); + const randomItems = []; + + for (const item of items) { + if (!item.isRandomItem) continue; + if (item.stock === 0) continue; + if (item.isDisabled) continue; + + randomItems.push(...Array(item.randomWeight).fill(item)); + } + + logger.info( + `유저 "${req.userOid}"에 의해 getRandomItem(depth=${depth})가 호출되었습니다.` + ); + logger.info( + `유저 "${req.userOid}"의 랜덤박스 확률 정보입니다: [${randomItems + .map((item) => item._id.toString()) + .join(",")}]` + ); + + const randomItem = + randomItems[Math.floor(Math.random() * randomItems.length)]; + try { + randomItem.stock--; + await randomItem.save(); + + const transaction = new transactionModel({ + type: "use", + amount: 0, + userId: req.userOid, + itemId: randomItem._id, + comment: `랜덤박스에서 ${randomItem.name} 획득 - 0개 차감`, + }); + await transaction.save(); + + return randomItem; + } catch (err) { + logger.warn( + `유저 "${req.userOid}"의 랜덤박스 추첨이 실패했습니다. 오류 정보: ${err}` + ); + + return await getRandomItem(depth + 1); + } +}; const listHandler = async (_, res) => { try { @@ -14,6 +63,79 @@ const listHandler = async (_, res) => { } }; +const purchaseHandler = async (req, res) => { + try { + const itemId = req.params.itemId; + const item = await itemModel.findOne({ _id: itemId }); + if (!item) + return res.status(400).json({ error: "Items/Purchase : invalid Item" }); + + const user = await getUserCreditAmount(req); + if (!user) + return res + .status(400) + .json({ error: "Items/Purchase : invalid EventStatus" }); + + // 구매 가능 조건: 크레딧이 충분하며, 재고가 남아있으며, 판매 중인 아이템이어야 합니다. + if (item.isDisabled) + return res.status(400).json({ error: "Items/Purchase : disabled item" }); + if (user.creditAmount < item.price) + return res + .status(400) + .json({ error: "Items/Purchase : not enough credit" }); + if (item.stock === 0) + return res + .status(400) + .json({ error: "Items/Purchase : item out of stock" }); + + // 1단계: 재고를 차감합니다. + // 재고가 차감됐으나 유저 크레딧이 차감되지 않은 경우, 나중에 Transaction 기록 분석을 통해 오류 복구가 가능합니다. + // 하지만 유저 크레딧이 차감됐으나 재고가 차감되지 않은 경우, 다른 유저가 품절된 상품을 구입할 수 있게 되고, 이는 다수의 유저에게 불편을 야기할 수 있습니다. + item.stock--; + await item.save(); + + // 2단계: 유저의 크레딧을 차감합니다. + await user.creditUpdate(-item.price); + + // 3단계: Transaction을 추가합니다. + // Transaction은 가장 마지막에 추가해야 다른 문서와의 불일치를 감지할 수 있습니다. + const transaction = new transactionModel({ + type: "use", + amount: item.price, + userId: req.userOid, + itemId: item._id, + comment: `${item.name} 구입 - ${item.price}개 차감`, + }); + await transaction.save(); + + // 4단계: 랜덤박스인 경우 아이템을 추첨합니다. + if (item.itemType !== 3) return res.json({ result: true }); + + const randomItem = await getRandomItem(req, 0); + if (!randomItem) + return res + .status(500) + .json({ error: "Items/Purchase : random box error" }); + + res.json({ + result: true, + reward: { + _id: randomItem._id, + name: randomItem.name, + imageUrl: randomItem.imageUrl, + price: randomItem.price, + description: randomItem.description, + isDisabled: randomItem.isDisabled, + stock: randomItem.stock, + }, + }); + } catch (err) { + logger.error(err); + res.status(500).json({ error: "Items/Purchase : internal server error" }); + } +}; + module.exports = { listHandler, + purchaseHandler, }; From 0db33d782833386f2238b84842b7362a094972a1 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 14:43:51 +0900 Subject: [PATCH 062/261] Docs: add description about /events/2023fall/items/purchase/:itemId endpoint --- src/lottery/routes/docs/items.js | 68 ++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/lottery/routes/docs/items.js b/src/lottery/routes/docs/items.js index ac061578..da1827ee 100644 --- a/src/lottery/routes/docs/items.js +++ b/src/lottery/routes/docs/items.js @@ -69,5 +69,73 @@ itemsDocs[`${apiPrefix}/list`] = { }, }, }; +itemsDocs[`${apiPrefix}/purchase/:itemId`] = { + post: { + tags: [`${apiPrefix}`], + summary: "상품 구매", + description: "상품을 구매합니다.", + responses: { + 200: { + description: "", + content: { + "application/json": { + schema: { + type: "object", + required: ["result"], + properties: { + result: { + type: "boolean", + description: "성공 여부. 항상 true입니다.", + example: true, + }, + reward: { + type: "object", + description: "랜덤박스를 구입한 경우에만 포함됩니다.", + properties: { + _id: { + type: "string", + description: "Item의 ObjectId", + example: "OBJECT ID", + }, + name: { + type: "string", + description: "상품의 이름", + example: "진짜송편", + }, + imageUrl: { + type: "string", + description: "이미지 썸네일 URL", + example: "THUMBNAIL URL", + }, + price: { + type: "number", + description: "상품의 가격. 0 이상입니다.", + example: 400, + }, + description: { + type: "string", + description: "상품의 설명", + example: "맛있는 송편입니다.", + }, + isDisabled: { + type: "boolean", + description: "판매 중지 여부", + example: false, + }, + stock: { + type: "number", + description: "남은 상품 재고. 0 이상입니다.", + example: 10, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}; module.exports = itemsDocs; From a9c91672c69f4b36be6ce2b27e574b3a50f13832 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 14:50:37 +0900 Subject: [PATCH 063/261] Refactor: change name of getUserCreditAmount --- src/lottery/modules/credit.js | 4 ++-- src/lottery/services/items.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lottery/modules/credit.js b/src/lottery/modules/credit.js index 10fabd94..d40f3eac 100644 --- a/src/lottery/modules/credit.js +++ b/src/lottery/modules/credit.js @@ -1,6 +1,6 @@ const { eventStatusModel } = require("../modules/stores/mongo"); -const getUserCreditAmount = async (req) => { +const useUserCreditAmount = async (req) => { const eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); if (!eventStatus) return null; @@ -14,5 +14,5 @@ const getUserCreditAmount = async (req) => { }; module.exports = { - getUserCreditAmount, + useUserCreditAmount, }; diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 6b488658..42e525fd 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -1,6 +1,6 @@ const { itemModel, transactionModel } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); -const { getUserCreditAmount } = require("../modules/credit"); +const { useUserCreditAmount } = require("../modules/credit"); const getRandomItem = async (req, depth) => { if (depth === 10) return null; @@ -70,7 +70,7 @@ const purchaseHandler = async (req, res) => { if (!item) return res.status(400).json({ error: "Items/Purchase : invalid Item" }); - const user = await getUserCreditAmount(req); + const user = await useUserCreditAmount(req); if (!user) return res .status(400) From 45d02719af58ccb5f78c9449c204b498557b3ace Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 15:04:21 +0900 Subject: [PATCH 064/261] Refactor: update find query when getting random item --- src/lottery/services/items.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 42e525fd..12137a36 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -5,16 +5,16 @@ const { useUserCreditAmount } = require("../modules/credit"); const getRandomItem = async (req, depth) => { if (depth === 10) return null; - const items = await itemModel.find(); const randomItems = []; - for (const item of items) { - if (!item.isRandomItem) continue; - if (item.stock === 0) continue; - if (item.isDisabled) continue; - - randomItems.push(...Array(item.randomWeight).fill(item)); - } + const items = await itemModel.find({ + isRandomItem: true, + stock: { $gt: 0 }, + isDisabled: false, + }); + items.forEach((item) => + randomItems.push(...Array(item.randomWeight).fill(item)) + ); logger.info( `유저 "${req.userOid}"에 의해 getRandomItem(depth=${depth})가 호출되었습니다.` From e80ab0c356ca93e5a1ce77e5e6d840c040161a09 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 15:28:04 +0900 Subject: [PATCH 065/261] Fix: save query conflict due to async --- src/lottery/modules/credit.js | 10 ++++++++-- src/lottery/services/items.js | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/lottery/modules/credit.js b/src/lottery/modules/credit.js index d40f3eac..1f359612 100644 --- a/src/lottery/modules/credit.js +++ b/src/lottery/modules/credit.js @@ -7,8 +7,14 @@ const useUserCreditAmount = async (req) => { return { creditAmount: eventStatus.creditAmount, creditUpdate: async (delta) => { - eventStatus.creditAmount += delta; - await eventStatus.save(); + await eventStatusModel.updateOne( + { _id: eventStatus._id }, + { + $inc: { + creditAmount: delta, + }, + } + ); }, }; }; diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 12137a36..67cc94ad 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -28,6 +28,15 @@ const getRandomItem = async (req, depth) => { const randomItem = randomItems[Math.floor(Math.random() * randomItems.length)]; try { + await itemModel.updateOne( + { _id: randomItem._id }, + { + $inc: { + stock: -1, + }, + } + ); + randomItem.stock--; await randomItem.save(); @@ -91,8 +100,14 @@ const purchaseHandler = async (req, res) => { // 1단계: 재고를 차감합니다. // 재고가 차감됐으나 유저 크레딧이 차감되지 않은 경우, 나중에 Transaction 기록 분석을 통해 오류 복구가 가능합니다. // 하지만 유저 크레딧이 차감됐으나 재고가 차감되지 않은 경우, 다른 유저가 품절된 상품을 구입할 수 있게 되고, 이는 다수의 유저에게 불편을 야기할 수 있습니다. - item.stock--; - await item.save(); + await itemModel.updateOne( + { _id: item._id }, + { + $inc: { + stock: -1, + }, + } + ); // 2단계: 유저의 크레딧을 차감합니다. await user.creditUpdate(-item.price); From 391c8ec064fae8e4e814010bc3a50dd01e3b765c Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 18:05:02 +0900 Subject: [PATCH 066/261] Refactor: services/items.js Co-authored-by: Geon Kim --- src/lottery/services/items.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 67cc94ad..ebf1ee2a 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -74,7 +74,7 @@ const listHandler = async (_, res) => { const purchaseHandler = async (req, res) => { try { - const itemId = req.params.itemId; + const { itemId } = req.params; const item = await itemModel.findOne({ _id: itemId }); if (!item) return res.status(400).json({ error: "Items/Purchase : invalid Item" }); From a41f6c123982d5d118df550d654f4a2c2f3f0b63 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 15:37:47 +0900 Subject: [PATCH 067/261] Refactor: handling negative when comparing with zero --- src/lottery/services/items.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index ebf1ee2a..43d379f1 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -25,6 +25,8 @@ const getRandomItem = async (req, depth) => { .join(",")}]` ); + if (randomItems.length === 0) return null; + const randomItem = randomItems[Math.floor(Math.random() * randomItems.length)]; try { @@ -92,7 +94,7 @@ const purchaseHandler = async (req, res) => { return res .status(400) .json({ error: "Items/Purchase : not enough credit" }); - if (item.stock === 0) + if (item.stock <= 0) return res .status(400) .json({ error: "Items/Purchase : item out of stock" }); From 2f87b1c8a3ebad2847b7d812124771b421b85794 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 16:49:03 +0900 Subject: [PATCH 068/261] Remove: duplicated query in getRandomItem --- src/lottery/services/items.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 43d379f1..cf3dcc4f 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -30,18 +30,19 @@ const getRandomItem = async (req, depth) => { const randomItem = randomItems[Math.floor(Math.random() * randomItems.length)]; try { - await itemModel.updateOne( + const newRandomItem = await itemModel.findOneAndUpdate( { _id: randomItem._id }, { $inc: { stock: -1, }, + }, + { + runValidators: true, + new: true, } ); - randomItem.stock--; - await randomItem.save(); - const transaction = new transactionModel({ type: "use", amount: 0, @@ -51,7 +52,7 @@ const getRandomItem = async (req, depth) => { }); await transaction.save(); - return randomItem; + return newRandomItem; } catch (err) { logger.warn( `유저 "${req.userOid}"의 랜덤박스 추첨이 실패했습니다. 오류 정보: ${err}` @@ -108,6 +109,9 @@ const purchaseHandler = async (req, res) => { $inc: { stock: -1, }, + }, + { + runValidators: true, } ); From b984b8d6b68ad5ab19c7dc9332c289e662c2e021 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Sat, 9 Sep 2023 16:53:25 +0900 Subject: [PATCH 069/261] Add: adminJS --- app.js | 8 ++++---- src/lottery/index.js | 7 +++++++ src/lottery/routes/admin.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 src/lottery/routes/admin.js diff --git a/app.js b/app.js index fae5e09c..d8a1f0f3 100644 --- a/app.js +++ b/app.js @@ -42,6 +42,10 @@ app.use(require("./src/middlewares/limitRate")); // [Router] Swagger (API 문서) app.use("/docs", require("./src/routes/docs")); +// 2023 추석 이벤트 전용 라우터입니다. +eventMode && + app.use(`/events/${eventMode}`, require("./src/lottery").lotteryRouter); + // [Middleware] 모든 API 요청에 대하여 origin 검증 app.use(require("./src/middlewares/originValidator")); @@ -55,10 +59,6 @@ app.use("/locations", require("./src/routes/locations")); app.use("/reports", require("./src/routes/reports")); app.use("/notifications", require("./src/routes/notifications")); -// 2023 추석 이벤트 전용 라우터입니다. -eventMode && - app.use(`/events/${eventMode}`, require("./src/lottery").lotteryRouter); - // [Middleware] 전역 에러 핸들러. 에러 핸들러는 router들보다 아래에 등록되어야 합니다. app.use(require("./src/middlewares/errorHandler")); diff --git a/src/lottery/index.js b/src/lottery/index.js index 9036da92..14d85629 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -11,6 +11,13 @@ const checkReward = (req, res, next) => { const lotteryRouter = express.Router(); // [Router] APIs +lotteryRouter.use("/admin", require("./routes/admin")); + +// [Middleware] 모든 API 요청에 대하여 origin 검증 +lotteryRouter.use(require("../middlewares/originValidator")); + +// [Router] APIs +lotteryRouter.use("/admin", require("./routes/admin")); lotteryRouter.use("/transactions", require("./routes/transactions")); lotteryRouter.use("/items", require("./routes/items")); diff --git a/src/lottery/routes/admin.js b/src/lottery/routes/admin.js new file mode 100644 index 00000000..6a5fdc0a --- /dev/null +++ b/src/lottery/routes/admin.js @@ -0,0 +1,29 @@ +const express = require("express"); +const AdminJS = require("adminjs"); +const AdminJSExpress = require("@adminjs/express"); +const AdminJSMongoose = require("@adminjs/mongoose"); +const { + eventStatusModel, + eventModel, + itemModel, + transactionModel, +} = require("../modules/stores/mongo"); +const { eventMode } = require("../../../loadenv"); + +const router = express.Router(); + +// Requires admin property of the user to enter admin page. +router.use(require("../../middlewares/authAdmin")); +router.use(require("../../middlewares/auth")); + +// Registration of the mongoose adapter +AdminJS.registerAdapter(AdminJSMongoose); + +// Create router for admin page +const adminJS = new AdminJS({ + rootPath: `/events/${eventMode}/admin`, + resources: [eventStatusModel, eventModel, itemModel, transactionModel], +}); +router.use(AdminJSExpress.buildRouter(adminJS)); + +module.exports = router; From 0dfaa5bccda503031bc54ad0f42aa185a8484e55 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 17:05:33 +0900 Subject: [PATCH 070/261] Refactor: use map instead of forEach to generate randomItems --- src/lottery/services/items.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index cf3dcc4f..1568baa8 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -3,18 +3,18 @@ const logger = require("../../modules/logger"); const { useUserCreditAmount } = require("../modules/credit"); const getRandomItem = async (req, depth) => { - if (depth === 10) return null; - - const randomItems = []; + if (depth >= 10) return null; const items = await itemModel.find({ isRandomItem: true, stock: { $gt: 0 }, isDisabled: false, }); - items.forEach((item) => - randomItems.push(...Array(item.randomWeight).fill(item)) - ); + const randomItems = items + .map((item) => { + return Array(item.randomWeight).fill(item); + }) + .reduce((a, b) => a.concat(b), []); logger.info( `유저 "${req.userOid}"에 의해 getRandomItem(depth=${depth})가 호출되었습니다.` From 8e98a013a8fb9b58ac49213c4a4700cd2147607e Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 9 Sep 2023 17:15:55 +0900 Subject: [PATCH 071/261] Add: add itemType to items/list --- loadenv.js | 2 +- src/lottery/routes/docs/items.js | 6 ++++++ src/lottery/services/items.js | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/loadenv.js b/loadenv.js index 2bce4fb9..715cbfeb 100644 --- a/loadenv.js +++ b/loadenv.js @@ -38,5 +38,5 @@ module.exports = { slackWebhookUrl: { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, - eventMode: undefined, + eventMode: "2023fall", }; diff --git a/src/lottery/routes/docs/items.js b/src/lottery/routes/docs/items.js index ac061578..db1db63c 100644 --- a/src/lottery/routes/docs/items.js +++ b/src/lottery/routes/docs/items.js @@ -58,6 +58,12 @@ itemsDocs[`${apiPrefix}/list`] = { description: "남은 상품 재고. 0 이상입니다.", example: 10, }, + itemType: { + type: "number", + description: + "아이템 유형. 0: 티켓아님, 1:티켓 타입1, 2: 티켓 타입 2, 3: 랜덤박스", + example: 0, + }, }, }, }, diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index ca832f60..59bf162a 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -5,7 +5,7 @@ const listHandler = async (_, res) => { try { const items = await itemModel.find( {}, - "name imageUrl price description isDisabled stock" + "name imageUrl price description isDisabled stock itemType" ); res.json({ items }); } catch (err) { From 62ec9e8b18bde8525a7fbfa1bd1e62e9e1b2d46f Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 9 Sep 2023 17:17:48 +0900 Subject: [PATCH 072/261] Fix: remove eventMode --- loadenv.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loadenv.js b/loadenv.js index 715cbfeb..2bce4fb9 100644 --- a/loadenv.js +++ b/loadenv.js @@ -38,5 +38,5 @@ module.exports = { slackWebhookUrl: { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, - eventMode: "2023fall", + eventMode: undefined, }; From d58f388cd7c40e42356ba6f07af66cffbda359c0 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 9 Sep 2023 18:31:14 +0900 Subject: [PATCH 073/261] Refactor: merge event admin page into main admin page --- src/lottery/index.js | 10 +++++++--- src/lottery/routes/admin.js | 29 ----------------------------- src/routes/admin.js | 34 +++++++++++++++++++++++----------- 3 files changed, 30 insertions(+), 43 deletions(-) delete mode 100644 src/lottery/routes/admin.js diff --git a/src/lottery/index.js b/src/lottery/index.js index c81d48b8..76e8008e 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -1,4 +1,10 @@ const express = require("express"); +const { + eventStatusModel, + eventModel, + itemModel, + transactionModel, +} = require("./modules/stores/mongo"); // [Routes] 기존 docs 라우터의 docs extend require("./routes/docs")(); @@ -10,9 +16,6 @@ const checkReward = (req, res, next) => { const lotteryRouter = express.Router(); -// [Router] APIs -lotteryRouter.use("/admin", require("./routes/admin")); - // [Middleware] 모든 API 요청에 대하여 origin 검증 lotteryRouter.use(require("../middlewares/originValidator")); @@ -24,4 +27,5 @@ lotteryRouter.use("/items", require("./routes/items")); module.exports = { checkReward, lotteryRouter, + models: [eventStatusModel, eventModel, itemModel, transactionModel], }; diff --git a/src/lottery/routes/admin.js b/src/lottery/routes/admin.js deleted file mode 100644 index 6a5fdc0a..00000000 --- a/src/lottery/routes/admin.js +++ /dev/null @@ -1,29 +0,0 @@ -const express = require("express"); -const AdminJS = require("adminjs"); -const AdminJSExpress = require("@adminjs/express"); -const AdminJSMongoose = require("@adminjs/mongoose"); -const { - eventStatusModel, - eventModel, - itemModel, - transactionModel, -} = require("../modules/stores/mongo"); -const { eventMode } = require("../../../loadenv"); - -const router = express.Router(); - -// Requires admin property of the user to enter admin page. -router.use(require("../../middlewares/authAdmin")); -router.use(require("../../middlewares/auth")); - -// Registration of the mongoose adapter -AdminJS.registerAdapter(AdminJSMongoose); - -// Create router for admin page -const adminJS = new AdminJS({ - rootPath: `/events/${eventMode}/admin`, - resources: [eventStatusModel, eventModel, itemModel, transactionModel], -}); -router.use(AdminJSExpress.buildRouter(adminJS)); - -module.exports = router; diff --git a/src/routes/admin.js b/src/routes/admin.js index 363e1282..a1a302fa 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -14,6 +14,7 @@ const { deviceTokenModel, notificationOptionModel, } = require("../modules/stores/mongo"); +const { eventMode } = require("../../loadenv"); const router = express.Router(); @@ -76,19 +77,30 @@ const resourceWrapper = (resource) => ({ ], }); +const baseResources = [ + userModel, + roomModel, + locationModel, + chatModel, + reportModel, + adminIPWhitelistModel, + adminLogModel, + deviceTokenModel, + notificationOptionModel, +]; +const resources = baseResources.concat( + (() => { + if (eventMode === "2023fall") { + return require("../lottery").models; + } else { + return []; + } + })() +); + // Create router for admin page const adminJS = new AdminJS({ - resources: [ - userModel, - roomModel, - locationModel, - chatModel, - reportModel, - adminIPWhitelistModel, - adminLogModel, - deviceTokenModel, - notificationOptionModel, - ].map(resourceWrapper), + resources: resources.map(resourceWrapper), }); router.use(AdminJSExpress.buildRouter(adminJS)); From fcb634948241288dfcdd6584758031d64801fa95 Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 9 Sep 2023 20:06:28 +0900 Subject: [PATCH 074/261] Fix: fix logger to print errors --- src/modules/logger.js | 147 ++++++++++++++++++++++++------------------ 1 file changed, 85 insertions(+), 62 deletions(-) diff --git a/src/modules/logger.js b/src/modules/logger.js index 0e02e56e..e532e7aa 100644 --- a/src/modules/logger.js +++ b/src/modules/logger.js @@ -1,72 +1,95 @@ -const { createLogger, format, transports } = require("winston"); -const dailyRotateFileTransport = require("winston-daily-rotate-file"); const path = require("path"); +const { createLogger, format, transports } = require("winston"); +const DailyRotateFileTransport = require("winston-daily-rotate-file"); const { nodeEnv } = require("../../loadenv"); -// 로깅에 사용하기 위한 포맷을 추가로 정의합니다. -const customFormat = { - time: "YYYY-MM-DD HH:mm:ss", // 로깅 시각 - line: format.printf(({ level, message, timestamp, stack }) => { - return `${timestamp} [${level}]: ${message} ${ - level === "error" ? stack : "" - }`; - }), // 메시지 포맷 - fileDate: "YYYY-MM-DD-HH", // 파일명에 포함되는 시각 -}; +// logger에서 사용할 포맷들을 정의합니다. +const baseFormat = format.combine( + format.timestamp({ format: "YYYY-MM-DD HH:mm:ss(UTCZ)" }), + format.errors({ stack: true }), + format.splat(), + format.json() +); +const finalFormat = format.printf( + ({ level, message, timestamp, stack }) => + `${timestamp} [${level}]: ${message} ${ + level === "error" && stack !== undefined ? stack : "" + }` +); + +// 파일 출력 시 사용될 포맷. 색 관련 특수문자가 파일에 쓰여지는 것을 방지하기 위해 색상이 표시되지 않습니다. +const uncolorizedFormat = format.combine( + baseFormat, + format.uncolorize(), + finalFormat +); + +// 콘솔 출력 시 사용될 포맷. 색상이 표시됩니다. +const colorizedFormat = format.combine( + baseFormat, + format.colorize({ all: true }), + finalFormat +); + +// 로그 파일명에 포함되는 시각 +const datePattern = "YYYY-MM-DD-HH"; +// 로그 파일당 최대 크기(=5MB). +const maxSize = 5 * 1024 * 1024; + +// 콘솔에 출력하기 위한 winston transport +const consoleTransport = new transports.Console(); /** - * console.log 대신 사용되는 winston Logger 객체입니다. + * console.log()와 console.error() 대신 사용되는 winston Logger 객체입니다. * - * 전체 로그는 *.combined.log 파일에, 예외 처리로 핸들링 된 오류 로그는 *.error.log 파일에, 예외 처리가 되지 않은 오류는 *.unhandled.log에 저장됩니다. - * @property {function} info() - 일반적인 정보 기록을 위한 로깅(API 접근 기록 등)을 위해 사용합니다. - * @property {function} error() - 오류 메시지를 로깅하기 위해 사용합니다. + * - "production" 환경: 모든 로그는 파일 시스템에 저장되고, 콘솔로도 출력됩니다. + * - "development" & "test" 환경: 모든 로그는 콘솔에 출력됩니다. + * + * @method info(message: string, callback: winston.LogCallback) - 일반적인 정보(API 접근 등) 기록을 위해 사용합니다. + * @method error(message: string, callback: winston.LogCallback) - 오류 메시지를 기록하기 위해 사용합니다. */ -const logger = createLogger({ - format: format.combine( - format.timestamp({ format: customFormat.time }), - format.errors({ stack: true }), - format.splat(), - format.json(), - customFormat.line - ), - defaultMeta: { service: "sparcs-taxi" }, - transports: [ - new dailyRotateFileTransport({ - filename: path.resolve("logs/%DATE%-combined.log"), - datePattern: customFormat.fileDate, - maxsize: 5242880, // 5MB - level: "info", - }), - new dailyRotateFileTransport({ - filename: path.resolve("logs/%DATE%-error.log"), - datePattern: customFormat.fileDate, - maxsize: 5242880, // 5MB - level: "error", - }), - ], - exceptionHandlers: [ - new dailyRotateFileTransport({ - filename: path.resolve("logs/%DATE%-unhandled.log"), - datePattern: customFormat.fileDate, - maxsize: 5242880, // 5MB - }), - ], -}); - -// If the environment is not production, the log is also recorded on console -if (nodeEnv !== "production") { - logger.add( - new transports.Console({ - format: format.combine( - format.timestamp({ format: customFormat.time }), - format.errors({ stack: true }), - format.splat(), - format.colorize(), - customFormat.line - ), - }) - ); -} +const logger = + nodeEnv === "production" + ? // "production" 환경에서 사용되는 Logger 객체 + createLogger({ + level: "info", + format: uncolorizedFormat, + defaultMeta: { service: "sparcs-taxi" }, + transports: [ + // 전체 로그("info", "warn", "error")를 파일로 출력합니다. + new DailyRotateFileTransport({ + level: "info", + filename: path.resolve("logs/%DATE%-combined.log"), + datePattern, + maxSize, + }), + // 예외 처리로 핸들링 된 오류 로그("error")를 파일과 콘솔에 출력합니다. + new DailyRotateFileTransport({ + level: "error", + filename: path.resolve("logs/%DATE%-error.log"), + datePattern, + maxSize, + }), + consoleTransport, + ], + exceptionHandlers: [ + // 예외 처리가 되지 않은 오류 로그("error")를 파일과 콘솔에 출력합니다. + new DailyRotateFileTransport({ + filename: path.resolve("logs/%DATE%-unhandled.log"), + datePattern, + maxSize, + }), + consoleTransport, + ], + }) + : // "development", "test" 환경에서 사용되는 Logger 객체 + createLogger({ + level: "info", + format: colorizedFormat, + defaultMeta: { service: "sparcs-kaist" }, + transports: [consoleTransport], + exceptionHandlers: [consoleTransport], + }); module.exports = logger; From 1ad36cfd88b1586078dd149f9581c571362d479f Mon Sep 17 00:00:00 2001 From: static Date: Sun, 10 Sep 2023 00:11:40 +0900 Subject: [PATCH 075/261] Add: routes/admin.js --- src/lottery/modules/admin.js | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/lottery/modules/admin.js diff --git a/src/lottery/modules/admin.js b/src/lottery/modules/admin.js new file mode 100644 index 00000000..f7d3219a --- /dev/null +++ b/src/lottery/modules/admin.js @@ -0,0 +1,39 @@ +const { useUserCreditAmount } = require("./credit"); +const { transactionModel } = require("./stores/mongo"); + +// eventId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. +const creditTransfer = async (req, amount, eventId, comment) => { + const user = await useUserCreditAmount(req); + + await user.creditUpdate(amount); + + const transaction = new transactionModel({ + type: "get", + amount, + userId: req.userOid, + eventId, + comment, + }); + await transaction.save(); +}; + +// itemId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. +const creditWithdraw = async (req, amount, itemId, comment) => { + const user = await useUserCreditAmount(req); + + await user.creditUpdate(-amount); + + const transaction = new transactionModel({ + type: "use", + amount, + userId: req.userOid, + itemId, + comment, + }); + await transaction.save(); +}; + +module.exports = { + creditTransfer, + creditWithdraw, +}; From 904f5435d5eb09860cfc625a57a23359c3409618 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 10 Sep 2023 02:27:49 +0900 Subject: [PATCH 076/261] Add: instagramRewardAction in modules/admin.js --- src/lottery/index.js | 12 +++++- src/lottery/modules/admin.js | 35 +++++++++++----- src/lottery/modules/credit.js | 4 +- src/lottery/services/items.js | 2 +- src/modules/adminResource.js | 79 +++++++++++++++++++++++++++++++++++ src/routes/admin.js | 62 ++------------------------- 6 files changed, 122 insertions(+), 72 deletions(-) create mode 100644 src/modules/adminResource.js diff --git a/src/lottery/index.js b/src/lottery/index.js index 76e8008e..d06be299 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -6,6 +6,9 @@ const { transactionModel, } = require("./modules/stores/mongo"); +const { buildResource } = require("../modules/adminResource"); +const { instagramRewardAction } = require("./modules/admin"); + // [Routes] 기존 docs 라우터의 docs extend require("./routes/docs")(); @@ -24,8 +27,15 @@ lotteryRouter.use("/global-state", require("./routes/globalState")); lotteryRouter.use("/transactions", require("./routes/transactions")); lotteryRouter.use("/items", require("./routes/items")); +const eventStatusResource = buildResource([instagramRewardAction])( + eventStatusModel +); +const otherResources = [eventModel, itemModel, transactionModel].map( + buildResource() +); + module.exports = { checkReward, lotteryRouter, - models: [eventStatusModel, eventModel, itemModel, transactionModel], + resources: [eventStatusResource, ...otherResources], }; diff --git a/src/lottery/modules/admin.js b/src/lottery/modules/admin.js index f7d3219a..fa598f2e 100644 --- a/src/lottery/modules/admin.js +++ b/src/lottery/modules/admin.js @@ -1,16 +1,16 @@ const { useUserCreditAmount } = require("./credit"); const { transactionModel } = require("./stores/mongo"); +const { recordAction } = require("../../modules/adminResource"); // eventId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. -const creditTransfer = async (req, amount, eventId, comment) => { - const user = await useUserCreditAmount(req); - +const creditTransfer = async (userId, amount, eventId, comment) => { + const user = await useUserCreditAmount(userId); await user.creditUpdate(amount); const transaction = new transactionModel({ type: "get", amount, - userId: req.userOid, + userId, eventId, comment, }); @@ -18,22 +18,37 @@ const creditTransfer = async (req, amount, eventId, comment) => { }; // itemId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. -const creditWithdraw = async (req, amount, itemId, comment) => { - const user = await useUserCreditAmount(req); - +const creditWithdraw = async (userId, amount, itemId, comment) => { + const user = await useUserCreditAmount(userId); await user.creditUpdate(-amount); const transaction = new transactionModel({ type: "use", amount, - userId: req.userOid, + userId, itemId, comment, }); await transaction.save(); }; +const instagramRewardAction = recordAction( + "instagramReward", + async (req, res, context) => { + await creditTransfer( + context?.record?.params?.userId, + 500 /*TODO: 송편개수*/, + "64fc99136b599860bff4780f" /*TODO: 이벤트ID*/, + "뿌슝빠슝" /*TODO: 코멘트*/ + ); + + let record = context.record.toJSON(context.currentAdmin); + record.params.creditAmount += 500; // 송편개수 + + return { record }; + } +); + module.exports = { - creditTransfer, - creditWithdraw, + instagramRewardAction, }; diff --git a/src/lottery/modules/credit.js b/src/lottery/modules/credit.js index 1f359612..b55613a0 100644 --- a/src/lottery/modules/credit.js +++ b/src/lottery/modules/credit.js @@ -1,7 +1,7 @@ const { eventStatusModel } = require("../modules/stores/mongo"); -const useUserCreditAmount = async (req) => { - const eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); +const useUserCreditAmount = async (userId) => { + const eventStatus = await eventStatusModel.findOne({ userId }); if (!eventStatus) return null; return { diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 1568baa8..94029823 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -82,7 +82,7 @@ const purchaseHandler = async (req, res) => { if (!item) return res.status(400).json({ error: "Items/Purchase : invalid Item" }); - const user = await useUserCreditAmount(req); + const user = await useUserCreditAmount(req.userOid); if (!user) return res .status(400) diff --git a/src/modules/adminResource.js b/src/modules/adminResource.js new file mode 100644 index 00000000..355c77c1 --- /dev/null +++ b/src/modules/adminResource.js @@ -0,0 +1,79 @@ +const { buildFeature } = require("adminjs"); +const { userModel, adminLogModel } = require("./stores/mongo"); + +// AdminJS에서 Log 저장을 하는 action +const logAction = (actionName) => async (res, req, context) => { + const user = await userModel.findOne({ id: req.userId }); + const modelName = context?.resource?.MongooseModel?.modelName; + const recordLength = `(length = ${context?.records?.length})`; + const recordId = `(_id = ${context?.record?.params?._id})` || recordLength; + const [action, target] = { + list: ["read", `List<${modelName}>${recordLength}`], + show: ["read", `${modelName}${recordId}`], + new: ["create", `${modelName}${recordId}`], + edit: ["update", `${modelName}${recordId}`], + delete: ["delete", `${modelName}${recordId}`], + bulkDelete: ["delete", `${modelName}${recordId}`], + }?.[actionName]; + + if ( + ["new", "edit", "bulkDelete"].includes(actionName) && + req.method !== "post" + ) + return res; + + if (user?._id && action && target) { + const newLog = new adminLogModel({ + user: user._id, // Log 취급자 User + time: req.timestamp, // Log 발생 시각 + ip: req.clientIP, // 접속 IP 주소 + target, // 처리한 정보주체 정보 + action, // 수행 업무 + }); + await newLog.save(); + } + return res; +}; + +// AdminJS에서 Log 기록을 하도록 action을 수정합니다 +const logFeature = buildFeature({ + actions: ["list", "show", "new", "edit", "delete", "bulkDelete"].reduce( + (before, actionName) => ({ + ...before, + [actionName]: { + after: logAction(actionName), + }, + }), + {} + ), +}); + +const recordAction = (actionName, handler) => ({ + actionName, + actionType: "record", + component: false, + handler, +}); + +const buildResource = + (actions = [], features = []) => + (resource) => ({ + resource, + options: { + actions: actions.reduce( + (before, action) => ({ + ...before, + [action.actionName]: { + ...action, // actionName이 포함되는 문제가 있지만 있어도 상관은 없을 것 같습니다. + }, + }), + {} + ), + }, + features: features.concat([logFeature]), + }); + +module.exports = { + recordAction, + buildResource, +}; diff --git a/src/routes/admin.js b/src/routes/admin.js index a1a302fa..68689d59 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -1,6 +1,5 @@ const express = require("express"); const AdminJS = require("adminjs"); -const { buildFeature } = require("adminjs"); const AdminJSExpress = require("@adminjs/express"); const AdminJSMongoose = require("@adminjs/mongoose"); const { @@ -15,6 +14,7 @@ const { notificationOptionModel, } = require("../modules/stores/mongo"); const { eventMode } = require("../../loadenv"); +const { buildResource } = require("../modules/adminResource"); const router = express.Router(); @@ -25,58 +25,6 @@ router.use(require("../middlewares/auth")); // Registration of the mongoose adapter AdminJS.registerAdapter(AdminJSMongoose); -// AdminJS에서 Log 저장을 하는 action -const logAction = (actionName) => async (res, req, context) => { - const user = await userModel.findOne({ id: req.userId }); - const modelName = context?.resource?.MongooseModel?.modelName; - const recordLength = `(length = ${context?.records?.length})`; - const recordId = `(_id = ${context?.record?.params?._id})` || recordLength; - const [action, target] = { - list: ["read", `List<${modelName}>${recordLength}`], - show: ["read", `${modelName}${recordId}`], - new: ["create", `${modelName}${recordId}`], - edit: ["update", `${modelName}${recordId}`], - delete: ["delete", `${modelName}${recordId}`], - bulkDelete: ["delete", `${modelName}${recordId}`], - }?.[actionName]; - - if ( - ["new", "edit", "bulkDelete"].includes(actionName) && - req.method !== "post" - ) - return res; - - if (user?._id && action && target) { - const newLog = new adminLogModel({ - user: user._id, // Log 취급자 User - time: req.timestamp, // Log 발생 시각 - ip: req.clientIP, // 접속 IP 주소 - target, // 처리한 정보주체 정보 - action, // 수행 업무 - }); - await newLog.save(); - } - return res; -}; - -// AdminJS에서 Log 기록을 하도록 action을 수정합니다 -const resourceWrapper = (resource) => ({ - resource, - features: [ - buildFeature({ - actions: ["list", "show", "new", "edit", "delete", "bulkDelete"].reduce( - (before, actionName) => ({ - ...before, - [actionName]: { - after: logAction(actionName), - }, - }), - {} - ), - }), - ], -}); - const baseResources = [ userModel, roomModel, @@ -87,11 +35,11 @@ const baseResources = [ adminLogModel, deviceTokenModel, notificationOptionModel, -]; +].map(buildResource()); const resources = baseResources.concat( (() => { if (eventMode === "2023fall") { - return require("../lottery").models; + return require("../lottery").resources; } else { return []; } @@ -99,9 +47,7 @@ const resources = baseResources.concat( ); // Create router for admin page -const adminJS = new AdminJS({ - resources: resources.map(resourceWrapper), -}); +const adminJS = new AdminJS({ resources }); router.use(AdminJSExpress.buildRouter(adminJS)); module.exports = router; From 60e53a5665de918dba67dfbe24e1be0bfe864930 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 10 Sep 2023 05:48:52 +0900 Subject: [PATCH 077/261] Add: logging custom adminJS actions --- src/lottery/modules/admin.js | 43 +++++++++++++------ src/modules/adminResource.js | 82 +++++++++++++++++++++++------------- 2 files changed, 83 insertions(+), 42 deletions(-) diff --git a/src/lottery/modules/admin.js b/src/lottery/modules/admin.js index fa598f2e..05635946 100644 --- a/src/lottery/modules/admin.js +++ b/src/lottery/modules/admin.js @@ -15,6 +15,8 @@ const creditTransfer = async (userId, amount, eventId, comment) => { comment, }); await transaction.save(); + + return transaction._id; }; // itemId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. @@ -30,23 +32,38 @@ const creditWithdraw = async (userId, amount, itemId, comment) => { comment, }); await transaction.save(); + + return transaction._id; +}; + +const instagramRewardActionHandler = async (req, res, context) => { + const transactionId = await creditTransfer( + context?.record?.params?.userId, + 500 /*TODO: 송편개수*/, + "64fc99136b599860bff4780f" /*TODO: 이벤트ID*/, + "뿌슝빠슝" /*TODO: 코멘트*/ + ); + + let record = context.record.toJSON(context.currentAdmin); + record.params.creditAmount += 500; // 송편개수 + + return { + record, + transactionId, + }; }; +const instagramRewardActionLogs = [ + "update", + { + action: "create", + target: (res, req, context) => `Transaction(_id = ${res.transactionId})`, + }, +]; const instagramRewardAction = recordAction( "instagramReward", - async (req, res, context) => { - await creditTransfer( - context?.record?.params?.userId, - 500 /*TODO: 송편개수*/, - "64fc99136b599860bff4780f" /*TODO: 이벤트ID*/, - "뿌슝빠슝" /*TODO: 코멘트*/ - ); - - let record = context.record.toJSON(context.currentAdmin); - record.params.creditAmount += 500; // 송편개수 - - return { record }; - } + instagramRewardActionHandler, + instagramRewardActionLogs ); module.exports = { diff --git a/src/modules/adminResource.js b/src/modules/adminResource.js index 355c77c1..e5c565e8 100644 --- a/src/modules/adminResource.js +++ b/src/modules/adminResource.js @@ -1,58 +1,81 @@ const { buildFeature } = require("adminjs"); -const { userModel, adminLogModel } = require("./stores/mongo"); +const { adminLogModel } = require("./stores/mongo"); -// AdminJS에서 Log 저장을 하는 action -const logAction = (actionName) => async (res, req, context) => { - const user = await userModel.findOne({ id: req.userId }); +const createLog = async (req, action, target) => { + const newLog = new adminLogModel({ + user: req.userOid, // Log 취급자 User + time: req.timestamp, // Log 발생 시각 + ip: req.clientIP, // 접속 IP 주소 + target, // 처리한 정보주체 정보 + action, // 수행 업무 + }); + await newLog.save(); +}; + +const generateTarget = (context, isList) => { const modelName = context?.resource?.MongooseModel?.modelName; const recordLength = `(length = ${context?.records?.length})`; - const recordId = `(_id = ${context?.record?.params?._id})` || recordLength; - const [action, target] = { - list: ["read", `List<${modelName}>${recordLength}`], - show: ["read", `${modelName}${recordId}`], - new: ["create", `${modelName}${recordId}`], - edit: ["update", `${modelName}${recordId}`], - delete: ["delete", `${modelName}${recordId}`], - bulkDelete: ["delete", `${modelName}${recordId}`], - }?.[actionName]; + const recordId = `(_id = ${context?.record?.params?._id})`; + return isList + ? `List<${modelName}>${recordLength}` + : `${modelName}${recordId}`; +}; + +const defaultActionAfterHandler = (actionName) => async (res, req, context) => { if ( ["new", "edit", "bulkDelete"].includes(actionName) && req.method !== "post" ) - return res; + return res; // 왜 필요한건지는 잘 모르겠으나, 기존에 존재하던 코드라 지우지 않고 유지합니다. + + const [action, isList] = { + list: ["read", true], + show: ["read", false], + new: ["create", false], + edit: ["update", false], + delete: ["delete", false], + bulkDelete: ["delete", true], + }?.[actionName]; + + const target = generateTarget(context, isList); + await createLog(req, action, target); - if (user?._id && action && target) { - const newLog = new adminLogModel({ - user: user._id, // Log 취급자 User - time: req.timestamp, // Log 발생 시각 - ip: req.clientIP, // 접속 IP 주소 - target, // 처리한 정보주체 정보 - action, // 수행 업무 - }); - await newLog.save(); - } return res; }; -// AdminJS에서 Log 기록을 하도록 action을 수정합니다 -const logFeature = buildFeature({ +const defaultActionLogFeature = buildFeature({ actions: ["list", "show", "new", "edit", "delete", "bulkDelete"].reduce( (before, actionName) => ({ ...before, [actionName]: { - after: logAction(actionName), + after: defaultActionAfterHandler(actionName), }, }), {} ), }); -const recordAction = (actionName, handler) => ({ +const recordActionAfterHandler = (actions) => async (res, req, context) => { + const actionsWrapper = Array.isArray(actions) ? actions : [actions]; + for (const action of actionsWrapper) { + if (typeof action === "string") { + const target = generateTarget(context, isList); + await createLog(req, action, target); + } else { + await createLog(req, action.action, action.target(res, req, context)); + } + } + + return res; +}; + +const recordAction = (actionName, handler, logActions) => ({ actionName, actionType: "record", component: false, handler, + after: recordActionAfterHandler(logActions), }); const buildResource = @@ -70,10 +93,11 @@ const buildResource = {} ), }, - features: features.concat([logFeature]), + features: features.concat([defaultActionLogFeature]), }); module.exports = { + generateTarget, recordAction, buildResource, }; From f56673161c1a9cc55f1c94306a5e9b79bd4f5f01 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 10 Sep 2023 05:53:16 +0900 Subject: [PATCH 078/261] Fix: invalid argument for generateTarget function --- src/modules/adminResource.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/adminResource.js b/src/modules/adminResource.js index e5c565e8..655e928b 100644 --- a/src/modules/adminResource.js +++ b/src/modules/adminResource.js @@ -60,7 +60,7 @@ const recordActionAfterHandler = (actions) => async (res, req, context) => { const actionsWrapper = Array.isArray(actions) ? actions : [actions]; for (const action of actionsWrapper) { if (typeof action === "string") { - const target = generateTarget(context, isList); + const target = generateTarget(context, false); await createLog(req, action, target); } else { await createLog(req, action.action, action.target(res, req, context)); From 18c35eb088d0b7fbb88bc473a53603b674cc3cb7 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 10 Sep 2023 06:01:06 +0900 Subject: [PATCH 079/261] Remove: test data for instagramRewardAction --- src/lottery/modules/admin.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lottery/modules/admin.js b/src/lottery/modules/admin.js index 05635946..a28ac6d2 100644 --- a/src/lottery/modules/admin.js +++ b/src/lottery/modules/admin.js @@ -1,6 +1,7 @@ const { useUserCreditAmount } = require("./credit"); const { transactionModel } = require("./stores/mongo"); const { recordAction } = require("../../modules/adminResource"); +const { eventEnv } = require("../../../loadenv"); // eventId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. const creditTransfer = async (userId, amount, eventId, comment) => { @@ -39,13 +40,13 @@ const creditWithdraw = async (userId, amount, itemId, comment) => { const instagramRewardActionHandler = async (req, res, context) => { const transactionId = await creditTransfer( context?.record?.params?.userId, - 500 /*TODO: 송편개수*/, - "64fc99136b599860bff4780f" /*TODO: 이벤트ID*/, - "뿌슝빠슝" /*TODO: 코멘트*/ + eventEnv.instagramReward, + eventEnv.instagramEventId, + eventEnv.instagramComment ); let record = context.record.toJSON(context.currentAdmin); - record.params.creditAmount += 500; // 송편개수 + record.params.creditAmount += eventEnv.instagramReward; return { record, From 9d129c25328c20c9efd53954af7bd385fb43d352 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 11 Sep 2023 03:00:35 +0900 Subject: [PATCH 080/261] Update: src/lottery/modules/admin.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jeong Sang (정상) --- src/lottery/modules/admin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lottery/modules/admin.js b/src/lottery/modules/admin.js index a28ac6d2..6e62d587 100644 --- a/src/lottery/modules/admin.js +++ b/src/lottery/modules/admin.js @@ -3,7 +3,7 @@ const { transactionModel } = require("./stores/mongo"); const { recordAction } = require("../../modules/adminResource"); const { eventEnv } = require("../../../loadenv"); -// eventId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. +/** eventId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. */ const creditTransfer = async (userId, amount, eventId, comment) => { const user = await useUserCreditAmount(userId); await user.creditUpdate(amount); From 294ae3c2ed8ed2027ae56bca2435e8102c5fb183 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 11 Sep 2023 03:00:47 +0900 Subject: [PATCH 081/261] Update: src/lottery/modules/admin.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jeong Sang (정상) --- src/lottery/modules/admin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lottery/modules/admin.js b/src/lottery/modules/admin.js index 6e62d587..a112323a 100644 --- a/src/lottery/modules/admin.js +++ b/src/lottery/modules/admin.js @@ -20,7 +20,7 @@ const creditTransfer = async (userId, amount, eventId, comment) => { return transaction._id; }; -// itemId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. +/** itemId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. */ const creditWithdraw = async (userId, amount, itemId, comment) => { const user = await useUserCreditAmount(userId); await user.creditUpdate(-amount); From da080c9280c8ce3bab3ed763bfbc69f6b94aef85 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 10 Sep 2023 06:28:13 +0900 Subject: [PATCH 082/261] Refactor: replace IIFE into ternary operator --- src/routes/admin.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/routes/admin.js b/src/routes/admin.js index 68689d59..d476142e 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -37,13 +37,7 @@ const baseResources = [ notificationOptionModel, ].map(buildResource()); const resources = baseResources.concat( - (() => { - if (eventMode === "2023fall") { - return require("../lottery").resources; - } else { - return []; - } - })() + eventMode === "2023fall" ? require("../lottery").resources : [] ); // Create router for admin page From 80e612e984b9fd26ab0ad1fe0c285542b0a315bb Mon Sep 17 00:00:00 2001 From: static Date: Sun, 10 Sep 2023 17:54:22 +0900 Subject: [PATCH 083/261] Docs: events/2023fall/transactions endpoint --- src/lottery/routes/docs/swaggerDocs.js | 6 ++ src/lottery/routes/docs/transactions.js | 117 ++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 src/lottery/routes/docs/transactions.js diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index 4896f122..654bcc43 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -1,6 +1,7 @@ const { eventMode } = require("../../../../loadenv"); const globalStateDocs = require("./globalState"); const itemsDocs = require("./items"); +const transactionsDocs = require("./transactions"); const apiPrefix = `/events/${eventMode}`; @@ -14,10 +15,15 @@ const eventSwaggerDocs = { name: `${apiPrefix}/items`, description: "이벤트 - 아이템 관련 API", }, + { + name: `${apiPrefix}/transactions`, + description: "이벤트 - 입출금 내역 관련 API", + }, ], paths: { ...globalStateDocs, ...itemsDocs, + ...transactionsDocs, }, components: { schemas: {}, diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js new file mode 100644 index 00000000..9c127bab --- /dev/null +++ b/src/lottery/routes/docs/transactions.js @@ -0,0 +1,117 @@ +const { eventMode } = require("../../../../loadenv"); +const apiPrefix = `/events/${eventMode}/transactions`; + +const transactionsDocs = {}; +transactionsDocs[`${apiPrefix}/`] = { + get: { + tags: [`${apiPrefix}`], + summary: "재화 입출금 내역 반환", + description: "유저의 재화 입출금 내역을 가져옵니다.", + responses: { + 200: { + description: "", + content: { + "application/json": { + schema: { + type: "object", + properties: { + transactions: { + type: "array", + description: "유저의 재화 입출금 기록의 배열", + items: { + type: "object", + properties: { + _id: { + type: "string", + description: "Transaction의 ObjectId", + example: "OBJECT ID", + }, + type: { + type: "string", + description: + "재화의 입금 또는 출금 여부. get 또는 use 중 하나입니다.", + example: "use", + }, + amount: { + type: "number", + description: "재화의 변화량의 절댓값", + example: 50, + }, + eventId: { + type: "string", + description: + "Transaction과 관련된 이벤트의 ObjectId. 이벤트와 관련된 Transaction인 경우에만 포함됩니다.", + example: "EVENT OBJECT ID", + }, + itemId: { + type: "object", + description: + "Transaction과 관련된 아이템의 ObjectId. 아이템과 관련된 Transaction인 경우에만 포함됩니다.", + properties: { + _id: { + type: "string", + description: "Item의 ObjectId", + example: "OBJECT ID", + }, + name: { + type: "string", + description: "상품의 이름", + example: "랜덤 상자", + }, + imageUrl: { + type: "string", + description: "이미지 썸네일 URL", + example: "THUMBNAIL URL", + }, + price: { + type: "number", + description: "상품의 가격. 0 이상입니다.", + example: 400, + }, + description: { + type: "string", + description: "상품의 설명", + example: + "랜덤으로 상품이 나오는 상자입니다. 확률은 다음과 같습니다: 진짜송편 100%, 치킨 0%, ...", + }, + isDisabled: { + type: "boolean", + description: "판매 중지 여부", + example: false, + }, + stock: { + type: "number", + description: "남은 상품 재고. 0 이상입니다.", + example: 10, + }, + itemType: { + type: "number", + description: + "아이템 유형. 0: 티켓아님, 1:티켓 타입1, 2: 티켓 타입 2, 3: 랜덤박스", + example: 0, + }, + }, + }, + comment: { + type: "string", + description: "입출금 내역에 대한 설명", + example: "랜덤 상자 구입 - 50개 차감", + }, + doneat: { + type: "string", + description: "입출금이 일어난 시각", + example: "2023-01-01 00:00:00", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}; + +module.exports = transactionsDocs; From eaf904ef5d44ea5175e3b34cfa768d5430dc1a54 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 10 Sep 2023 18:12:08 +0900 Subject: [PATCH 084/261] Add: populate eventId and itemId in transactions endpoint --- src/lottery/modules/populates/transactions.js | 11 ++++++ src/lottery/routes/docs/transactions.js | 37 +++++++++++++++++-- src/lottery/services/transactions.js | 10 +++-- 3 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 src/lottery/modules/populates/transactions.js diff --git a/src/lottery/modules/populates/transactions.js b/src/lottery/modules/populates/transactions.js new file mode 100644 index 00000000..8d35eb8c --- /dev/null +++ b/src/lottery/modules/populates/transactions.js @@ -0,0 +1,11 @@ +const transactionPopulateOption = [ + { path: "eventId" }, + { + path: "itemId", + select: "name imageUrl price description isDisabled stock itemType", + }, +]; + +module.exports = { + transactionPopulateOption, +}; diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index 9c127bab..d428809a 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -40,13 +40,44 @@ transactionsDocs[`${apiPrefix}/`] = { eventId: { type: "string", description: - "Transaction과 관련된 이벤트의 ObjectId. 이벤트와 관련된 Transaction인 경우에만 포함됩니다.", - example: "EVENT OBJECT ID", + "Transaction과 관련된 이벤트의 Object. 이벤트와 관련된 Transaction인 경우에만 포함됩니다.", + properties: { + _id: { + type: "string", + description: "Event의 ObjectId", + example: "OBJECT ID", + }, + name: { + type: "string", + description: "이벤트의 이름", + example: "최초 로그인 이벤트", + }, + rewardAmount: { + type: "number", + description: "달성 보상", + example: 100, + }, + maxCount: { + type: "number", + description: "최대 달성 가능 횟수", + example: 1, + }, + expireat: { + type: "string", + description: "달성할 수 있는 마지막 시각", + example: "2023-01-01 00:00:00", + }, + isDisabled: { + type: "boolean", + description: "달성 불가능 여부", + example: false, + }, + }, }, itemId: { type: "object", description: - "Transaction과 관련된 아이템의 ObjectId. 아이템과 관련된 Transaction인 경우에만 포함됩니다.", + "Transaction과 관련된 아이템의 Object. 아이템과 관련된 Transaction인 경우에만 포함됩니다.", properties: { _id: { type: "string", diff --git a/src/lottery/services/transactions.js b/src/lottery/services/transactions.js index 85f6a993..2afbc609 100644 --- a/src/lottery/services/transactions.js +++ b/src/lottery/services/transactions.js @@ -1,12 +1,14 @@ const { transactionModel } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); +const { + transactionPopulateOption, +} = require("../modules/populates/transactions"); const getUserTransactionsHandler = async (req, res) => { try { - const transactions = await transactionModel.find( - { userId: req.userOid }, - "-userId" - ); + const transactions = await transactionModel + .find({ userId: req.userOid }, "-userId") + .populate(transactionPopulateOption); res.json({ transactions }); // userId는 이미 Frontend에서 알고 있고, 중복되는 값이므로 제외합니다. } catch (err) { logger.error(err); From d28bc456969330eac3eb53e9dfcc1dad8a158849 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 10 Sep 2023 18:43:23 +0900 Subject: [PATCH 085/261] Refactor: rename eventId and itemId in transactionSchema --- src/lottery/modules/admin.js | 4 ++-- src/lottery/modules/populates/transactions.js | 4 ++-- src/lottery/modules/stores/mongo.js | 4 ++-- src/lottery/routes/docs/transactions.js | 6 +++--- src/lottery/services/globalState.js | 2 +- src/lottery/services/items.js | 4 ++-- src/lottery/services/transactions.js | 9 +++++++-- 7 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/lottery/modules/admin.js b/src/lottery/modules/admin.js index a112323a..758cd041 100644 --- a/src/lottery/modules/admin.js +++ b/src/lottery/modules/admin.js @@ -12,7 +12,7 @@ const creditTransfer = async (userId, amount, eventId, comment) => { type: "get", amount, userId, - eventId, + event: eventId, comment, }); await transaction.save(); @@ -29,7 +29,7 @@ const creditWithdraw = async (userId, amount, itemId, comment) => { type: "use", amount, userId, - itemId, + item: itemId, comment, }); await transaction.save(); diff --git a/src/lottery/modules/populates/transactions.js b/src/lottery/modules/populates/transactions.js index 8d35eb8c..f4206d87 100644 --- a/src/lottery/modules/populates/transactions.js +++ b/src/lottery/modules/populates/transactions.js @@ -1,7 +1,7 @@ const transactionPopulateOption = [ - { path: "eventId" }, + { path: "event" }, { - path: "itemId", + path: "item", select: "name imageUrl price description isDisabled stock itemType", }, ]; diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index c6b38d03..e7a8c2d8 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -115,11 +115,11 @@ const transactionSchema = Schema({ ref: "User", required: true, }, - eventId: { + event: { type: Schema.Types.ObjectId, ref: "Event", }, - itemId: { + item: { type: Schema.Types.ObjectId, ref: "Item", }, diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index d428809a..a51e5dbb 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -37,8 +37,8 @@ transactionsDocs[`${apiPrefix}/`] = { description: "재화의 변화량의 절댓값", example: 50, }, - eventId: { - type: "string", + event: { + type: "object", description: "Transaction과 관련된 이벤트의 Object. 이벤트와 관련된 Transaction인 경우에만 포함됩니다.", properties: { @@ -74,7 +74,7 @@ transactionsDocs[`${apiPrefix}/`] = { }, }, }, - itemId: { + item: { type: "object", description: "Transaction과 관련된 아이템의 Object. 아이템과 관련된 Transaction인 경우에만 포함됩니다.", diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 37e9731c..88ae60cc 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -23,7 +23,7 @@ const getUserGlobalStateHandler = async (req, res) => { const itemPurchaseTransactions = await transactionModel.find({ userId: req.userOid, type: "use", - itemId: { + item: { $exists: true, $ne: null, }, diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 94029823..485744bf 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -47,7 +47,7 @@ const getRandomItem = async (req, depth) => { type: "use", amount: 0, userId: req.userOid, - itemId: randomItem._id, + item: randomItem._id, comment: `랜덤박스에서 ${randomItem.name} 획득 - 0개 차감`, }); await transaction.save(); @@ -124,7 +124,7 @@ const purchaseHandler = async (req, res) => { type: "use", amount: item.price, userId: req.userOid, - itemId: item._id, + item: item._id, comment: `${item.name} 구입 - ${item.price}개 차감`, }); await transaction.save(); diff --git a/src/lottery/services/transactions.js b/src/lottery/services/transactions.js index 2afbc609..665d2625 100644 --- a/src/lottery/services/transactions.js +++ b/src/lottery/services/transactions.js @@ -7,9 +7,14 @@ const { const getUserTransactionsHandler = async (req, res) => { try { const transactions = await transactionModel - .find({ userId: req.userOid }, "-userId") + .find({ userId: req.userOid }, "-userId -__v") .populate(transactionPopulateOption); - res.json({ transactions }); // userId는 이미 Frontend에서 알고 있고, 중복되는 값이므로 제외합니다. + if (transactions) + res.json({ + transactions, + }); // userId는 이미 Frontend에서 알고 있고, 중복되는 값이므로 제외합니다. + else + res.status(500).json({ error: "Transactions/ : internal server error" }); } catch (err) { logger.error(err); res.status(500).json({ error: "Transactions/ : internal server error" }); From dc506ea1734fb71361beb2165fbe95cb3aecc78a Mon Sep 17 00:00:00 2001 From: static Date: Sun, 10 Sep 2023 20:23:39 +0900 Subject: [PATCH 086/261] Add: events field in the response of global-state endpoint --- src/lottery/services/globalState.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 88ae60cc..6843fd53 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -1,5 +1,6 @@ const { eventStatusModel, + eventModel, transactionModel, itemModel, } = require("../modules/stores/mongo"); @@ -30,7 +31,7 @@ const getUserGlobalStateHandler = async (req, res) => { }); await Promise.all( itemPurchaseTransactions.map(async (purchase) => { - const item = await itemModel.findOne({ _id: purchase.itemId }); + const item = await itemModel.findOne({ _id: purchase.item }); if (item.itemType === 1) { ticket1Amount++; @@ -40,11 +41,14 @@ const getUserGlobalStateHandler = async (req, res) => { }) ); + const events = await eventModel.find({}, "-__v"); + res.json({ creditAmount: eventStatus.creditAmount, eventStatus: eventStatus.eventList.map((id) => id.toString()), ticket1Amount, ticket2Amount, + events, }); } catch (err) { logger.error(err); From 166be0bbc335d538adda7cac77e81f7e377ed50d Mon Sep 17 00:00:00 2001 From: static Date: Sun, 10 Sep 2023 20:30:23 +0900 Subject: [PATCH 087/261] Docs: events field in the response of global-state endpoint --- src/lottery/routes/docs/globalState.js | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 8499e8fb..3ebb2f97 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -40,6 +40,45 @@ globalStateDocs[`${apiPrefix}/`] = { description: "추첨권 (2)의 개수. 0 이상입니다.", example: 10, }, + events: { + type: "array", + description: "Event의 배열", + items: { + type: "object", + properties: { + _id: { + type: "string", + description: "Event의 ObjectId", + example: "OBJECT ID", + }, + name: { + type: "string", + description: "이벤트의 이름", + example: "최초 로그인 이벤트", + }, + rewardAmount: { + type: "number", + description: "달성 보상", + example: 100, + }, + maxCount: { + type: "number", + description: "최대 달성 가능 횟수", + example: 1, + }, + expireat: { + type: "string", + description: "달성할 수 있는 마지막 시각", + example: "2023-01-01 00:00:00", + }, + isDisabled: { + type: "boolean", + description: "달성 불가능 여부", + example: false, + }, + }, + }, + }, }, }, }, From cdbac5558393f92e08a58f0bc1125471ec4f0d07 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 10 Sep 2023 20:54:37 +0900 Subject: [PATCH 088/261] Refactor: use lean query in lottery module --- src/lottery/modules/admin.js | 4 +- src/lottery/modules/credit.js | 6 +-- src/lottery/services/globalState.js | 26 ++++++----- src/lottery/services/items.js | 64 ++++++++++++++-------------- src/lottery/services/transactions.js | 6 ++- 5 files changed, 56 insertions(+), 50 deletions(-) diff --git a/src/lottery/modules/admin.js b/src/lottery/modules/admin.js index 758cd041..248da144 100644 --- a/src/lottery/modules/admin.js +++ b/src/lottery/modules/admin.js @@ -6,7 +6,7 @@ const { eventEnv } = require("../../../loadenv"); /** eventId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. */ const creditTransfer = async (userId, amount, eventId, comment) => { const user = await useUserCreditAmount(userId); - await user.creditUpdate(amount); + await user.update(amount); const transaction = new transactionModel({ type: "get", @@ -23,7 +23,7 @@ const creditTransfer = async (userId, amount, eventId, comment) => { /** itemId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. */ const creditWithdraw = async (userId, amount, itemId, comment) => { const user = await useUserCreditAmount(userId); - await user.creditUpdate(-amount); + await user.update(-amount); const transaction = new transactionModel({ type: "use", diff --git a/src/lottery/modules/credit.js b/src/lottery/modules/credit.js index b55613a0..2349d0c4 100644 --- a/src/lottery/modules/credit.js +++ b/src/lottery/modules/credit.js @@ -1,12 +1,12 @@ const { eventStatusModel } = require("../modules/stores/mongo"); const useUserCreditAmount = async (userId) => { - const eventStatus = await eventStatusModel.findOne({ userId }); + const eventStatus = await eventStatusModel.findOne({ userId }).lean(); if (!eventStatus) return null; return { - creditAmount: eventStatus.creditAmount, - creditUpdate: async (delta) => { + amount: eventStatus.creditAmount, + update: async (delta) => { await eventStatusModel.updateOne( { _id: eventStatus._id }, { diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 6843fd53..b408e0b0 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -8,7 +8,9 @@ const logger = require("../../modules/logger"); const getUserGlobalStateHandler = async (req, res) => { try { - let eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); + let eventStatus = await eventStatusModel + .findOne({ userId: req.userOid }) + .lean(); if (!eventStatus) { // User마다 EventStatus를 가져야 하고, 현재 Taxi에는 회원 탈퇴 시스템이 없으므로, EventStatus가 없으면 새롭게 생성하도록 구현합니다. // EventStatus의 생성은 이곳에서만 이루어집니다!! @@ -21,17 +23,19 @@ const getUserGlobalStateHandler = async (req, res) => { let ticket1Amount = 0; let ticket2Amount = 0; - const itemPurchaseTransactions = await transactionModel.find({ - userId: req.userOid, - type: "use", - item: { - $exists: true, - $ne: null, - }, - }); + const itemPurchaseTransactions = await transactionModel + .find({ + userId: req.userOid, + type: "use", + item: { + $exists: true, + $ne: null, + }, + }) + .lean(); await Promise.all( itemPurchaseTransactions.map(async (purchase) => { - const item = await itemModel.findOne({ _id: purchase.item }); + const item = await itemModel.findOne({ _id: purchase.item }).lean(); if (item.itemType === 1) { ticket1Amount++; @@ -41,7 +45,7 @@ const getUserGlobalStateHandler = async (req, res) => { }) ); - const events = await eventModel.find({}, "-__v"); + const events = await eventModel.find({}, "-__v").lean(); res.json({ creditAmount: eventStatus.creditAmount, diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 485744bf..3cc154b0 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -5,11 +5,13 @@ const { useUserCreditAmount } = require("../modules/credit"); const getRandomItem = async (req, depth) => { if (depth >= 10) return null; - const items = await itemModel.find({ - isRandomItem: true, - stock: { $gt: 0 }, - isDisabled: false, - }); + const items = await itemModel + .find({ + isRandomItem: true, + stock: { $gt: 0 }, + isDisabled: false, + }) + .lean(); const randomItems = items .map((item) => { return Array(item.randomWeight).fill(item); @@ -30,18 +32,25 @@ const getRandomItem = async (req, depth) => { const randomItem = randomItems[Math.floor(Math.random() * randomItems.length)]; try { - const newRandomItem = await itemModel.findOneAndUpdate( - { _id: randomItem._id }, - { - $inc: { - stock: -1, + const newRandomItem = await itemModel + .findOneAndUpdate( + { _id: randomItem._id }, + { + $inc: { + stock: -1, + }, }, - }, - { - runValidators: true, - new: true, - } - ); + { + runValidators: true, + new: true, + fields: { + itemType: 0, + isRandomItem: 0, + randomWeight: 0, + }, + } + ) + .lean(); const transaction = new transactionModel({ type: "use", @@ -64,10 +73,9 @@ const getRandomItem = async (req, depth) => { const listHandler = async (_, res) => { try { - const items = await itemModel.find( - {}, - "name imageUrl price description isDisabled stock itemType" - ); + const items = await itemModel + .find({}, "name imageUrl price description isDisabled stock itemType") + .lean(); res.json({ items }); } catch (err) { logger.error(err); @@ -78,7 +86,7 @@ const listHandler = async (_, res) => { const purchaseHandler = async (req, res) => { try { const { itemId } = req.params; - const item = await itemModel.findOne({ _id: itemId }); + const item = await itemModel.findOne({ _id: itemId }).lean(); if (!item) return res.status(400).json({ error: "Items/Purchase : invalid Item" }); @@ -91,7 +99,7 @@ const purchaseHandler = async (req, res) => { // 구매 가능 조건: 크레딧이 충분하며, 재고가 남아있으며, 판매 중인 아이템이어야 합니다. if (item.isDisabled) return res.status(400).json({ error: "Items/Purchase : disabled item" }); - if (user.creditAmount < item.price) + if (user.amount < item.price) return res .status(400) .json({ error: "Items/Purchase : not enough credit" }); @@ -116,7 +124,7 @@ const purchaseHandler = async (req, res) => { ); // 2단계: 유저의 크레딧을 차감합니다. - await user.creditUpdate(-item.price); + await user.update(-item.price); // 3단계: Transaction을 추가합니다. // Transaction은 가장 마지막에 추가해야 다른 문서와의 불일치를 감지할 수 있습니다. @@ -140,15 +148,7 @@ const purchaseHandler = async (req, res) => { res.json({ result: true, - reward: { - _id: randomItem._id, - name: randomItem.name, - imageUrl: randomItem.imageUrl, - price: randomItem.price, - description: randomItem.description, - isDisabled: randomItem.isDisabled, - stock: randomItem.stock, - }, + reward: randomItem, }); } catch (err) { logger.error(err); diff --git a/src/lottery/services/transactions.js b/src/lottery/services/transactions.js index 665d2625..c151d81f 100644 --- a/src/lottery/services/transactions.js +++ b/src/lottery/services/transactions.js @@ -6,13 +6,15 @@ const { const getUserTransactionsHandler = async (req, res) => { try { + // userId는 이미 Frontend에서 알고 있고, 중복되는 값이므로 제외합니다. const transactions = await transactionModel .find({ userId: req.userOid }, "-userId -__v") - .populate(transactionPopulateOption); + .populate(transactionPopulateOption) + .lean(); if (transactions) res.json({ transactions, - }); // userId는 이미 Frontend에서 알고 있고, 중복되는 값이므로 제외합니다. + }); else res.status(500).json({ error: "Transactions/ : internal server error" }); } catch (err) { From daf64380758dfea06503047af996843bd8ccb72b Mon Sep 17 00:00:00 2001 From: static Date: Sun, 10 Sep 2023 21:09:02 +0900 Subject: [PATCH 089/261] Fix: infinity recursive call of getRandomItem function --- src/lottery/services/items.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 3cc154b0..41256097 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -17,14 +17,15 @@ const getRandomItem = async (req, depth) => { return Array(item.randomWeight).fill(item); }) .reduce((a, b) => a.concat(b), []); + const dumpRandomItems = randomItems + .map((item) => item._id.toString()) + .join(","); logger.info( - `유저 "${req.userOid}"에 의해 getRandomItem(depth=${depth})가 호출되었습니다.` + `[RandomBox] getRandomItem(depth=${depth}) is called by the user(id=${req.userOid}).` ); logger.info( - `유저 "${req.userOid}"의 랜덤박스 확률 정보입니다: [${randomItems - .map((item) => item._id.toString()) - .join(",")}]` + `[RandomBox] randomItems of the user(id=${req.userOid}) is [${dumpRandomItems}].` ); if (randomItems.length === 0) return null; @@ -64,10 +65,10 @@ const getRandomItem = async (req, depth) => { return newRandomItem; } catch (err) { logger.warn( - `유저 "${req.userOid}"의 랜덤박스 추첨이 실패했습니다. 오류 정보: ${err}` + `[RandomBox] getRandomItem(depth=${depth}) by the user(id=${req.userOid}) failed due to ${err}.` ); - return await getRandomItem(depth + 1); + return await getRandomItem(req, depth + 1); } }; From 292dac5d5f12384515d3eb5b12789264651feebd Mon Sep 17 00:00:00 2001 From: static Date: Sun, 10 Sep 2023 21:22:41 +0900 Subject: [PATCH 090/261] Refactor: improve the performance of calculating ticketAmounts --- src/lottery/modules/admin.js | 4 ++- src/lottery/modules/stores/mongo.js | 4 +++ src/lottery/services/globalState.js | 43 ++++++++++++---------------- src/lottery/services/items.js | 2 ++ src/lottery/services/transactions.js | 2 +- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/lottery/modules/admin.js b/src/lottery/modules/admin.js index 248da144..337d7d96 100644 --- a/src/lottery/modules/admin.js +++ b/src/lottery/modules/admin.js @@ -21,7 +21,8 @@ const creditTransfer = async (userId, amount, eventId, comment) => { }; /** itemId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. */ -const creditWithdraw = async (userId, amount, itemId, comment) => { +/** itemType이 없는 경우 null이 아닌 undefined를 넣어야 합니다. */ +const creditWithdraw = async (userId, amount, itemId, itemType, comment) => { const user = await useUserCreditAmount(userId); await user.update(-amount); @@ -30,6 +31,7 @@ const creditWithdraw = async (userId, amount, itemId, comment) => { amount, userId, item: itemId, + itemType, comment, }); await transaction.save(); diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index e7a8c2d8..b5e6f127 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -123,6 +123,10 @@ const transactionSchema = Schema({ type: Schema.Types.ObjectId, ref: "Item", }, + itemType: { + type: Number, + enum: [0, 1, 2, 3], + }, comment: { type: String, required: true, diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index b408e0b0..b771430e 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -20,31 +20,24 @@ const getUserGlobalStateHandler = async (req, res) => { await eventStatus.save(); } - let ticket1Amount = 0; - let ticket2Amount = 0; - - const itemPurchaseTransactions = await transactionModel - .find({ - userId: req.userOid, - type: "use", - item: { - $exists: true, - $ne: null, - }, - }) - .lean(); - await Promise.all( - itemPurchaseTransactions.map(async (purchase) => { - const item = await itemModel.findOne({ _id: purchase.item }).lean(); - - if (item.itemType === 1) { - ticket1Amount++; - } else if (item.itemType === 2) { - ticket2Amount++; - } - }) - ); - + const ticket1Amount = await transactionModel.count({ + userId: req.userOid, + type: "use", + item: { + $exists: true, + $ne: null, + }, + itemType: 1, + }); + const ticket2Amount = await transactionModel.count({ + userId: req.userOid, + type: "use", + item: { + $exists: true, + $ne: null, + }, + itemType: 2, + }); const events = await eventModel.find({}, "-__v").lean(); res.json({ diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 41256097..d47c2479 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -58,6 +58,7 @@ const getRandomItem = async (req, depth) => { amount: 0, userId: req.userOid, item: randomItem._id, + itemType: randomItem.itemType, comment: `랜덤박스에서 ${randomItem.name} 획득 - 0개 차감`, }); await transaction.save(); @@ -134,6 +135,7 @@ const purchaseHandler = async (req, res) => { amount: item.price, userId: req.userOid, item: item._id, + itemType: item.itemType, comment: `${item.name} 구입 - ${item.price}개 차감`, }); await transaction.save(); diff --git a/src/lottery/services/transactions.js b/src/lottery/services/transactions.js index c151d81f..2009e540 100644 --- a/src/lottery/services/transactions.js +++ b/src/lottery/services/transactions.js @@ -8,7 +8,7 @@ const getUserTransactionsHandler = async (req, res) => { try { // userId는 이미 Frontend에서 알고 있고, 중복되는 값이므로 제외합니다. const transactions = await transactionModel - .find({ userId: req.userOid }, "-userId -__v") + .find({ userId: req.userOid }, "-userId -itemType -__v") .populate(transactionPopulateOption) .lean(); if (transactions) From f8b7df185d9b67fc19d76c56704ac1c5d06d1540 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 16:14:03 +0900 Subject: [PATCH 091/261] Add: modules/events.js --- src/lottery/modules/admin.js | 77 ++++++++++++----------------------- src/lottery/modules/events.js | 50 +++++++++++++++++++++++ src/modules/adminResource.js | 2 + 3 files changed, 79 insertions(+), 50 deletions(-) create mode 100644 src/lottery/modules/events.js diff --git a/src/lottery/modules/admin.js b/src/lottery/modules/admin.js index 337d7d96..e95d1066 100644 --- a/src/lottery/modules/admin.js +++ b/src/lottery/modules/admin.js @@ -1,65 +1,42 @@ -const { useUserCreditAmount } = require("./credit"); -const { transactionModel } = require("./stores/mongo"); const { recordAction } = require("../../modules/adminResource"); const { eventEnv } = require("../../../loadenv"); -/** eventId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. */ -const creditTransfer = async (userId, amount, eventId, comment) => { - const user = await useUserCreditAmount(userId); - await user.update(amount); - - const transaction = new transactionModel({ - type: "get", - amount, - userId, - event: eventId, - comment, - }); - await transaction.save(); - - return transaction._id; -}; - -/** itemId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. */ -/** itemType이 없는 경우 null이 아닌 undefined를 넣어야 합니다. */ -const creditWithdraw = async (userId, amount, itemId, itemType, comment) => { - const user = await useUserCreditAmount(userId); - await user.update(-amount); - - const transaction = new transactionModel({ - type: "use", - amount, - userId, - item: itemId, - itemType, - comment, - }); - await transaction.save(); - - return transaction._id; -}; +const { eventHandler } = require("./events"); const instagramRewardActionHandler = async (req, res, context) => { - const transactionId = await creditTransfer( + const result = await eventHandler( context?.record?.params?.userId, - eventEnv.instagramReward, - eventEnv.instagramEventId, - eventEnv.instagramComment + eventEnv.instagramEventId ); - - let record = context.record.toJSON(context.currentAdmin); - record.params.creditAmount += eventEnv.instagramReward; - - return { - record, - transactionId, - }; + const record = context.record.toJSON(context.currentAdmin); + + if (result) { + record.params.creditAmount += result.event.rewardAmount; + + return { + record, + notice: { + message: "성공적으로 보상을 지급했습니다.", + }, + response: { + transactionId: result.transactionId, + }, + }; + } else + return { + record, + notice: { + message: "보상을 지급하지 못했습니다. 이미 보상을 받은 유저입니다.", + type: "error", + }, + }; }; const instagramRewardActionLogs = [ "update", { action: "create", - target: (res, req, context) => `Transaction(_id = ${res.transactionId})`, + target: (res, req, context) => + `Transaction(_id = ${res.response.transactionId})`, }, ]; diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js new file mode 100644 index 00000000..c62e565e --- /dev/null +++ b/src/lottery/modules/events.js @@ -0,0 +1,50 @@ +const { + eventStatusModel, + eventModel, + transactionModel, +} = require("./stores/mongo"); +const logger = require("../../modules/logger"); + +const eventHandler = async (userId, eventId) => { + const event = await eventModel.findOne({ _id: eventId }).lean(); + if (!event) { + logger.error(`알 수 없는 이벤트 ID 입니다: ${eventId}`); // 프로그래머의 실수로 인해서만 발생하므로 logger를 통해 오류를 알립니다. + return null; + } + + const eventStatus = await eventStatusModel.findOne({ userId }).lean(); + const eventCount = eventStatus.eventList.filter( + (event) => event.toString() === eventId + ).length; + if (eventCount >= event.maxCount) return null; // 이미 최대로 달성한 이벤트입니다. + + await eventStatusModel.updateOne( + { userId }, + { + $inc: { + creditAmount: event.rewardAmount, + }, + $push: { + eventList: eventId, + }, + } + ); + + const transaction = new transactionModel({ + type: "get", + amount: event.rewardAmount, + userId, + event: eventId, + comment: `${event.name} 달성 - ${event.rewardAmount}개 획득`, + }); + await transaction.save(); + + return { + event, + transactionId: transaction._id, + }; +}; + +module.exports = { + eventHandler, +}; diff --git a/src/modules/adminResource.js b/src/modules/adminResource.js index 655e928b..b75013b3 100644 --- a/src/modules/adminResource.js +++ b/src/modules/adminResource.js @@ -57,6 +57,8 @@ const defaultActionLogFeature = buildFeature({ }); const recordActionAfterHandler = (actions) => async (res, req, context) => { + if (!res.response) return res; + const actionsWrapper = Array.isArray(actions) ? actions : [actions]; for (const action of actionsWrapper) { if (typeof action === "string") { From b5238d78a05748686c21e64d5f109305e266ed04 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 16:16:07 +0900 Subject: [PATCH 092/261] Remove: instagramRewardAction --- src/lottery/index.js | 15 +++++------ src/lottery/modules/admin.js | 51 ------------------------------------ 2 files changed, 7 insertions(+), 59 deletions(-) delete mode 100644 src/lottery/modules/admin.js diff --git a/src/lottery/index.js b/src/lottery/index.js index d06be299..9ca0f3eb 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -7,7 +7,6 @@ const { } = require("./modules/stores/mongo"); const { buildResource } = require("../modules/adminResource"); -const { instagramRewardAction } = require("./modules/admin"); // [Routes] 기존 docs 라우터의 docs extend require("./routes/docs")(); @@ -27,15 +26,15 @@ lotteryRouter.use("/global-state", require("./routes/globalState")); lotteryRouter.use("/transactions", require("./routes/transactions")); lotteryRouter.use("/items", require("./routes/items")); -const eventStatusResource = buildResource([instagramRewardAction])( - eventStatusModel -); -const otherResources = [eventModel, itemModel, transactionModel].map( - buildResource() -); +const resources = [ + eventStatusModel, + eventModel, + itemModel, + transactionModel, +].map(buildResource()); module.exports = { checkReward, lotteryRouter, - resources: [eventStatusResource, ...otherResources], + resources, }; diff --git a/src/lottery/modules/admin.js b/src/lottery/modules/admin.js deleted file mode 100644 index e95d1066..00000000 --- a/src/lottery/modules/admin.js +++ /dev/null @@ -1,51 +0,0 @@ -const { recordAction } = require("../../modules/adminResource"); -const { eventEnv } = require("../../../loadenv"); - -const { eventHandler } = require("./events"); - -const instagramRewardActionHandler = async (req, res, context) => { - const result = await eventHandler( - context?.record?.params?.userId, - eventEnv.instagramEventId - ); - const record = context.record.toJSON(context.currentAdmin); - - if (result) { - record.params.creditAmount += result.event.rewardAmount; - - return { - record, - notice: { - message: "성공적으로 보상을 지급했습니다.", - }, - response: { - transactionId: result.transactionId, - }, - }; - } else - return { - record, - notice: { - message: "보상을 지급하지 못했습니다. 이미 보상을 받은 유저입니다.", - type: "error", - }, - }; -}; -const instagramRewardActionLogs = [ - "update", - { - action: "create", - target: (res, req, context) => - `Transaction(_id = ${res.response.transactionId})`, - }, -]; - -const instagramRewardAction = recordAction( - "instagramReward", - instagramRewardActionHandler, - instagramRewardActionLogs -); - -module.exports = { - instagramRewardAction, -}; From f047d853d7193fe31e5134b3388f72dc8ab3c549 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 17:58:55 +0900 Subject: [PATCH 093/261] Add: startat field in eventSchema --- src/lottery/modules/stores/mongo.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index b5e6f127..91dafbad 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -42,6 +42,10 @@ const eventSchema = Schema({ min: 0, validate: integerValidator, }, + startat: { + type: Date, + required: true, + }, expireat: { type: Date, required: true, From f190d7c5aecc2fddeb8c9745b41ec80b3ec2383c Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 18:06:43 +0900 Subject: [PATCH 094/261] Docs: startat field of the response of some endpoint --- src/lottery/routes/docs/globalState.js | 5 +++++ src/lottery/routes/docs/transactions.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 3ebb2f97..f236a7ed 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -66,6 +66,11 @@ globalStateDocs[`${apiPrefix}/`] = { description: "최대 달성 가능 횟수", example: 1, }, + startat: { + type: "string", + description: "달성할 수 있는 처음 시각", + example: "2023-01-01 00:00:00", + }, expireat: { type: "string", description: "달성할 수 있는 마지막 시각", diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index a51e5dbb..5ceb7e01 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -62,6 +62,11 @@ transactionsDocs[`${apiPrefix}/`] = { description: "최대 달성 가능 횟수", example: 1, }, + startat: { + type: "string", + description: "달성할 수 있는 처음 시각", + example: "2023-01-01 00:00:00", + }, expireat: { type: "string", description: "달성할 수 있는 마지막 시각", From 13a42caa7f1cff82fc57a0b7e90b5f9851ef80ef Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 18:36:05 +0900 Subject: [PATCH 095/261] Add: check if event can be achieved in eventHandler --- src/lottery/modules/events.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js index c62e565e..e5b0cca4 100644 --- a/src/lottery/modules/events.js +++ b/src/lottery/modules/events.js @@ -18,6 +18,9 @@ const eventHandler = async (userId, eventId) => { ).length; if (eventCount >= event.maxCount) return null; // 이미 최대로 달성한 이벤트입니다. + const now = Date.now(); + if (now < event.startat || now > event.expireat) return null; + await eventStatusModel.updateOne( { userId }, { From fa8ca5f082a31f17e3f822004a5901672bb3b0bb Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 20:23:20 +0900 Subject: [PATCH 096/261] Add: imageUrl field to eventSchema --- src/lottery/modules/stores/mongo.js | 4 ++++ src/lottery/routes/docs/globalState.js | 5 +++++ src/lottery/routes/docs/transactions.js | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index b5e6f127..57b471ca 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -50,6 +50,10 @@ const eventSchema = Schema({ type: Boolean, default: false, }, + imageUrl: { + type: String, + required: true, + }, }); const itemSchema = Schema({ diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 3ebb2f97..17486077 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -76,6 +76,11 @@ globalStateDocs[`${apiPrefix}/`] = { description: "달성 불가능 여부", example: false, }, + imageUrl: { + type: "string", + description: "이미지 썸네일 URL", + example: "THUMBNAIL URL", + }, }, }, }, diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index a51e5dbb..3054844e 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -72,6 +72,11 @@ transactionsDocs[`${apiPrefix}/`] = { description: "달성 불가능 여부", example: false, }, + imageUrl: { + type: "string", + description: "이미지 썸네일 URL", + example: "THUMBNAIL URL", + }, }, }, item: { From 39b9a92235b85e6d08c61dbf9469da85ea9203f3 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 20:32:12 +0900 Subject: [PATCH 097/261] Add: description field to eventSchema --- src/lottery/modules/stores/mongo.js | 4 ++++ src/lottery/routes/docs/globalState.js | 6 ++++++ src/lottery/routes/docs/transactions.js | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 57b471ca..8675851a 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -54,6 +54,10 @@ const eventSchema = Schema({ type: String, required: true, }, + description: { + type: String, + required: true, + }, }); const itemSchema = Schema({ diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 17486077..f623aaf6 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -81,6 +81,12 @@ globalStateDocs[`${apiPrefix}/`] = { description: "이미지 썸네일 URL", example: "THUMBNAIL URL", }, + description: { + type: "string", + description: "이벤트의 설명", + example: + "처음으로 이벤트 기간 중 Taxi에 로그인하면 송편을 드립니다.", + }, }, }, }, diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index 3054844e..4d8af2cc 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -77,6 +77,12 @@ transactionsDocs[`${apiPrefix}/`] = { description: "이미지 썸네일 URL", example: "THUMBNAIL URL", }, + description: { + type: "string", + description: "이벤트의 설명", + example: + "처음으로 이벤트 기간 중 Taxi에 로그인하면 송편을 드립니다.", + }, }, }, item: { From c1d1948ef914abed6078e8155cae61b3f04bbaf0 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 13 Sep 2023 20:45:46 +0900 Subject: [PATCH 098/261] Add: instagram event handler --- src/lottery/index.js | 1 + src/lottery/routes/docs/events.js | 0 src/lottery/routes/events.js | 9 +++++++++ src/lottery/services/events.js | 28 ++++++++++++++++++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 src/lottery/routes/docs/events.js create mode 100644 src/lottery/routes/events.js create mode 100644 src/lottery/services/events.js diff --git a/src/lottery/index.js b/src/lottery/index.js index 9ca0f3eb..98ab5a36 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -22,6 +22,7 @@ const lotteryRouter = express.Router(); lotteryRouter.use(require("../middlewares/originValidator")); // [Router] APIs +lotteryRouter.use("/events", require("./routes/events")); lotteryRouter.use("/global-state", require("./routes/globalState")); lotteryRouter.use("/transactions", require("./routes/transactions")); lotteryRouter.use("/items", require("./routes/items")); diff --git a/src/lottery/routes/docs/events.js b/src/lottery/routes/docs/events.js new file mode 100644 index 00000000..e69de29b diff --git a/src/lottery/routes/events.js b/src/lottery/routes/events.js new file mode 100644 index 00000000..9f2e6b18 --- /dev/null +++ b/src/lottery/routes/events.js @@ -0,0 +1,9 @@ +const express = require("express"); +const router = express.Router(); +const events = require("../services/events"); + +router.use(require("../../middlewares/auth")); +router.post("/instagram/share-event", events.instagramEventShareHandler); +router.post("/instagram/share-purchase", events.instagramPurchaseShareHandler); + +module.exports = router; diff --git a/src/lottery/services/events.js b/src/lottery/services/events.js new file mode 100644 index 00000000..a9da434f --- /dev/null +++ b/src/lottery/services/events.js @@ -0,0 +1,28 @@ +const logger = require("../../modules/logger"); +const eventHandler = require("../modules/events"); +// 인스타그램 스토리에 이벤트를 공유했을 때. +const instagramEventShareHandler = async (req, res) => { + try { + const userId = req.userOid; + // const eventId = + } catch (err) { + logger.err(err); + res.status(500).json({ error: "Events/Insagram/Share-Event" }); + } +}; + +// 인스타그램 스토리에 아이템 구매 내역을 공유했을 때. +const instagramPurchaseShareHandler = async (req, res) => { + try { + const userId = req.userOid; + // const eventId = + } catch (err) { + logger.err(err); + res.status(500).json({ error: "Events/Insagram/Share-Purchase" }); + } +}; + +module.exports = { + instagramEventShareHandler, + instagramPurchaseShareHandler, +}; From 180de96e3c8eb1f10d4cb613dcff0a8895a3f2af Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 22:11:33 +0900 Subject: [PATCH 099/261] Add: docs/eventsSchema.js and docs/itemsSchema.js --- src/lottery/routes/docs/eventsSchema.js | 66 +++++++++++++++++ src/lottery/routes/docs/globalState.js | 57 +++------------ src/lottery/routes/docs/items.js | 87 +--------------------- src/lottery/routes/docs/itemsSchema.js | 77 ++++++++++++++++++++ src/lottery/routes/docs/swaggerDocs.js | 7 +- src/lottery/routes/docs/transactions.js | 97 +------------------------ 6 files changed, 167 insertions(+), 224 deletions(-) create mode 100644 src/lottery/routes/docs/eventsSchema.js create mode 100644 src/lottery/routes/docs/itemsSchema.js diff --git a/src/lottery/routes/docs/eventsSchema.js b/src/lottery/routes/docs/eventsSchema.js new file mode 100644 index 00000000..710814b3 --- /dev/null +++ b/src/lottery/routes/docs/eventsSchema.js @@ -0,0 +1,66 @@ +const eventBase = { + type: "object", + required: [ + "_id", + "name", + "rewardAmount", + "maxCount", + "expireat", + "isDisabled", + "imageUrl", + "description", + ], + properties: { + _id: { + type: "string", + description: "Event의 ObjectId", + example: "OBJECT ID", + }, + name: { + type: "string", + description: "이벤트의 이름", + example: "최초 로그인 이벤트", + }, + rewardAmount: { + type: "number", + description: "달성 보상", + example: 100, + }, + maxCount: { + type: "number", + description: "최대 달성 가능 횟수", + example: 1, + }, + expireat: { + type: "string", + description: "달성할 수 있는 마지막 시각", + example: "2023-01-01 00:00:00", + }, + isDisabled: { + type: "boolean", + description: "달성 불가능 여부", + example: false, + }, + imageUrl: { + type: "string", + description: "이미지 썸네일 URL", + example: "THUMBNAIL URL", + }, + description: { + type: "string", + description: "이벤트의 설명", + example: "처음으로 이벤트 기간 중 Taxi에 로그인하면 송편을 드립니다.", + }, + }, +}; + +const eventsSchema = { + event: eventBase, + relatedEvent: { + ...eventBase, + description: + "Transaction과 관련된 이벤트의 Object. 이벤트와 관련된 Transaction인 경우에만 포함됩니다.", + }, +}; + +module.exports = eventsSchema; diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index f623aaf6..e350e35c 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -15,6 +15,13 @@ globalStateDocs[`${apiPrefix}/`] = { "application/json": { schema: { type: "object", + required: [ + "creditAmount", + "eventStatus", + "ticket1Amount", + "ticket2Amount", + "events", + ], properties: { creditAmount: { type: "number", @@ -28,66 +35,24 @@ globalStateDocs[`${apiPrefix}/`] = { items: { type: "string", description: "Event의 ObjectId", + example: "OBJECT ID", }, }, ticket1Amount: { type: "number", - description: "추첨권 (1)의 개수. 0 이상입니다.", + description: "일반 티켓의 개수. 0 이상입니다.", example: 10, }, ticket2Amount: { type: "number", - description: "추첨권 (2)의 개수. 0 이상입니다.", + description: "고급 티켓의 개수. 0 이상입니다.", example: 10, }, events: { type: "array", description: "Event의 배열", items: { - type: "object", - properties: { - _id: { - type: "string", - description: "Event의 ObjectId", - example: "OBJECT ID", - }, - name: { - type: "string", - description: "이벤트의 이름", - example: "최초 로그인 이벤트", - }, - rewardAmount: { - type: "number", - description: "달성 보상", - example: 100, - }, - maxCount: { - type: "number", - description: "최대 달성 가능 횟수", - example: 1, - }, - expireat: { - type: "string", - description: "달성할 수 있는 마지막 시각", - example: "2023-01-01 00:00:00", - }, - isDisabled: { - type: "boolean", - description: "달성 불가능 여부", - example: false, - }, - imageUrl: { - type: "string", - description: "이미지 썸네일 URL", - example: "THUMBNAIL URL", - }, - description: { - type: "string", - description: "이벤트의 설명", - example: - "처음으로 이벤트 기간 중 Taxi에 로그인하면 송편을 드립니다.", - }, - }, + $ref: "#/components/schemas/event", }, }, }, diff --git a/src/lottery/routes/docs/items.js b/src/lottery/routes/docs/items.js index 865a3902..d1c0956e 100644 --- a/src/lottery/routes/docs/items.js +++ b/src/lottery/routes/docs/items.js @@ -15,56 +15,13 @@ itemsDocs[`${apiPrefix}/list`] = { "application/json": { schema: { type: "object", + required: ["items"], properties: { items: { type: "array", description: "Item의 배열", items: { - type: "object", - properties: { - _id: { - type: "string", - description: "Item의 ObjectId", - example: "OBJECT ID", - }, - name: { - type: "string", - description: "상품의 이름", - example: "랜덤 상자", - }, - imageUrl: { - type: "string", - description: "이미지 썸네일 URL", - example: "THUMBNAIL URL", - }, - price: { - type: "number", - description: "상품의 가격. 0 이상입니다.", - example: 400, - }, - description: { - type: "string", - description: "상품의 설명", - example: - "랜덤으로 상품이 나오는 상자입니다. 확률은 다음과 같습니다: 진짜송편 100%, 치킨 0%, ...", - }, - isDisabled: { - type: "boolean", - description: "판매 중지 여부", - example: false, - }, - stock: { - type: "number", - description: "남은 상품 재고. 0 이상입니다.", - example: 10, - }, - itemType: { - type: "number", - description: - "아이템 유형. 0: 티켓아님, 1:티켓 타입1, 2: 티켓 타입 2, 3: 랜덤박스", - example: 0, - }, - }, + $ref: "#/components/schemas/item", }, }, }, @@ -95,45 +52,7 @@ itemsDocs[`${apiPrefix}/purchase/:itemId`] = { example: true, }, reward: { - type: "object", - description: "랜덤박스를 구입한 경우에만 포함됩니다.", - properties: { - _id: { - type: "string", - description: "Item의 ObjectId", - example: "OBJECT ID", - }, - name: { - type: "string", - description: "상품의 이름", - example: "진짜송편", - }, - imageUrl: { - type: "string", - description: "이미지 썸네일 URL", - example: "THUMBNAIL URL", - }, - price: { - type: "number", - description: "상품의 가격. 0 이상입니다.", - example: 400, - }, - description: { - type: "string", - description: "상품의 설명", - example: "맛있는 송편입니다.", - }, - isDisabled: { - type: "boolean", - description: "판매 중지 여부", - example: false, - }, - stock: { - type: "number", - description: "남은 상품 재고. 0 이상입니다.", - example: 10, - }, - }, + $ref: "#/components/schemas/rewardItem", }, }, }, diff --git a/src/lottery/routes/docs/itemsSchema.js b/src/lottery/routes/docs/itemsSchema.js new file mode 100644 index 00000000..dc80c5d0 --- /dev/null +++ b/src/lottery/routes/docs/itemsSchema.js @@ -0,0 +1,77 @@ +const itemBase = { + type: "object", + required: [ + "_id", + "name", + "imageUrl", + "price", + "description", + "isDisabled", + "stock", + ], + properties: { + _id: { + type: "string", + description: "Item의 ObjectId", + example: "OBJECT ID", + }, + name: { + type: "string", + description: "상품의 이름", + example: "진짜송편", + }, + imageUrl: { + type: "string", + description: "이미지 썸네일 URL", + example: "THUMBNAIL URL", + }, + price: { + type: "number", + description: "상품의 가격. 0 이상입니다.", + example: 400, + }, + description: { + type: "string", + description: "상품의 설명", + example: "맛있는 송편입니다.", + }, + isDisabled: { + type: "boolean", + description: "판매 중지 여부", + example: false, + }, + stock: { + type: "number", + description: "남은 상품 재고. 0 이상입니다.", + example: 10, + }, + }, +}; +const itemWithType = { + type: itemBase.type, + required: itemBase.required.concat(["itemType"]), + properties: { + ...itemBase.properties, + itemType: { + type: "number", + description: + "상품 유형. 0: 일반 상품, 1: 일반 티켓, 2: 고급 티켓, 3: 랜덤박스입니다.", + example: 0, + }, + }, +}; + +const itemsSchema = { + item: itemWithType, + relatedItem: { + ...itemWithType, + description: + "Transaction과 관련된 아이템의 Object. 아이템과 관련된 Transaction인 경우에만 포함됩니다.", + }, + rewardItem: { + ...itemBase, + description: "랜덤박스를 구입한 경우에만 포함됩니다.", + }, +}; + +module.exports = itemsSchema; diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index 654bcc43..efec40c5 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -2,6 +2,8 @@ const { eventMode } = require("../../../../loadenv"); const globalStateDocs = require("./globalState"); const itemsDocs = require("./items"); const transactionsDocs = require("./transactions"); +const eventsSchema = require("./eventsSchema"); +const itemsSchema = require("./itemsSchema"); const apiPrefix = `/events/${eventMode}`; @@ -26,7 +28,10 @@ const eventSwaggerDocs = { ...transactionsDocs, }, components: { - schemas: {}, + schemas: { + ...eventsSchema, + ...itemsSchema, + }, }, }; diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index 4d8af2cc..df1e1cb9 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -14,12 +14,14 @@ transactionsDocs[`${apiPrefix}/`] = { "application/json": { schema: { type: "object", + required: ["transactions"], properties: { transactions: { type: "array", description: "유저의 재화 입출금 기록의 배열", items: { type: "object", + required: ["_id", "type", "amount", "comment", "doneat"], properties: { _id: { type: "string", @@ -38,101 +40,10 @@ transactionsDocs[`${apiPrefix}/`] = { example: 50, }, event: { - type: "object", - description: - "Transaction과 관련된 이벤트의 Object. 이벤트와 관련된 Transaction인 경우에만 포함됩니다.", - properties: { - _id: { - type: "string", - description: "Event의 ObjectId", - example: "OBJECT ID", - }, - name: { - type: "string", - description: "이벤트의 이름", - example: "최초 로그인 이벤트", - }, - rewardAmount: { - type: "number", - description: "달성 보상", - example: 100, - }, - maxCount: { - type: "number", - description: "최대 달성 가능 횟수", - example: 1, - }, - expireat: { - type: "string", - description: "달성할 수 있는 마지막 시각", - example: "2023-01-01 00:00:00", - }, - isDisabled: { - type: "boolean", - description: "달성 불가능 여부", - example: false, - }, - imageUrl: { - type: "string", - description: "이미지 썸네일 URL", - example: "THUMBNAIL URL", - }, - description: { - type: "string", - description: "이벤트의 설명", - example: - "처음으로 이벤트 기간 중 Taxi에 로그인하면 송편을 드립니다.", - }, - }, + $ref: "#/components/schemas/relatedEvent", }, item: { - type: "object", - description: - "Transaction과 관련된 아이템의 Object. 아이템과 관련된 Transaction인 경우에만 포함됩니다.", - properties: { - _id: { - type: "string", - description: "Item의 ObjectId", - example: "OBJECT ID", - }, - name: { - type: "string", - description: "상품의 이름", - example: "랜덤 상자", - }, - imageUrl: { - type: "string", - description: "이미지 썸네일 URL", - example: "THUMBNAIL URL", - }, - price: { - type: "number", - description: "상품의 가격. 0 이상입니다.", - example: 400, - }, - description: { - type: "string", - description: "상품의 설명", - example: - "랜덤으로 상품이 나오는 상자입니다. 확률은 다음과 같습니다: 진짜송편 100%, 치킨 0%, ...", - }, - isDisabled: { - type: "boolean", - description: "판매 중지 여부", - example: false, - }, - stock: { - type: "number", - description: "남은 상품 재고. 0 이상입니다.", - example: 10, - }, - itemType: { - type: "number", - description: - "아이템 유형. 0: 티켓아님, 1:티켓 타입1, 2: 티켓 타입 2, 3: 랜덤박스", - example: 0, - }, - }, + $ref: "#/components/schemas/relatedItem", }, comment: { type: "string", From 6c387ea9ccbc78b448d94878708865fcf3ec7dfe Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 22:41:25 +0900 Subject: [PATCH 100/261] Add: eventIds in loadenv.js --- loadenv.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/loadenv.js b/loadenv.js index 2bce4fb9..0bef02f3 100644 --- a/loadenv.js +++ b/loadenv.js @@ -39,4 +39,20 @@ module.exports = { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, eventMode: undefined, + eventIds: { + // required if eventMode === "2023fall" + firstLogin: process.env.EVENT_2023FALL_FIRST_LOGIN_ID, + payingAndSending: process.env.EVENT_2023FALL_PAYING_AND_SENDING_ID, + firstRoomCreation: process.env.EVENT_2023FALL_FIRST_ROOM_CREATION_ID, + roomSharing: process.env.EVENT_2023FALL_ROOM_SHARING_ID, + paying: process.env.EVENT_2023FALL_PAYING_ID, + sending: process.env.EVENT_2023FALL_SENDING_ID, + nicknameChanging: process.env.EVENT_2023FALL_NICKNAME_CHANGING_ID, + accountChanging: process.env.EVENT_2023FALL_ACCOUNT_CHANGING_ID, + AdPushAgreement: process.env.EVENT_2023FALL_AD_PUSH_AGREEMENT_ID, + eventSharingOnInstagram: + process.env.EVENT_2023FALL_EVENT_SHARING_ON_INSTAGRAM_ID, + purchaseSharingOnInstagram: + process.env.EVENT_2023FALL_PURCHASE_SHARING_ON_INSTAGRAM_ID, + }, }; From f6c3b944204ccabc3aa98d440e483e82cfaaee69 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 22:44:38 +0900 Subject: [PATCH 101/261] Fix: rename AdPushAgreement to adPushAgreement --- loadenv.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loadenv.js b/loadenv.js index 0bef02f3..5ce5827d 100644 --- a/loadenv.js +++ b/loadenv.js @@ -49,7 +49,7 @@ module.exports = { sending: process.env.EVENT_2023FALL_SENDING_ID, nicknameChanging: process.env.EVENT_2023FALL_NICKNAME_CHANGING_ID, accountChanging: process.env.EVENT_2023FALL_ACCOUNT_CHANGING_ID, - AdPushAgreement: process.env.EVENT_2023FALL_AD_PUSH_AGREEMENT_ID, + adPushAgreement: process.env.EVENT_2023FALL_AD_PUSH_AGREEMENT_ID, eventSharingOnInstagram: process.env.EVENT_2023FALL_EVENT_SHARING_ON_INSTAGRAM_ID, purchaseSharingOnInstagram: From 6326969fdbdbaf5e5e5aa67e0f8d691657f5a86e Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 22:53:07 +0900 Subject: [PATCH 102/261] Add: contracts/2023fall.js --- src/lottery/index.js | 4 ++ src/lottery/modules/contracts/2023fall.js | 64 +++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/lottery/modules/contracts/2023fall.js diff --git a/src/lottery/index.js b/src/lottery/index.js index 9ca0f3eb..26d2195f 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -6,6 +6,7 @@ const { transactionModel, } = require("./modules/stores/mongo"); +const { eventMode } = requires("../../loadenv"); const { buildResource } = require("../modules/adminResource"); // [Routes] 기존 docs 라우터의 docs extend @@ -33,8 +34,11 @@ const resources = [ transactionModel, ].map(buildResource()); +const contracts = require(`./modules/contracts/${eventMode}`); + module.exports = { checkReward, lotteryRouter, resources, + contracts, }; diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js new file mode 100644 index 00000000..a4cda756 --- /dev/null +++ b/src/lottery/modules/contracts/2023fall.js @@ -0,0 +1,64 @@ +const { eventIds } = require("../../../../loadenv"); +const { eventHandler } = require("../events"); + +// 로그인할 때마다 호출해 주세요. +const requestFirstLoginEvent = async (userId) => { + return await eventHandler(userId, eventIds.firstLogin); +}; + +const requestPayingAndSendingEvent = async () => { + // TODO +}; + +// 방을 만들 때마다 호출해 주세요. +const requestFirstRoomCreation = async (userId) => { + return await eventHandler(userId, eventIds.firstRoomCreation); +}; + +const requestRoomSharingEvent = async () => { + // TODO +}; + +const requestPayingEvent = async () => { + // TODO +}; + +const requestSendingEvent = async () => { + // TODO +}; + +// 닉네임을 변경할 때마다 호출해 주세요. +const requestNicknameChangingEvent = async (userId) => { + return await eventHandler(userId, eventIds.nicknameChanging); +}; + +// 계좌를 변경할 때마다 호출해 주세요. +const requestAccountChangingEvent = async (userId) => { + return await eventHandler(userId, eventIds.accountChanging); +}; + +const requestAdPushAgreementEvent = async () => { + // TODO +}; + +const requestEventSharingOnInstagram = async () => { + // TODO +}; + +const requestPurchaseSharingOnInstagram = async () => { + // TODO +}; + +module.exports = { + requestFirstLoginEvent, + requestPayingAndSendingEvent, + requestFirstRoomCreation, + requestRoomSharingEvent, + requestPayingEvent, + requestSendingEvent, + requestNicknameChangingEvent, + requestAccountChangingEvent, + requestAdPushAgreementEvent, + requestEventSharingOnInstagram, + requestPurchaseSharingOnInstagram, +}; From 7d943f2a478a1745974cedfc51af8df5f36992bd Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 23:02:49 +0900 Subject: [PATCH 103/261] Add: implement firstLoginEvent --- src/lottery/index.js | 2 +- src/lottery/modules/contracts/2023fall.js | 1 + src/services/auth.js | 3 +++ src/services/auth.mobile.js | 3 +++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lottery/index.js b/src/lottery/index.js index 26d2195f..e06c2988 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -6,7 +6,7 @@ const { transactionModel, } = require("./modules/stores/mongo"); -const { eventMode } = requires("../../loadenv"); +const { eventMode } = require("../../loadenv"); const { buildResource } = require("../modules/adminResource"); // [Routes] 기존 docs 라우터의 docs extend diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index a4cda756..1a9bc97d 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -2,6 +2,7 @@ const { eventIds } = require("../../../../loadenv"); const { eventHandler } = require("../events"); // 로그인할 때마다 호출해 주세요. +// 사용된 곳: auth/tryLogin, auth.mobile/tryLoginHandler const requestFirstLoginEvent = async (userId) => { return await eventHandler(userId, eventIds.firstLogin); }; diff --git a/src/services/auth.js b/src/services/auth.js index bab3fc71..9a3fef85 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -16,6 +16,8 @@ const { const jwt = require("../modules/auths/jwt"); const logger = require("../modules/logger"); +const { contracts } = require("../lottery"); + // SPARCS SSO const Client = require("../modules/auths/sparcssso"); const client = new Client(sparcsssoEnv?.id, sparcsssoEnv?.key); @@ -91,6 +93,7 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { } login(req, userData.sid, user.id, user._id, user.name); + await contracts.requestFirstLoginEvent(user._id); res.redirect(new URL(redirectPath, redirectOrigin).href); } catch (err) { logger.error(err); diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index fac66032..731ea64d 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -10,6 +10,8 @@ const logger = require("../modules/logger"); const { TOKEN_EXPIRED, TOKEN_INVALID } = require("../../loadenv").jwt; +const { contracts } = require("../lottery"); + const tokenLoginHandler = async (req, res) => { const { accessToken, deviceToken } = req.query; try { @@ -36,6 +38,7 @@ const tokenLoginHandler = async (req, res) => { login(req, user.sid, user.id, user._id, user.name); req.session.isApp = true; req.session.deviceToken = deviceToken; + await contracts.requestFirstLoginEvent(user._id); return res.status(200).json({ message: "success" }); } catch (e) { logger.error(e); From cebd609b0b2da45c3c4cd6f58140e7b8ae70d89c Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 23:13:17 +0900 Subject: [PATCH 104/261] Add: logging in eventHandler function --- src/lottery/modules/events.js | 100 +++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 38 deletions(-) diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js index e5b0cca4..03e17957 100644 --- a/src/lottery/modules/events.js +++ b/src/lottery/modules/events.js @@ -6,46 +6,70 @@ const { const logger = require("../../modules/logger"); const eventHandler = async (userId, eventId) => { - const event = await eventModel.findOne({ _id: eventId }).lean(); - if (!event) { - logger.error(`알 수 없는 이벤트 ID 입니다: ${eventId}`); // 프로그래머의 실수로 인해서만 발생하므로 logger를 통해 오류를 알립니다. - return null; - } + try { + logger.info( + `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 호출되었습니다.` + ); + + const event = await eventModel.findOne({ _id: eventId }).lean(); + if (!event) { + logger.error(`알 수 없는 이벤트 ID 입니다: ${eventId}`); // 프로그래머의 실수로 인해서만 발생하므로 logger를 통해 오류를 알립니다. + return null; + } + + const eventStatus = await eventStatusModel.findOne({ userId }).lean(); + const eventCount = eventStatus.eventList.filter( + (event) => event.toString() === eventId + ).length; + if (eventCount >= event.maxCount) { + logger.info( + `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 종료되었습니다: 이미 최대로 달성한 이벤트입니다.` + ); + return null; + } - const eventStatus = await eventStatusModel.findOne({ userId }).lean(); - const eventCount = eventStatus.eventList.filter( - (event) => event.toString() === eventId - ).length; - if (eventCount >= event.maxCount) return null; // 이미 최대로 달성한 이벤트입니다. - - const now = Date.now(); - if (now < event.startat || now > event.expireat) return null; - - await eventStatusModel.updateOne( - { userId }, - { - $inc: { - creditAmount: event.rewardAmount, - }, - $push: { - eventList: eventId, - }, + const now = Date.now(); + if (now < event.startat || now > event.expireat) { + logger.info( + `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 종료되었습니다: 달성할 수 있는 기간이 아닙니다.` + ); + return null; } - ); - - const transaction = new transactionModel({ - type: "get", - amount: event.rewardAmount, - userId, - event: eventId, - comment: `${event.name} 달성 - ${event.rewardAmount}개 획득`, - }); - await transaction.save(); - - return { - event, - transactionId: transaction._id, - }; + + await eventStatusModel.updateOne( + { userId }, + { + $inc: { + creditAmount: event.rewardAmount, + }, + $push: { + eventList: eventId, + }, + } + ); + + const transaction = new transactionModel({ + type: "get", + amount: event.rewardAmount, + userId, + event: eventId, + comment: `${event.name} 달성 - ${event.rewardAmount}개 획득`, + }); + await transaction.save(); + + logger.info( + `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 종료되었습니다: 성공했습니다.` + ); + return { + event, + transactionId: transaction._id, + }; + } catch (err) { + logger.error( + `eventHandler(userId=${userId}, eventId=${eventId}) 함수에서 예외가 발생했습니다: ${err}` + ); + return null; + } }; module.exports = { From 04b6fe1f57d5eb4521681dfeddfaab5cf93dd00f Mon Sep 17 00:00:00 2001 From: static Date: Thu, 14 Sep 2023 00:26:10 +0900 Subject: [PATCH 105/261] Add: implement firstRoomCreationEvent --- src/lottery/modules/contracts/2023fall.js | 13 +++++++++++-- src/services/auth.js | 4 ++++ src/services/auth.mobile.js | 4 ++++ src/services/rooms.js | 10 +++++++++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 1a9bc97d..b0d62503 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,9 +1,11 @@ -const { eventIds } = require("../../../../loadenv"); +const { eventMode, eventIds } = require("../../../../loadenv"); const { eventHandler } = require("../events"); // 로그인할 때마다 호출해 주세요. -// 사용된 곳: auth/tryLogin, auth.mobile/tryLoginHandler +// 사용된 곳: auth/tryLogin, auth.mobile/tokenLoginHandler const requestFirstLoginEvent = async (userId) => { + if (eventMode !== "2023fall") return null; + return await eventHandler(userId, eventIds.firstLogin); }; @@ -12,7 +14,10 @@ const requestPayingAndSendingEvent = async () => { }; // 방을 만들 때마다 호출해 주세요. +// 사용된 곳: rooms/createHandler const requestFirstRoomCreation = async (userId) => { + if (eventMode !== "2023fall") return null; + return await eventHandler(userId, eventIds.firstRoomCreation); }; @@ -30,11 +35,15 @@ const requestSendingEvent = async () => { // 닉네임을 변경할 때마다 호출해 주세요. const requestNicknameChangingEvent = async (userId) => { + if (eventMode !== "2023fall") return null; + return await eventHandler(userId, eventIds.nicknameChanging); }; // 계좌를 변경할 때마다 호출해 주세요. const requestAccountChangingEvent = async (userId) => { + if (eventMode !== "2023fall") return null; + return await eventHandler(userId, eventIds.accountChanging); }; diff --git a/src/services/auth.js b/src/services/auth.js index 9a3fef85..230e4d72 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -16,6 +16,7 @@ const { const jwt = require("../modules/auths/jwt"); const logger = require("../modules/logger"); +// 이벤트 코드입니다. const { contracts } = require("../lottery"); // SPARCS SSO @@ -93,7 +94,10 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { } login(req, userData.sid, user.id, user._id, user.name); + + // 이벤트 코드입니다. await contracts.requestFirstLoginEvent(user._id); + res.redirect(new URL(redirectPath, redirectOrigin).href); } catch (err) { logger.error(err); diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index 731ea64d..6ff4e00f 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -10,6 +10,7 @@ const logger = require("../modules/logger"); const { TOKEN_EXPIRED, TOKEN_INVALID } = require("../../loadenv").jwt; +// 이벤트 코드입니다. const { contracts } = require("../lottery"); const tokenLoginHandler = async (req, res) => { @@ -38,7 +39,10 @@ const tokenLoginHandler = async (req, res) => { login(req, user.sid, user.id, user._id, user.name); req.session.isApp = true; req.session.deviceToken = deviceToken; + + // 이벤트 코드입니다. await contracts.requestFirstLoginEvent(user._id); + return res.status(200).json({ message: "success" }); } catch (e) { logger.error(e); diff --git a/src/services/rooms.js b/src/services/rooms.js index 0456d534..6c7c32fb 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -11,6 +11,9 @@ const { getIsOver, } = require("../modules/populates/rooms"); +// 이벤트 코드입니다. +const { contracts } = require("../lottery"); + const createHandler = async (req, res) => { const { name, from, to, time, maxPartLength } = req.body; @@ -81,7 +84,12 @@ const createHandler = async (req, res) => { }); const roomObject = (await room.populate(roomPopulateOption)).toObject(); - return res.send(formatSettlement(roomObject)); + const roomObjectFormated = formatSettlement(roomObject); + + // 이벤트 코드입니다. + await contracts.requestFirstRoomCreation(user._id); + + return res.send(roomObjectFormated); } catch (err) { logger.error(err); res.status(500).json({ From 153ffb274628ea319b695bb0c0353193debc269f Mon Sep 17 00:00:00 2001 From: static Date: Thu, 14 Sep 2023 00:41:38 +0900 Subject: [PATCH 106/261] Add: nicknameChangingEvent and accountChangingEvent --- src/lottery/modules/contracts/2023fall.js | 2 + src/services/users.js | 71 +++++++++++++---------- 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index b0d62503..f88c2711 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -34,6 +34,7 @@ const requestSendingEvent = async () => { }; // 닉네임을 변경할 때마다 호출해 주세요. +// 사용된 곳: users/editNicknameHandler const requestNicknameChangingEvent = async (userId) => { if (eventMode !== "2023fall") return null; @@ -41,6 +42,7 @@ const requestNicknameChangingEvent = async (userId) => { }; // 계좌를 변경할 때마다 호출해 주세요. +// 사용된 곳: users/editAccountHandler const requestAccountChangingEvent = async (userId) => { if (eventMode !== "2023fall") return null; diff --git a/src/services/users.js b/src/services/users.js index 77af8c7d..ce51ea49 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -2,6 +2,9 @@ const { userModel } = require("../modules/stores/mongo"); const logger = require("../modules/logger"); const aws = require("../modules/stores/aws"); +// 이벤트 코드입니다. +const { contracts } = require("../lottery"); + const agreeOnTermsOfServiceHandler = async (req, res) => { try { let user = await userModel.findOne({ id: req.userId }); @@ -34,43 +37,47 @@ const getAgreeOnTermsOfServiceHandler = async (req, res) => { }; const editNicknameHandler = async (req, res) => { - const newNickname = req.body.nickname; + try { + const newNickname = req.body.nickname; + const result = await userModel.findOneAndUpdate( + { id: req.userId }, + { nickname: newNickname } + ); - // 닉네임을 갱신하고 결과를 반환 - await userModel - .findOneAndUpdate({ id: req.userId }, { nickname: newNickname }) - .then((result) => { - if (result) { - res - .status(200) - .send("User/editNickname : edit user nickname successful"); - } else { - res.status(400).send("User/editNickname : such user id does not exist"); - } - }) - .catch((err) => { - logger.error(err); - res.status(500).send("User/editNickname : internal server error"); - }); + if (result) { + // 이벤트 코드입니다. + await contracts.requestNicknameChangingEvent(req.userOid); + + res.status(200).send("User/editNickname : edit user nickname successful"); + } else { + res.status(400).send("User/editNickname : such user id does not exist"); + } + } catch (err) { + logger.error(err); + res.status(500).send("User/editNickname : internal server error"); + } }; const editAccountHandler = async (req, res) => { - const newAccount = req.body.account; + try { + const newAccount = req.body.account; + const result = await userModel.findOneAndUpdate( + { id: req.userId }, + { account: newAccount } + ); - // 계좌번호를 갱신하고 결과를 반환 - await userModel - .findOneAndUpdate({ id: req.userId }, { account: newAccount }) - .then((result) => { - if (result) { - res.status(200).send("User/editAccount : edit user account successful"); - } else { - res.status(400).send("User/editAccount : such user id does not exist"); - } - }) - .catch((err) => { - logger.error(err); - res.status(500).send("User/editAccount : internal server error"); - }); + if (result) { + // 이벤트 코드입니다. + await contracts.requestAccountChangingEvent(req.userOid); + + res.status(200).send("User/editAccount : edit user account successful"); + } else { + res.status(400).send("User/editAccount : such user id does not exist"); + } + } catch (err) { + logger.error(err); + res.status(500).send("User/editAccount : internal server error"); + } }; const editProfileImgGetPUrlHandler = async (req, res) => { From 69c0f8157512b084176c49586795377f8d005350 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 14 Sep 2023 01:31:08 +0900 Subject: [PATCH 107/261] Add: implement payingEvent and sendingEvent --- src/lottery/modules/contracts/2023fall.js | 18 ++++++++++++++---- src/services/rooms.js | 6 ++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index f88c2711..6877cedf 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -25,12 +25,22 @@ const requestRoomSharingEvent = async () => { // TODO }; -const requestPayingEvent = async () => { - // TODO +// 정산 요청이 이루어질 때마다 호출해 주세요. +// 사용된 곳: rooms/commitPaymentHandler +const requestPayingEvent = async (userId, roomObject) => { + if (eventMode !== "2023fall") return null; + if (roomObject.part.length < 2) return null; + + return await eventHandler(userId, eventIds.paying); }; -const requestSendingEvent = async () => { - // TODO +// 송금이 이루어질 때마다 호출해 주세요. +// 사용된 곳: rooms/settlementHandler +const requestSendingEvent = async (userId, roomObject) => { + if (eventMode !== "2023fall") return null; + if (roomObject.part.length < 2) return null; + + return await eventHandler(userId, eventIds.sending); }; // 닉네임을 변경할 때마다 호출해 주세요. diff --git a/src/services/rooms.js b/src/services/rooms.js index 6c7c32fb..51056084 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -491,6 +491,9 @@ const commitPaymentHandler = async (req, res) => { authorId: user._id, }); + // 이벤트 코드입니다. + await contracts.requestPayingEvent(req.userOid, roomObject); + // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); } catch (err) { @@ -557,6 +560,9 @@ const settlementHandler = async (req, res) => { authorId: user._id, }); + // 이벤트 코드입니다. + await contracts.requestSendingEvent(req.userOid, roomObject); + // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); } catch (err) { From 74206ddc78aefd632f8380716909fbbad1acae04 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 14 Sep 2023 02:42:52 +0900 Subject: [PATCH 108/261] Add: implement payingAndSendingEvent --- src/lottery/modules/contracts/2023fall.js | 15 +++++++++++++-- src/services/rooms.js | 2 ++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 6877cedf..9f7ca22f 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -9,8 +9,19 @@ const requestFirstLoginEvent = async (userId) => { return await eventHandler(userId, eventIds.firstLogin); }; -const requestPayingAndSendingEvent = async () => { - // TODO +// 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. +// 사용된 곳: rooms/commitPaymentHandler, rooms/settlementHandler +const requestPayingAndSendingEvent = async (roomObject) => { + if (eventMode !== "2023fall") return null; + if (roomObject.part.length < 2) return null; + if (roomObject.part.length > roomObject.settlementTotal) return null; + + return await Promise.all( + roomObject.part.map( + async (participant) => + await eventHandler(participant.user._id, eventIds.payingAndSending) + ) + ); }; // 방을 만들 때마다 호출해 주세요. diff --git a/src/services/rooms.js b/src/services/rooms.js index 51056084..f4d738ca 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -493,6 +493,7 @@ const commitPaymentHandler = async (req, res) => { // 이벤트 코드입니다. await contracts.requestPayingEvent(req.userOid, roomObject); + await contracts.requestPayingAndSendingEvent(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); @@ -562,6 +563,7 @@ const settlementHandler = async (req, res) => { // 이벤트 코드입니다. await contracts.requestSendingEvent(req.userOid, roomObject); + await contracts.requestPayingAndSendingEvent(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); From ce6a30a809dd7a492d68880d4bc15f0ec81b3830 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 14 Sep 2023 03:09:28 +0900 Subject: [PATCH 109/261] Fix: error occuring when eventMode === undefined --- src/lottery/index.js | 20 ++++++++++++-------- src/lottery/modules/contracts/2023fall.js | 13 +------------ src/services/auth.js | 4 ++-- src/services/auth.mobile.js | 4 ++-- src/services/rooms.js | 12 ++++++------ src/services/users.js | 6 +++--- 6 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/lottery/index.js b/src/lottery/index.js index e06c2988..c4b060ce 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -8,15 +8,11 @@ const { const { eventMode } = require("../../loadenv"); const { buildResource } = require("../modules/adminResource"); +const logger = require("../modules/logger"); // [Routes] 기존 docs 라우터의 docs extend require("./routes/docs")(); -// [Middleware] 목표 달성 여부 검증 -const checkReward = (req, res, next) => { - next(); -}; - const lotteryRouter = express.Router(); // [Middleware] 모든 API 요청에 대하여 origin 검증 @@ -34,11 +30,19 @@ const resources = [ transactionModel, ].map(buildResource()); -const contracts = require(`./modules/contracts/${eventMode}`); +const contracts = eventMode ? require(`./modules/contracts/${eventMode}`) : {}; +const getContract = (name) => { + const contract = contracts[name]; + if (contract) return contract; + + if (eventMode) { + logger.error(`Contract ${name}를 찾을 수 없습니다.`); + } + return () => null; +}; module.exports = { - checkReward, lotteryRouter, resources, - contracts, + getContract, }; diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 9f7ca22f..2d487c1f 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,18 +1,15 @@ -const { eventMode, eventIds } = require("../../../../loadenv"); +const { eventIds } = require("../../../../loadenv"); const { eventHandler } = require("../events"); // 로그인할 때마다 호출해 주세요. // 사용된 곳: auth/tryLogin, auth.mobile/tokenLoginHandler const requestFirstLoginEvent = async (userId) => { - if (eventMode !== "2023fall") return null; - return await eventHandler(userId, eventIds.firstLogin); }; // 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. // 사용된 곳: rooms/commitPaymentHandler, rooms/settlementHandler const requestPayingAndSendingEvent = async (roomObject) => { - if (eventMode !== "2023fall") return null; if (roomObject.part.length < 2) return null; if (roomObject.part.length > roomObject.settlementTotal) return null; @@ -27,8 +24,6 @@ const requestPayingAndSendingEvent = async (roomObject) => { // 방을 만들 때마다 호출해 주세요. // 사용된 곳: rooms/createHandler const requestFirstRoomCreation = async (userId) => { - if (eventMode !== "2023fall") return null; - return await eventHandler(userId, eventIds.firstRoomCreation); }; @@ -39,7 +34,6 @@ const requestRoomSharingEvent = async () => { // 정산 요청이 이루어질 때마다 호출해 주세요. // 사용된 곳: rooms/commitPaymentHandler const requestPayingEvent = async (userId, roomObject) => { - if (eventMode !== "2023fall") return null; if (roomObject.part.length < 2) return null; return await eventHandler(userId, eventIds.paying); @@ -48,7 +42,6 @@ const requestPayingEvent = async (userId, roomObject) => { // 송금이 이루어질 때마다 호출해 주세요. // 사용된 곳: rooms/settlementHandler const requestSendingEvent = async (userId, roomObject) => { - if (eventMode !== "2023fall") return null; if (roomObject.part.length < 2) return null; return await eventHandler(userId, eventIds.sending); @@ -57,16 +50,12 @@ const requestSendingEvent = async (userId, roomObject) => { // 닉네임을 변경할 때마다 호출해 주세요. // 사용된 곳: users/editNicknameHandler const requestNicknameChangingEvent = async (userId) => { - if (eventMode !== "2023fall") return null; - return await eventHandler(userId, eventIds.nicknameChanging); }; // 계좌를 변경할 때마다 호출해 주세요. // 사용된 곳: users/editAccountHandler const requestAccountChangingEvent = async (userId) => { - if (eventMode !== "2023fall") return null; - return await eventHandler(userId, eventIds.accountChanging); }; diff --git a/src/services/auth.js b/src/services/auth.js index 230e4d72..43d62bf9 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -17,7 +17,7 @@ const jwt = require("../modules/auths/jwt"); const logger = require("../modules/logger"); // 이벤트 코드입니다. -const { contracts } = require("../lottery"); +const { getContract } = require("../lottery"); // SPARCS SSO const Client = require("../modules/auths/sparcssso"); @@ -96,7 +96,7 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { login(req, userData.sid, user.id, user._id, user.name); // 이벤트 코드입니다. - await contracts.requestFirstLoginEvent(user._id); + await getContract("requestFirstLoginEvent")(user._id); res.redirect(new URL(redirectPath, redirectOrigin).href); } catch (err) { diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index 6ff4e00f..e4b4b894 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -11,7 +11,7 @@ const logger = require("../modules/logger"); const { TOKEN_EXPIRED, TOKEN_INVALID } = require("../../loadenv").jwt; // 이벤트 코드입니다. -const { contracts } = require("../lottery"); +const { getContract } = require("../lottery"); const tokenLoginHandler = async (req, res) => { const { accessToken, deviceToken } = req.query; @@ -41,7 +41,7 @@ const tokenLoginHandler = async (req, res) => { req.session.deviceToken = deviceToken; // 이벤트 코드입니다. - await contracts.requestFirstLoginEvent(user._id); + await getContract("requestFirstLoginEvent")(user._id); return res.status(200).json({ message: "success" }); } catch (e) { diff --git a/src/services/rooms.js b/src/services/rooms.js index f4d738ca..b4367bff 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -12,7 +12,7 @@ const { } = require("../modules/populates/rooms"); // 이벤트 코드입니다. -const { contracts } = require("../lottery"); +const { getContract } = require("../lottery"); const createHandler = async (req, res) => { const { name, from, to, time, maxPartLength } = req.body; @@ -87,7 +87,7 @@ const createHandler = async (req, res) => { const roomObjectFormated = formatSettlement(roomObject); // 이벤트 코드입니다. - await contracts.requestFirstRoomCreation(user._id); + await getContract("requestFirstRoomCreation")(user._id); return res.send(roomObjectFormated); } catch (err) { @@ -492,8 +492,8 @@ const commitPaymentHandler = async (req, res) => { }); // 이벤트 코드입니다. - await contracts.requestPayingEvent(req.userOid, roomObject); - await contracts.requestPayingAndSendingEvent(roomObject); + await getContract("requestPayingEvent")(req.userOid, roomObject); + await getContract("requestPayingAndSendingEvent")(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); @@ -562,8 +562,8 @@ const settlementHandler = async (req, res) => { }); // 이벤트 코드입니다. - await contracts.requestSendingEvent(req.userOid, roomObject); - await contracts.requestPayingAndSendingEvent(roomObject); + await getContract("requestSendingEvent")(req.userOid, roomObject); + await getContract("requestPayingAndSendingEvent")(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); diff --git a/src/services/users.js b/src/services/users.js index ce51ea49..1ec9978d 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -3,7 +3,7 @@ const logger = require("../modules/logger"); const aws = require("../modules/stores/aws"); // 이벤트 코드입니다. -const { contracts } = require("../lottery"); +const { getContract } = require("../lottery"); const agreeOnTermsOfServiceHandler = async (req, res) => { try { @@ -46,7 +46,7 @@ const editNicknameHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await contracts.requestNicknameChangingEvent(req.userOid); + await getContract("requestNicknameChangingEvent")(req.userOid); res.status(200).send("User/editNickname : edit user nickname successful"); } else { @@ -68,7 +68,7 @@ const editAccountHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await contracts.requestAccountChangingEvent(req.userOid); + await getContract("requestAccountChangingEvent")(req.userOid); res.status(200).send("User/editAccount : edit user account successful"); } else { From fc8e7af42b83447f34fe84cef7087495fbc03a52 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 14 Sep 2023 10:43:18 +0900 Subject: [PATCH 110/261] Docs: add comment for itemBase, itemWithType and eventBase --- src/lottery/routes/docs/eventsSchema.js | 1 + src/lottery/routes/docs/itemsSchema.js | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/lottery/routes/docs/eventsSchema.js b/src/lottery/routes/docs/eventsSchema.js index 710814b3..04fcb616 100644 --- a/src/lottery/routes/docs/eventsSchema.js +++ b/src/lottery/routes/docs/eventsSchema.js @@ -1,3 +1,4 @@ +/** Event에 대한 기본적인 프로퍼티를 갖고 있는 스키마입니다. */ const eventBase = { type: "object", required: [ diff --git a/src/lottery/routes/docs/itemsSchema.js b/src/lottery/routes/docs/itemsSchema.js index dc80c5d0..227f2b08 100644 --- a/src/lottery/routes/docs/itemsSchema.js +++ b/src/lottery/routes/docs/itemsSchema.js @@ -1,3 +1,4 @@ +/** Item에 대한 기본적인 프로퍼티를 갖고 있는 스키마입니다. */ const itemBase = { type: "object", required: [ @@ -47,6 +48,8 @@ const itemBase = { }, }, }; + +/** itemBase에 itemType(상품 유형) 프로퍼티가 추가된 스키마입니다. */ const itemWithType = { type: itemBase.type, required: itemBase.required.concat(["itemType"]), From b5dc891322ec7ce404c38fe92d9bfa830d07a729 Mon Sep 17 00:00:00 2001 From: cokia Date: Thu, 14 Sep 2023 22:47:44 +0900 Subject: [PATCH 111/261] chore: add jsdoc to 2023fall event logic --- src/lottery/modules/contracts/2023fall.js | 64 ++++++++++++++++++----- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 2d487c1f..20ed8227 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,14 +1,23 @@ const { eventIds } = require("../../../../loadenv"); const { eventHandler } = require("../events"); -// 로그인할 때마다 호출해 주세요. -// 사용된 곳: auth/tryLogin, auth.mobile/tokenLoginHandler +/** + * @param {string} userId + * @returns {Promise} + * @description 로그인할 때마다 호출해 주세요. + * @usage auth/tryLogin, auth/mobile/tokenLoginHandler + */ const requestFirstLoginEvent = async (userId) => { return await eventHandler(userId, eventIds.firstLogin); }; -// 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. -// 사용된 곳: rooms/commitPaymentHandler, rooms/settlementHandler +/** + * + * @param {*} roomObject + * @returns {Promise} + * @description 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. + * @usage rooms/commitPaymentHandler, rooms/settlementHandler + */ const requestPayingAndSendingEvent = async (roomObject) => { if (roomObject.part.length < 2) return null; if (roomObject.part.length > roomObject.settlementTotal) return null; @@ -21,8 +30,13 @@ const requestPayingAndSendingEvent = async (roomObject) => { ); }; -// 방을 만들 때마다 호출해 주세요. -// 사용된 곳: rooms/createHandler +/** + * + * @param {*} userId + * @returns {Promise} + * @description 방을 만들 때마다 호출해 주세요. + * @usage rooms/createHandler + */ const requestFirstRoomCreation = async (userId) => { return await eventHandler(userId, eventIds.firstRoomCreation); }; @@ -31,30 +45,52 @@ const requestRoomSharingEvent = async () => { // TODO }; -// 정산 요청이 이루어질 때마다 호출해 주세요. -// 사용된 곳: rooms/commitPaymentHandler +/** + * + * @param {*} userId + * @param {*} roomObject + * @returns {Promise} + * @description 정산 요청이 이루어질 때마다 호출해 주세요. + * @usage rooms/commitPaymentHandler + */ const requestPayingEvent = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; return await eventHandler(userId, eventIds.paying); }; -// 송금이 이루어질 때마다 호출해 주세요. -// 사용된 곳: rooms/settlementHandler +/** + * + * @param {*} userId + * @param {*} roomObject + * @returns {Promise} + * @description 송금이 이루어질 때마다 호출해 주세요. + * @usage rooms/settlementHandler + */ const requestSendingEvent = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; return await eventHandler(userId, eventIds.sending); }; -// 닉네임을 변경할 때마다 호출해 주세요. -// 사용된 곳: users/editNicknameHandler +/** + * + * @param {*} userId + * @returns {Promise} + * @description 닉네임을 변경할 때마다 호출해 주세요. + * @usage users/editNicknameHandler + */ const requestNicknameChangingEvent = async (userId) => { return await eventHandler(userId, eventIds.nicknameChanging); }; -// 계좌를 변경할 때마다 호출해 주세요. -// 사용된 곳: users/editAccountHandler +/** + * + * @param {*} userId + * @returns {Promise} + * @description 계좌를 변경할 때마다 호출해 주세요. + * @usage users/editAccountHandler + */ const requestAccountChangingEvent = async (userId) => { return await eventHandler(userId, eventIds.accountChanging); }; From f454b62a736412bcf433ae2d262bf273e5d8a486 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Thu, 14 Sep 2023 23:03:34 +0900 Subject: [PATCH 112/261] ADD: docs/events.js event documentation. --- src/lottery/routes/docs/events.js | 29 ++++++++++++++++++++++++++ src/lottery/routes/docs/swaggerDocs.js | 6 ++++++ 2 files changed, 35 insertions(+) diff --git a/src/lottery/routes/docs/events.js b/src/lottery/routes/docs/events.js index e69de29b..937ad1ee 100644 --- a/src/lottery/routes/docs/events.js +++ b/src/lottery/routes/docs/events.js @@ -0,0 +1,29 @@ +const { eventMode } = require("../../../../loadenv"); +const apiPrefix = `/events/${eventMode}/instagram`; + +const eventsDocs = {}; +eventsDocs[`${apiPrefix}/share-event`] = { + post: { + tags: [`${apiPrefix}`], + summary: "이벤트 공유시 보상 반환", + description: "인스타그램 스토리에 이벤트를 공유하면 보상 반환", + responses: { + 200: {}, + 500: {}, + }, + }, +}; + +eventsDocs[`${apiPrefix}/share-purchase`] = { + post: { + tags: [`${apiPrefix}`], + summary: "이벤트 공유시 보상 반환", + description: "인스타그램 스토리에 구매내역을 공유하면 보상 반환", + responses: { + 200: {}, + 500: {}, + }, + }, +}; + +module.exports = eventsDocs; diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index 654bcc43..85af25cd 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -2,6 +2,7 @@ const { eventMode } = require("../../../../loadenv"); const globalStateDocs = require("./globalState"); const itemsDocs = require("./items"); const transactionsDocs = require("./transactions"); +const eventsDocs = require("./events"); const apiPrefix = `/events/${eventMode}`; @@ -19,11 +20,16 @@ const eventSwaggerDocs = { name: `${apiPrefix}/transactions`, description: "이벤트 - 입출금 내역 관련 API", }, + { + name: `${apiPrefix}/instagram`, + description: "이벤트 - 인스타그램 이벤트 관련 API", + }, ], paths: { ...globalStateDocs, ...itemsDocs, ...transactionsDocs, + ...eventsDocs, }, components: { schemas: {}, From 0d5d0e18cd67bed7a9ccd1b77f62843a5029860f Mon Sep 17 00:00:00 2001 From: static Date: Thu, 14 Sep 2023 23:36:43 +0900 Subject: [PATCH 113/261] Refactor: use eventName instead of eventId in eventHandler --- loadenv.js | 16 --------------- src/lottery/modules/contracts/2023fall.js | 18 +++++++++-------- src/lottery/modules/events.js | 24 ++++++++++++----------- src/lottery/modules/stores/mongo.js | 1 + 4 files changed, 24 insertions(+), 35 deletions(-) diff --git a/loadenv.js b/loadenv.js index 5ce5827d..2bce4fb9 100644 --- a/loadenv.js +++ b/loadenv.js @@ -39,20 +39,4 @@ module.exports = { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, eventMode: undefined, - eventIds: { - // required if eventMode === "2023fall" - firstLogin: process.env.EVENT_2023FALL_FIRST_LOGIN_ID, - payingAndSending: process.env.EVENT_2023FALL_PAYING_AND_SENDING_ID, - firstRoomCreation: process.env.EVENT_2023FALL_FIRST_ROOM_CREATION_ID, - roomSharing: process.env.EVENT_2023FALL_ROOM_SHARING_ID, - paying: process.env.EVENT_2023FALL_PAYING_ID, - sending: process.env.EVENT_2023FALL_SENDING_ID, - nicknameChanging: process.env.EVENT_2023FALL_NICKNAME_CHANGING_ID, - accountChanging: process.env.EVENT_2023FALL_ACCOUNT_CHANGING_ID, - adPushAgreement: process.env.EVENT_2023FALL_AD_PUSH_AGREEMENT_ID, - eventSharingOnInstagram: - process.env.EVENT_2023FALL_EVENT_SHARING_ON_INSTAGRAM_ID, - purchaseSharingOnInstagram: - process.env.EVENT_2023FALL_PURCHASE_SHARING_ON_INSTAGRAM_ID, - }, }; diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 20ed8227..226c1aaf 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,4 +1,3 @@ -const { eventIds } = require("../../../../loadenv"); const { eventHandler } = require("../events"); /** @@ -8,7 +7,7 @@ const { eventHandler } = require("../events"); * @usage auth/tryLogin, auth/mobile/tokenLoginHandler */ const requestFirstLoginEvent = async (userId) => { - return await eventHandler(userId, eventIds.firstLogin); + return await eventHandler(userId, "이벤트 기간 첫 로그인"); }; /** @@ -25,7 +24,10 @@ const requestPayingAndSendingEvent = async (roomObject) => { return await Promise.all( roomObject.part.map( async (participant) => - await eventHandler(participant.user._id, eventIds.payingAndSending) + await eventHandler( + participant.user._id, + "2명 이상 탑승한 방에서 정산/송금 완료" + ) ) ); }; @@ -38,7 +40,7 @@ const requestPayingAndSendingEvent = async (roomObject) => { * @usage rooms/createHandler */ const requestFirstRoomCreation = async (userId) => { - return await eventHandler(userId, eventIds.firstRoomCreation); + return await eventHandler(userId, "첫 방 개설"); }; const requestRoomSharingEvent = async () => { @@ -56,7 +58,7 @@ const requestRoomSharingEvent = async () => { const requestPayingEvent = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await eventHandler(userId, eventIds.paying); + return await eventHandler(userId, "2명 이상 탑승한 방에서 정산하기"); }; /** @@ -70,7 +72,7 @@ const requestPayingEvent = async (userId, roomObject) => { const requestSendingEvent = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await eventHandler(userId, eventIds.sending); + return await eventHandler(userId, "2명 이상 탑승한 방에서 송금하기"); }; /** @@ -81,7 +83,7 @@ const requestSendingEvent = async (userId, roomObject) => { * @usage users/editNicknameHandler */ const requestNicknameChangingEvent = async (userId) => { - return await eventHandler(userId, eventIds.nicknameChanging); + return await eventHandler(userId, "닉네임 변경"); }; /** @@ -92,7 +94,7 @@ const requestNicknameChangingEvent = async (userId) => { * @usage users/editAccountHandler */ const requestAccountChangingEvent = async (userId) => { - return await eventHandler(userId, eventIds.accountChanging); + return await eventHandler(userId, "계좌 등록 또는 수정"); }; const requestAdPushAgreementEvent = async () => { diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js index 03e17957..caa9cff6 100644 --- a/src/lottery/modules/events.js +++ b/src/lottery/modules/events.js @@ -5,25 +5,27 @@ const { } = require("./stores/mongo"); const logger = require("../../modules/logger"); -const eventHandler = async (userId, eventId) => { +const eventHandler = async (userId, eventName) => { try { logger.info( - `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 호출되었습니다.` + `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 호출되었습니다.` ); - const event = await eventModel.findOne({ _id: eventId }).lean(); + const event = await eventModel.findOne({ name: eventName }).lean(); if (!event) { - logger.error(`알 수 없는 이벤트 ID 입니다: ${eventId}`); // 프로그래머의 실수로 인해서만 발생하므로 logger를 통해 오류를 알립니다. + logger.error( + `eventHandler(userId=${userId}, eventName="${eventName}") 함수에서 예외가 발생했습니다: 알 수 없는 이벤트 이름입니다.` + ); return null; } const eventStatus = await eventStatusModel.findOne({ userId }).lean(); const eventCount = eventStatus.eventList.filter( - (event) => event.toString() === eventId + (achievedEventId) => achievedEventId.toString() === event.id.toString() ).length; if (eventCount >= event.maxCount) { logger.info( - `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 종료되었습니다: 이미 최대로 달성한 이벤트입니다.` + `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 이미 최대로 달성한 이벤트입니다.` ); return null; } @@ -31,7 +33,7 @@ const eventHandler = async (userId, eventId) => { const now = Date.now(); if (now < event.startat || now > event.expireat) { logger.info( - `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 종료되었습니다: 달성할 수 있는 기간이 아닙니다.` + `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 달성할 수 있는 기간이 아닙니다.` ); return null; } @@ -43,7 +45,7 @@ const eventHandler = async (userId, eventId) => { creditAmount: event.rewardAmount, }, $push: { - eventList: eventId, + eventList: event._id, }, } ); @@ -52,13 +54,13 @@ const eventHandler = async (userId, eventId) => { type: "get", amount: event.rewardAmount, userId, - event: eventId, + event: event._id, comment: `${event.name} 달성 - ${event.rewardAmount}개 획득`, }); await transaction.save(); logger.info( - `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 종료되었습니다: 성공했습니다.` + `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 성공했습니다.` ); return { event, @@ -66,7 +68,7 @@ const eventHandler = async (userId, eventId) => { }; } catch (err) { logger.error( - `eventHandler(userId=${userId}, eventId=${eventId}) 함수에서 예외가 발생했습니다: ${err}` + `eventHandler(userId=${userId}, eventName="${eventName}") 함수에서 예외가 발생했습니다: ${err}` ); return null; } diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 91dafbad..c2a6ab2c 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -29,6 +29,7 @@ const eventSchema = Schema({ name: { type: String, required: true, + unique: true, }, rewardAmount: { type: Number, From ee6dace7c9c4cfbed13f8df3054dfce75dbbef1e Mon Sep 17 00:00:00 2001 From: static Date: Fri, 15 Sep 2023 01:00:18 +0900 Subject: [PATCH 114/261] Refactor: calling method of contracts --- src/lottery/index.js | 15 ++++----------- src/lottery/modules/events.js | 7 ++++++- src/services/auth.js | 4 ++-- src/services/auth.mobile.js | 4 ++-- src/services/rooms.js | 20 ++++++++++++++------ src/services/users.js | 10 +++++++--- 6 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/lottery/index.js b/src/lottery/index.js index c4b060ce..0663051f 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -30,19 +30,12 @@ const resources = [ transactionModel, ].map(buildResource()); -const contracts = eventMode ? require(`./modules/contracts/${eventMode}`) : {}; -const getContract = (name) => { - const contract = contracts[name]; - if (contract) return contract; - - if (eventMode) { - logger.error(`Contract ${name}를 찾을 수 없습니다.`); - } - return () => null; -}; +const contracts = eventMode + ? require(`./modules/contracts/${eventMode}`) + : undefined; module.exports = { lotteryRouter, resources, - getContract, + contracts, }; diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js index caa9cff6..dc447930 100644 --- a/src/lottery/modules/events.js +++ b/src/lottery/modules/events.js @@ -17,11 +17,16 @@ const eventHandler = async (userId, eventName) => { `eventHandler(userId=${userId}, eventName="${eventName}") 함수에서 예외가 발생했습니다: 알 수 없는 이벤트 이름입니다.` ); return null; + } else if (event.isDisabled) { + logger.info( + `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 달성할 수 없는 이벤트입니다.` + ); + return null; } const eventStatus = await eventStatusModel.findOne({ userId }).lean(); const eventCount = eventStatus.eventList.filter( - (achievedEventId) => achievedEventId.toString() === event.id.toString() + (achievedEventId) => achievedEventId.toString() === event._id.toString() ).length; if (eventCount >= event.maxCount) { logger.info( diff --git a/src/services/auth.js b/src/services/auth.js index 43d62bf9..69e89318 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -17,7 +17,7 @@ const jwt = require("../modules/auths/jwt"); const logger = require("../modules/logger"); // 이벤트 코드입니다. -const { getContract } = require("../lottery"); +const { contracts } = require("../lottery"); // SPARCS SSO const Client = require("../modules/auths/sparcssso"); @@ -96,7 +96,7 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { login(req, userData.sid, user.id, user._id, user.name); // 이벤트 코드입니다. - await getContract("requestFirstLoginEvent")(user._id); + contracts ? await contracts.requestFirstLoginEvent(user._id) : undefined; res.redirect(new URL(redirectPath, redirectOrigin).href); } catch (err) { diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index e4b4b894..7b125ac2 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -11,7 +11,7 @@ const logger = require("../modules/logger"); const { TOKEN_EXPIRED, TOKEN_INVALID } = require("../../loadenv").jwt; // 이벤트 코드입니다. -const { getContract } = require("../lottery"); +const { contracts } = require("../lottery"); const tokenLoginHandler = async (req, res) => { const { accessToken, deviceToken } = req.query; @@ -41,7 +41,7 @@ const tokenLoginHandler = async (req, res) => { req.session.deviceToken = deviceToken; // 이벤트 코드입니다. - await getContract("requestFirstLoginEvent")(user._id); + contracts ? await contracts.requestFirstLoginEvent(user._id) : undefined; return res.status(200).json({ message: "success" }); } catch (e) { diff --git a/src/services/rooms.js b/src/services/rooms.js index b4367bff..7149b24d 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -12,7 +12,7 @@ const { } = require("../modules/populates/rooms"); // 이벤트 코드입니다. -const { getContract } = require("../lottery"); +const { contracts } = require("../lottery"); const createHandler = async (req, res) => { const { name, from, to, time, maxPartLength } = req.body; @@ -87,7 +87,7 @@ const createHandler = async (req, res) => { const roomObjectFormated = formatSettlement(roomObject); // 이벤트 코드입니다. - await getContract("requestFirstRoomCreation")(user._id); + contracts ? await contracts.requestFirstRoomCreation(user._id) : undefined; return res.send(roomObjectFormated); } catch (err) { @@ -492,8 +492,12 @@ const commitPaymentHandler = async (req, res) => { }); // 이벤트 코드입니다. - await getContract("requestPayingEvent")(req.userOid, roomObject); - await getContract("requestPayingAndSendingEvent")(roomObject); + contracts + ? await contracts.requestPayingEvent(user._id, roomObject) + : undefined; + contracts + ? await contracts.requestPayingAndSendingEvent(roomObject) + : undefined; // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); @@ -562,8 +566,12 @@ const settlementHandler = async (req, res) => { }); // 이벤트 코드입니다. - await getContract("requestSendingEvent")(req.userOid, roomObject); - await getContract("requestPayingAndSendingEvent")(roomObject); + contracts + ? await contracts.requestSendingEvent(user._id, roomObject) + : undefined; + contracts + ? await contracts.requestPayingAndSendingEvent(roomObject) + : undefined; // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); diff --git a/src/services/users.js b/src/services/users.js index 1ec9978d..df5b8a21 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -3,7 +3,7 @@ const logger = require("../modules/logger"); const aws = require("../modules/stores/aws"); // 이벤트 코드입니다. -const { getContract } = require("../lottery"); +const { contracts } = require("../lottery"); const agreeOnTermsOfServiceHandler = async (req, res) => { try { @@ -46,7 +46,9 @@ const editNicknameHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await getContract("requestNicknameChangingEvent")(req.userOid); + contracts + ? await contracts.requestNicknameChangingEvent(req.userOid) + : undefined; res.status(200).send("User/editNickname : edit user nickname successful"); } else { @@ -68,7 +70,9 @@ const editAccountHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await getContract("requestAccountChangingEvent")(req.userOid); + contracts + ? await contracts.requestAccountChangingEvent(req.userOid) + : undefined; res.status(200).send("User/editAccount : edit user account successful"); } else { From f07ba8d33819574d5d28b129e459bbafd906eb87 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 15 Sep 2023 01:04:14 +0900 Subject: [PATCH 115/261] Remove: importing logger in lottery/index.js --- src/lottery/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lottery/index.js b/src/lottery/index.js index 0663051f..0dd69f5c 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -8,7 +8,6 @@ const { const { eventMode } = require("../../loadenv"); const { buildResource } = require("../modules/adminResource"); -const logger = require("../modules/logger"); // [Routes] 기존 docs 라우터의 docs extend require("./routes/docs")(); From 12df4bb8e257ba348c4871aaef7581bd15baad5c Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Fri, 15 Sep 2023 04:01:25 +0900 Subject: [PATCH 116/261] =?UTF-8?q?ADD:=20publicNoticePopulationOption=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20=EA=B8=B0=EC=A1=B4=20=EC=98=B5=EC=85=98?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lottery/index.js | 3 +- src/lottery/modules/populates/publicNotice.js | 14 +++++ src/lottery/routes/docs/events.js | 28 +++++++-- src/lottery/routes/docs/publicNotice.js | 27 ++++++++ src/lottery/routes/docs/swaggerDocs.js | 6 ++ src/lottery/routes/publicNotice.js | 10 +++ src/lottery/services/events.js | 8 ++- src/lottery/services/publicNotice.js | 62 +++++++++++++++++++ 8 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 src/lottery/modules/populates/publicNotice.js create mode 100644 src/lottery/routes/docs/publicNotice.js create mode 100644 src/lottery/routes/publicNotice.js create mode 100644 src/lottery/services/publicNotice.js diff --git a/src/lottery/index.js b/src/lottery/index.js index 98ab5a36..35861589 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -24,8 +24,9 @@ lotteryRouter.use(require("../middlewares/originValidator")); // [Router] APIs lotteryRouter.use("/events", require("./routes/events")); lotteryRouter.use("/global-state", require("./routes/globalState")); -lotteryRouter.use("/transactions", require("./routes/transactions")); lotteryRouter.use("/items", require("./routes/items")); +lotteryRouter.use("/transactions", require("./routes/transactions")); +lotteryRouter.use("/public-notice", require("./routes/publicNotice")); const resources = [ eventStatusModel, diff --git a/src/lottery/modules/populates/publicNotice.js b/src/lottery/modules/populates/publicNotice.js new file mode 100644 index 00000000..d0c3e5c5 --- /dev/null +++ b/src/lottery/modules/populates/publicNotice.js @@ -0,0 +1,14 @@ +const publicNoticePopulateOption = [ + { path: "event" }, + { + path: "item", + select: "name imageUrl price description isDisabled stock itemType", + }, + { + path: "", + }, +]; + +module.exports = { + publicNoticePopulateOption, +}; diff --git a/src/lottery/routes/docs/events.js b/src/lottery/routes/docs/events.js index 937ad1ee..9998e4ce 100644 --- a/src/lottery/routes/docs/events.js +++ b/src/lottery/routes/docs/events.js @@ -8,8 +8,18 @@ eventsDocs[`${apiPrefix}/share-event`] = { summary: "이벤트 공유시 보상 반환", description: "인스타그램 스토리에 이벤트를 공유하면 보상 반환", responses: { - 200: {}, - 500: {}, + 200: { + description: "", + content: { + "application/json": { + schema: { + type: "boolean", + example: { result: true }, + }, + }, + }, + }, + 500: { error: "Events/Insagram/Share-Event" }, }, }, }; @@ -20,8 +30,18 @@ eventsDocs[`${apiPrefix}/share-purchase`] = { summary: "이벤트 공유시 보상 반환", description: "인스타그램 스토리에 구매내역을 공유하면 보상 반환", responses: { - 200: {}, - 500: {}, + 200: { + description: "", + content: { + "application/json": { + schema: { + type: "boolean", + example: { result: true }, + }, + }, + }, + }, + 500: { error: "Events/Insagram/Share-Purchase" }, }, }, }; diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js new file mode 100644 index 00000000..de51e82e --- /dev/null +++ b/src/lottery/routes/docs/publicNotice.js @@ -0,0 +1,27 @@ +const { eventMode } = require("../../../../loadenv"); +const apiPrefix = `/events/${eventMode}/public-notice`; + +const publicNoticeDocs = {}; +publicNoticeDocs[`${apiPrefix}/get-recent-transaction`] = { + get: { + tags: [`${apiPrefix}`], + summary: "공지를 띄움", + description: "상품 구매, 뽑기 획득 등등에 대한 공지를 반환", + responses: { + 200: { + content: { + "application/json": { + schema: { + type: "array", + description: + "use 트랜젝션의 정보를 담은 string 배열. 구매자의 익명 이름과 구매 내역을 문자열로 변환 후 이를 배열로 담아 5개 반환", + example: ["AB****님께서 송편을(를) 구매하셨습니다.", "..."], + }, + }, + }, + }, + }, + }, +}; + +module.exports = publicNoticeDocs; diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index 85af25cd..d2a05a43 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -3,6 +3,7 @@ const globalStateDocs = require("./globalState"); const itemsDocs = require("./items"); const transactionsDocs = require("./transactions"); const eventsDocs = require("./events"); +const publicNoticeDocs = require("./publicNotice"); const apiPrefix = `/events/${eventMode}`; @@ -24,12 +25,17 @@ const eventSwaggerDocs = { name: `${apiPrefix}/instagram`, description: "이벤트 - 인스타그램 이벤트 관련 API", }, + { + name: `${apiPrefix}/public-notice`, + description: "이벤트 - 아이템 구매, 뽑기, 획득 공지 관련 API", + }, ], paths: { ...globalStateDocs, ...itemsDocs, ...transactionsDocs, ...eventsDocs, + ...publicNoticeDocs, }, components: { schemas: {}, diff --git a/src/lottery/routes/publicNotice.js b/src/lottery/routes/publicNotice.js new file mode 100644 index 00000000..454183a6 --- /dev/null +++ b/src/lottery/routes/publicNotice.js @@ -0,0 +1,10 @@ +const express = require("express"); +const router = express.Router(); +const publicNotice = require("../services/publicNotice"); + +// 상점이용은 로그인을 요구합니다. +router.use(require("../../middlewares/auth")); +router.get("/", publicNotice.test); +router.get("/get-recent-transaction", publicNotice.getRecentTransaction); + +module.exports = router; diff --git a/src/lottery/services/events.js b/src/lottery/services/events.js index a9da434f..8ea9bedc 100644 --- a/src/lottery/services/events.js +++ b/src/lottery/services/events.js @@ -4,7 +4,9 @@ const eventHandler = require("../modules/events"); const instagramEventShareHandler = async (req, res) => { try { const userId = req.userOid; - // const eventId = + const eventId = "650302f799c6f338d5ea5427"; + const transactionResult = await eventHandler(userId, eventId); + res.json({ result: !!transactionResult ? true : false }); } catch (err) { logger.err(err); res.status(500).json({ error: "Events/Insagram/Share-Event" }); @@ -15,7 +17,9 @@ const instagramEventShareHandler = async (req, res) => { const instagramPurchaseShareHandler = async (req, res) => { try { const userId = req.userOid; - // const eventId = + const eventId = "6503030e99c6f338d5ea5433"; + const transactionResult = await eventHandler(userId, eventId); + res.json({ result: !!transactionResult ? true : false }); } catch (err) { logger.err(err); res.status(500).json({ error: "Events/Insagram/Share-Purchase" }); diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js new file mode 100644 index 00000000..5b653bcb --- /dev/null +++ b/src/lottery/services/publicNotice.js @@ -0,0 +1,62 @@ +const { transactionModel } = require("../modules/stores/mongo"); +const logger = require("../../modules/logger"); +const { + transactionPopulateOption, +} = require("../modules/populates/transactions"); + +const test = (req, res) => { + console.log("test"); + res.status(200).json({ msg: "success" }); +}; + +const getRecentTransaction = async (req, res) => { + try { + const transactions = await transactionModel + .find({ type: "use" }) + .sort({ doneat: -1 }) + .limit(5) + .populate(transactionPopulateOption) + .lean(); + let transactionListString = []; + if (transactions) { + transactions.forEach((item, index) => { + let purchaceMessage = ""; + + console.log(item, index); + + if (item.comment.includes("구매")) { + purchaceMessage = "구입하셨습니다."; + } else if (item.comment.includes("획득")) { + purchaceMessage = "뽑았습니다."; + } else { + purchaceMessage = "획득하셨습니다."; + } + transactionListString[index] = `${item.userId + .toString() + .slice(0, 2)}${"*".repeat(item.userId.length - 2)}"님께서 "${ + item.item + }"을(를) " ${purchaceMessage}`; + console.log(transactionListString[index]); + }); + console.log(transactionListString); + + res.json({ + transactionListString, + }); + } else { + res.status(500).json({ + error: "PublicNotice/get-recent-transaction : internal server error", + }); + } + } catch (err) { + logger.error(err); + res.status(500).json({ + error: "PublicNotice/get-recent-transaction : internal server error", + }); + } +}; + +module.exports = { + test, + getRecentTransaction, +}; From ee3c625e2986a43ef9aaba600ee22bd9c5131085 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 15 Sep 2023 04:04:10 +0900 Subject: [PATCH 117/261] Docs: describe type of roomObject in contracts/2023fall.js --- src/lottery/modules/contracts/2023fall.js | 43 +++++++++++++---------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 226c1aaf..6cd91fe1 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,18 +1,21 @@ const { eventHandler } = require("../events"); +const mongoose = require("mongoose"); -/** - * @param {string} userId - * @returns {Promise} - * @description 로그인할 때마다 호출해 주세요. - * @usage auth/tryLogin, auth/mobile/tokenLoginHandler +/** + * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @returns {Promise} + * @description 로그인할 때마다 호출해 주세요. + * @usage auth/tryLogin, auth/mobile/tokenLoginHandler */ const requestFirstLoginEvent = async (userId) => { return await eventHandler(userId, "이벤트 기간 첫 로그인"); }; /** - * - * @param {*} roomObject + * + * @param {Object} roomObject - 방의 정보입니다. + * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. + * @param {number} roomObject.settlementTotal - 정산 또는 송금이 완료된 참여자 수입니다. * @returns {Promise} * @description 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. * @usage rooms/commitPaymentHandler, rooms/settlementHandler @@ -33,8 +36,8 @@ const requestPayingAndSendingEvent = async (roomObject) => { }; /** - * - * @param {*} userId + * + * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 방을 만들 때마다 호출해 주세요. * @usage rooms/createHandler @@ -48,9 +51,10 @@ const requestRoomSharingEvent = async () => { }; /** - * - * @param {*} userId - * @param {*} roomObject + * + * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {Object} roomObject - 방의 정보입니다. + * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} * @description 정산 요청이 이루어질 때마다 호출해 주세요. * @usage rooms/commitPaymentHandler @@ -62,9 +66,10 @@ const requestPayingEvent = async (userId, roomObject) => { }; /** - * - * @param {*} userId - * @param {*} roomObject + * + * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {Object} roomObject - 방의 정보입니다. + * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} * @description 송금이 이루어질 때마다 호출해 주세요. * @usage rooms/settlementHandler @@ -76,8 +81,8 @@ const requestSendingEvent = async (userId, roomObject) => { }; /** - * - * @param {*} userId + * + * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 닉네임을 변경할 때마다 호출해 주세요. * @usage users/editNicknameHandler @@ -87,8 +92,8 @@ const requestNicknameChangingEvent = async (userId) => { }; /** - * - * @param {*} userId + * + * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 계좌를 변경할 때마다 호출해 주세요. * @usage users/editAccountHandler From 42c1ca5faa643b7757f072f8e0ba875f814c2af1 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 15 Sep 2023 05:54:19 +0900 Subject: [PATCH 118/261] Add: ticket1Amount, ticket2Amount fields in eventStatusSchema --- src/lottery/modules/stores/mongo.js | 12 ++++++++++ src/lottery/services/globalState.js | 22 ++--------------- src/lottery/services/items.js | 37 +++++++++++++++++++++-------- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 8675851a..a3becf49 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -23,6 +23,18 @@ const eventStatusSchema = Schema({ min: 0, validate: integerValidator, }, + ticket1Amount: { + type: Number, + default: 0, + min: 0, + validate: integerValidator, + }, + ticket2Amount: { + type: Number, + default: 0, + min: 0, + validate: integerValidator, + }, }); const eventSchema = Schema({ diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index b771430e..ac3ee699 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -20,31 +20,13 @@ const getUserGlobalStateHandler = async (req, res) => { await eventStatus.save(); } - const ticket1Amount = await transactionModel.count({ - userId: req.userOid, - type: "use", - item: { - $exists: true, - $ne: null, - }, - itemType: 1, - }); - const ticket2Amount = await transactionModel.count({ - userId: req.userOid, - type: "use", - item: { - $exists: true, - $ne: null, - }, - itemType: 2, - }); const events = await eventModel.find({}, "-__v").lean(); res.json({ creditAmount: eventStatus.creditAmount, eventStatus: eventStatus.eventList.map((id) => id.toString()), - ticket1Amount, - ticket2Amount, + ticket1Amount: eventStatus.ticket1Amount, + ticket2Amount: eventStatus.ticket2Amount, events, }); } catch (err) { diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index d47c2479..cc44fca9 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -1,6 +1,21 @@ -const { itemModel, transactionModel } = require("../modules/stores/mongo"); +const { + eventStatusModel, + itemModel, + transactionModel, +} = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); -const { useUserCreditAmount } = require("../modules/credit"); + +const updateEventStatus = async (userId, creditDelta, itemType) => + await eventStatusModel.updateOne( + { userId }, + { + $inc: { + creditAmount: creditDelta, + ticket1Amount: itemType === 1 ? 1 : 0, + ticket2Amount: itemType === 2 ? 1 : 0, + }, + } + ); const getRandomItem = async (req, depth) => { if (depth >= 10) return null; @@ -33,6 +48,7 @@ const getRandomItem = async (req, depth) => { const randomItem = randomItems[Math.floor(Math.random() * randomItems.length)]; try { + // 1단계: 재고를 차감합니다. const newRandomItem = await itemModel .findOneAndUpdate( { _id: randomItem._id }, @@ -53,6 +69,10 @@ const getRandomItem = async (req, depth) => { ) .lean(); + // 2단계: 유저 정보를 업데이트합니다. + await updateEventStatus(req.userOid, 0, randomItem.itemType); + + // 3단계: Transaction을 추가합니다. const transaction = new transactionModel({ type: "use", amount: 0, @@ -92,8 +112,8 @@ const purchaseHandler = async (req, res) => { if (!item) return res.status(400).json({ error: "Items/Purchase : invalid Item" }); - const user = await useUserCreditAmount(req.userOid); - if (!user) + const eventStatus = await eventStatusModel.find({ userId: req.userOid }); + if (!eventStatus) return res .status(400) .json({ error: "Items/Purchase : invalid EventStatus" }); @@ -101,7 +121,7 @@ const purchaseHandler = async (req, res) => { // 구매 가능 조건: 크레딧이 충분하며, 재고가 남아있으며, 판매 중인 아이템이어야 합니다. if (item.isDisabled) return res.status(400).json({ error: "Items/Purchase : disabled item" }); - if (user.amount < item.price) + if (eventStatus.creditAmount < item.price) return res .status(400) .json({ error: "Items/Purchase : not enough credit" }); @@ -111,8 +131,6 @@ const purchaseHandler = async (req, res) => { .json({ error: "Items/Purchase : item out of stock" }); // 1단계: 재고를 차감합니다. - // 재고가 차감됐으나 유저 크레딧이 차감되지 않은 경우, 나중에 Transaction 기록 분석을 통해 오류 복구가 가능합니다. - // 하지만 유저 크레딧이 차감됐으나 재고가 차감되지 않은 경우, 다른 유저가 품절된 상품을 구입할 수 있게 되고, 이는 다수의 유저에게 불편을 야기할 수 있습니다. await itemModel.updateOne( { _id: item._id }, { @@ -125,11 +143,10 @@ const purchaseHandler = async (req, res) => { } ); - // 2단계: 유저의 크레딧을 차감합니다. - await user.update(-item.price); + // 2단계: 유저 정보를 업데이트합니다. + await updateEventStatus(req.userOid, -item.price, item.itemType); // 3단계: Transaction을 추가합니다. - // Transaction은 가장 마지막에 추가해야 다른 문서와의 불일치를 감지할 수 있습니다. const transaction = new transactionModel({ type: "use", amount: item.price, From 9ec9be800918fca308e89f466958f588a7e91100 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 15 Sep 2023 06:56:48 +0900 Subject: [PATCH 119/261] Add: events/2023fall/public-notice/leaderboard endpoint --- src/lottery/index.js | 1 + src/lottery/routes/publicNotice.js | 13 ++++++ src/lottery/services/globalState.js | 7 +-- src/lottery/services/publicNotice.js | 68 ++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 src/lottery/routes/publicNotice.js create mode 100644 src/lottery/services/publicNotice.js diff --git a/src/lottery/index.js b/src/lottery/index.js index d06be299..410097be 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -26,6 +26,7 @@ lotteryRouter.use(require("../middlewares/originValidator")); lotteryRouter.use("/global-state", require("./routes/globalState")); lotteryRouter.use("/transactions", require("./routes/transactions")); lotteryRouter.use("/items", require("./routes/items")); +lotteryRouter.use("/public-notice", require("./routes/publicNotice")); const eventStatusResource = buildResource([instagramRewardAction])( eventStatusModel diff --git a/src/lottery/routes/publicNotice.js b/src/lottery/routes/publicNotice.js new file mode 100644 index 00000000..59b67982 --- /dev/null +++ b/src/lottery/routes/publicNotice.js @@ -0,0 +1,13 @@ +const express = require("express"); + +const router = express.Router(); +const publicNoticeHandlers = require("../services/publicNotice"); +const auth = require("../../middlewares/auth"); + +router.get( + "/leaderboard", + auth, + publicNoticeHandlers.getTicketLeaderboardHandler +); + +module.exports = router; diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index ac3ee699..c55c13a3 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -1,9 +1,4 @@ -const { - eventStatusModel, - eventModel, - transactionModel, - itemModel, -} = require("../modules/stores/mongo"); +const { eventStatusModel, eventModel } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); const getUserGlobalStateHandler = async (req, res) => { diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js new file mode 100644 index 00000000..be2221e4 --- /dev/null +++ b/src/lottery/services/publicNotice.js @@ -0,0 +1,68 @@ +const { eventStatusModel } = require("../modules/stores/mongo"); +const { userModel } = require("../../modules/stores/mongo"); +const logger = require("../../modules/logger"); + +const getTicketLeaderboardHandler = async (req, res) => { + try { + const users = await eventStatusModel + .find( + { + $or: [ + { + ticket1Amount: { + $gt: 0, + }, + }, + { + ticket2Amount: { + $gt: 0, + }, + }, + ], + }, + "userId ticket1Amount ticket2Amount" + ) + .lean(); + const sortedUsers = users + .map((user) => ({ + userId: user.userId.toString(), + weight: user.ticket1Amount + 5 * user.ticket2Amount, + })) + .sort((a, b) => a.weight > b.weight); + + let rank = -1; + const weightSum = sortedUsers.reduce((before, user, index) => { + if (user.userId === req.userOid) { + rank = index; + } + return before + user.weight; + }, 0); + + const leaderboard = await Promise.all( + sortedUsers.slice(0, 20).map(async (user) => { + const userInfo = await userModel.findOne({ _id: user.userId }).lean(); + return { + nickname: userInfo?.nickname, + probability: user.weight / weightSum, + }; + }) + ); + + if (rank >= 0) + res.json({ + leaderboard, + rank: rank + 1, + probability: sortedUsers[rank].weight / weightSum, + }); + else res.json({ leaderboard }); + } catch (err) { + logger.error(err); + res + .status(500) + .json({ error: "PublicNotice/Leaderboard : internal server error" }); + } +}; + +module.exports = { + getTicketLeaderboardHandler, +}; From 0436396e076c0d26037a366508d4b88317b9bcc5 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Fri, 15 Sep 2023 14:40:15 +0900 Subject: [PATCH 120/261] =?UTF-8?q?ADD:=20publicNotice.js=20=EA=B5=AC?= =?UTF-8?q?=EB=A7=A4,=20=ED=9A=8D=EB=93=9D=20=EB=BD=91=EA=B8=B0=20?= =?UTF-8?q?=EA=B3=B5=EC=A7=80=20=EC=A0=95=EC=83=81=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lottery/modules/populates/publicNotice.js | 5 +- src/lottery/routes/docs/publicNotice.js | 8 ++- src/lottery/routes/publicNotice.js | 1 - src/lottery/services/publicNotice.js | 54 ++++++++++++------- 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/lottery/modules/populates/publicNotice.js b/src/lottery/modules/populates/publicNotice.js index d0c3e5c5..5709ed7d 100644 --- a/src/lottery/modules/populates/publicNotice.js +++ b/src/lottery/modules/populates/publicNotice.js @@ -2,10 +2,7 @@ const publicNoticePopulateOption = [ { path: "event" }, { path: "item", - select: "name imageUrl price description isDisabled stock itemType", - }, - { - path: "", + select: "name price description itemType", }, ]; diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js index de51e82e..2910361f 100644 --- a/src/lottery/routes/docs/publicNotice.js +++ b/src/lottery/routes/docs/publicNotice.js @@ -15,7 +15,13 @@ publicNoticeDocs[`${apiPrefix}/get-recent-transaction`] = { type: "array", description: "use 트랜젝션의 정보를 담은 string 배열. 구매자의 익명 이름과 구매 내역을 문자열로 변환 후 이를 배열로 담아 5개 반환", - example: ["AB****님께서 송편을(를) 구매하셨습니다.", "..."], + example: [ + "su****님께서 매점2000원권을(를) 구입하셨습니다.", + "mo****님께서 치킨을(를) 구입하셨습니다.", + "tu*****님께서 매점2000원권을(를) 구입하셨습니다.", + "as**님께서 진짜송편을(를) 구입하셨습니다.", + "qw**님께서 전동킥보드을(를) 뽑았습니다.", + ], }, }, }, diff --git a/src/lottery/routes/publicNotice.js b/src/lottery/routes/publicNotice.js index 454183a6..6f5f20c3 100644 --- a/src/lottery/routes/publicNotice.js +++ b/src/lottery/routes/publicNotice.js @@ -4,7 +4,6 @@ const publicNotice = require("../services/publicNotice"); // 상점이용은 로그인을 요구합니다. router.use(require("../../middlewares/auth")); -router.get("/", publicNotice.test); router.get("/get-recent-transaction", publicNotice.getRecentTransaction); module.exports = router; diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 5b653bcb..4e229962 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -1,29 +1,46 @@ const { transactionModel } = require("../modules/stores/mongo"); +const { userModel } = require("../../modules/stores/mongo"); const logger = require("../../modules/logger"); const { - transactionPopulateOption, -} = require("../modules/populates/transactions"); + publicNoticePopulateOption, +} = require("../modules/populates/publicNotice"); -const test = (req, res) => { - console.log("test"); - res.status(200).json({ msg: "success" }); -}; - -const getRecentTransaction = async (req, res) => { +const getTransactions = async () => { try { const transactions = await transactionModel .find({ type: "use" }) .sort({ doneat: -1 }) .limit(5) - .populate(transactionPopulateOption) + .populate(publicNoticePopulateOption) .lean(); - let transactionListString = []; if (transactions) { + return await getTransactionsCallbackGetUser(transactions); + } else { + return undefined; + } + } catch (err) { + return undefined; + } +}; +const getTransactionsCallbackGetUser = async (transactions) => { + const users = await userModel.find(); + for (let user of users) { + for (let transaction of transactions) { + if (user._id.equals(transaction.userId)) { + transaction.id = user.id; + } + } + } + return transactions; +}; +const getRecentTransaction = async (req, res) => { + try { + let transactionListString = []; + await getTransactions(); + const transactions = await getTransactions(); + if (!!transactions) { transactions.forEach((item, index) => { let purchaceMessage = ""; - - console.log(item, index); - if (item.comment.includes("구매")) { purchaceMessage = "구입하셨습니다."; } else if (item.comment.includes("획득")) { @@ -31,15 +48,13 @@ const getRecentTransaction = async (req, res) => { } else { purchaceMessage = "획득하셨습니다."; } - transactionListString[index] = `${item.userId + transactionListString[index] = `${item.id .toString() - .slice(0, 2)}${"*".repeat(item.userId.length - 2)}"님께서 "${ - item.item - }"을(를) " ${purchaceMessage}`; - console.log(transactionListString[index]); + .slice(0, 2)}${"*".repeat(item.id.length - 2)}님께서 ${ + item.item.name + }을(를) ${purchaceMessage}`; }); console.log(transactionListString); - res.json({ transactionListString, }); @@ -57,6 +72,5 @@ const getRecentTransaction = async (req, res) => { }; module.exports = { - test, getRecentTransaction, }; From d437ef2c66e2e23b04b85b2ff185d3396386b596 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 15 Sep 2023 15:41:20 +0900 Subject: [PATCH 121/261] Add: allow access to leaderboard for all requests --- src/lottery/routes/publicNotice.js | 7 +------ src/lottery/services/publicNotice.js | 11 ++++++++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/lottery/routes/publicNotice.js b/src/lottery/routes/publicNotice.js index 59b67982..2e09ee65 100644 --- a/src/lottery/routes/publicNotice.js +++ b/src/lottery/routes/publicNotice.js @@ -2,12 +2,7 @@ const express = require("express"); const router = express.Router(); const publicNoticeHandlers = require("../services/publicNotice"); -const auth = require("../../middlewares/auth"); -router.get( - "/leaderboard", - auth, - publicNoticeHandlers.getTicketLeaderboardHandler -); +router.get("/leaderboard", publicNoticeHandlers.getTicketLeaderboardHandler); module.exports = router; diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index be2221e4..29deb5be 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -1,6 +1,14 @@ const { eventStatusModel } = require("../modules/stores/mongo"); const { userModel } = require("../../modules/stores/mongo"); const logger = require("../../modules/logger"); +const { isLogin, getLoginInfo } = require("../../modules/auths/login"); + +const login = (req) => { + if (isLogin(req)) { + const { oid } = getLoginInfo(req); + return oid; + } else return null; +}; const getTicketLeaderboardHandler = async (req, res) => { try { @@ -31,8 +39,9 @@ const getTicketLeaderboardHandler = async (req, res) => { .sort((a, b) => a.weight > b.weight); let rank = -1; + const userId = login(req); const weightSum = sortedUsers.reduce((before, user, index) => { - if (user.userId === req.userOid) { + if (user.userId === userId) { rank = index; } return before + user.weight; From 2367f6ce9018e89fdd246bfd3bfc42cf75506615 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 15 Sep 2023 15:55:50 +0900 Subject: [PATCH 122/261] Docs: add docs/publicNotice.js --- src/lottery/routes/docs/publicNotice.js | 59 +++++++++++++++++++++++++ src/lottery/routes/docs/swaggerDocs.js | 6 +++ 2 files changed, 65 insertions(+) create mode 100644 src/lottery/routes/docs/publicNotice.js diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js new file mode 100644 index 00000000..c2b702e3 --- /dev/null +++ b/src/lottery/routes/docs/publicNotice.js @@ -0,0 +1,59 @@ +const { eventMode } = require("../../../../loadenv"); +const apiPrefix = `/events/${eventMode}/public-notice`; + +const publicNoticeDocs = {}; +publicNoticeDocs[`${apiPrefix}/leaderboard`] = { + get: { + tags: [`${apiPrefix}`], + summary: "리더보드 반환", + description: + "티켓 개수(고급 티켓은 일반 티켓 5개와 등가입니다.) 기준의 리더보드와 관련된 정보를 가져옵니다.", + responses: { + 200: { + description: "", + content: { + "application/json": { + schema: { + type: "object", + required: ["leaderboard"], + properties: { + leaderboard: { + type: "array", + description: "상위 20명만 포함된 리더보드", + items: { + type: "object", + required: ["nickname", "probability"], + properties: { + nickname: { + type: "string", + description: "유저의 닉네임", + example: "asdf", + }, + probability: { + type: "number", + description: "1등 당첨 확률", + example: 0.001, + }, + }, + }, + }, + rank: { + type: "number", + description: "유저의 리더보드 순위. 1부터 시작합니다.", + example: 30, + }, + probability: { + type: "number", + description: "1등 당첨 확률", + example: 0.00003, + }, + }, + }, + }, + }, + }, + }, + }, +}; + +module.exports = publicNoticeDocs; diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index efec40c5..cf48929f 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -4,6 +4,7 @@ const itemsDocs = require("./items"); const transactionsDocs = require("./transactions"); const eventsSchema = require("./eventsSchema"); const itemsSchema = require("./itemsSchema"); +const publicNoticeDocs = require("./publicNotice"); const apiPrefix = `/events/${eventMode}`; @@ -21,11 +22,16 @@ const eventSwaggerDocs = { name: `${apiPrefix}/transactions`, description: "이벤트 - 입출금 내역 관련 API", }, + { + name: `${apiPrefix}/public-notice`, + description: "이벤트 - 공지사항 관련 API", + }, ], paths: { ...globalStateDocs, ...itemsDocs, ...transactionsDocs, + ...publicNoticeDocs, }, components: { schemas: { From 9ed166ca026be5c5f184a7b3cbc9ae2eb1c7f85b Mon Sep 17 00:00:00 2001 From: static Date: Fri, 15 Sep 2023 23:19:07 +0900 Subject: [PATCH 123/261] Refactor: make shorter some code related to contracts --- src/lottery/index.js | 4 +--- src/lottery/modules/contracts/2023fall.js | 8 +------- src/services/auth.js | 2 +- src/services/auth.mobile.js | 2 +- src/services/rooms.js | 18 +++++------------- src/services/users.js | 8 ++------ 6 files changed, 11 insertions(+), 31 deletions(-) diff --git a/src/lottery/index.js b/src/lottery/index.js index 0dd69f5c..1ecef91d 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -29,9 +29,7 @@ const resources = [ transactionModel, ].map(buildResource()); -const contracts = eventMode - ? require(`./modules/contracts/${eventMode}`) - : undefined; +const contracts = eventMode && require(`./modules/contracts/${eventMode}`); module.exports = { lotteryRouter, diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 6cd91fe1..8b40a706 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -5,14 +5,13 @@ const mongoose = require("mongoose"); * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 로그인할 때마다 호출해 주세요. - * @usage auth/tryLogin, auth/mobile/tokenLoginHandler + * @usage auth/tryLogin, auth.mobile/tokenLoginHandler */ const requestFirstLoginEvent = async (userId) => { return await eventHandler(userId, "이벤트 기간 첫 로그인"); }; /** - * * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @param {number} roomObject.settlementTotal - 정산 또는 송금이 완료된 참여자 수입니다. @@ -36,7 +35,6 @@ const requestPayingAndSendingEvent = async (roomObject) => { }; /** - * * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 방을 만들 때마다 호출해 주세요. @@ -51,7 +49,6 @@ const requestRoomSharingEvent = async () => { }; /** - * * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. @@ -66,7 +63,6 @@ const requestPayingEvent = async (userId, roomObject) => { }; /** - * * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. @@ -81,7 +77,6 @@ const requestSendingEvent = async (userId, roomObject) => { }; /** - * * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 닉네임을 변경할 때마다 호출해 주세요. @@ -92,7 +87,6 @@ const requestNicknameChangingEvent = async (userId) => { }; /** - * * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 계좌를 변경할 때마다 호출해 주세요. diff --git a/src/services/auth.js b/src/services/auth.js index 69e89318..7b0851f1 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -96,7 +96,7 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { login(req, userData.sid, user.id, user._id, user.name); // 이벤트 코드입니다. - contracts ? await contracts.requestFirstLoginEvent(user._id) : undefined; + await contracts?.requestFirstLoginEvent(user._id); res.redirect(new URL(redirectPath, redirectOrigin).href); } catch (err) { diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index 7b125ac2..e2303961 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -41,7 +41,7 @@ const tokenLoginHandler = async (req, res) => { req.session.deviceToken = deviceToken; // 이벤트 코드입니다. - contracts ? await contracts.requestFirstLoginEvent(user._id) : undefined; + await contracts?.requestFirstLoginEvent(user._id); return res.status(200).json({ message: "success" }); } catch (e) { diff --git a/src/services/rooms.js b/src/services/rooms.js index 7149b24d..08f0776e 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -87,7 +87,7 @@ const createHandler = async (req, res) => { const roomObjectFormated = formatSettlement(roomObject); // 이벤트 코드입니다. - contracts ? await contracts.requestFirstRoomCreation(user._id) : undefined; + await contracts?.requestFirstRoomCreation(user._id); return res.send(roomObjectFormated); } catch (err) { @@ -492,12 +492,8 @@ const commitPaymentHandler = async (req, res) => { }); // 이벤트 코드입니다. - contracts - ? await contracts.requestPayingEvent(user._id, roomObject) - : undefined; - contracts - ? await contracts.requestPayingAndSendingEvent(roomObject) - : undefined; + await contracts?.requestPayingEvent(user._id, roomObject); + await contracts?.requestPayingAndSendingEvent(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); @@ -566,12 +562,8 @@ const settlementHandler = async (req, res) => { }); // 이벤트 코드입니다. - contracts - ? await contracts.requestSendingEvent(user._id, roomObject) - : undefined; - contracts - ? await contracts.requestPayingAndSendingEvent(roomObject) - : undefined; + await contracts?.requestSendingEvent(user._id, roomObject); + await contracts?.requestPayingAndSendingEvent(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); diff --git a/src/services/users.js b/src/services/users.js index df5b8a21..d37bf502 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -46,9 +46,7 @@ const editNicknameHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - contracts - ? await contracts.requestNicknameChangingEvent(req.userOid) - : undefined; + await contracts?.requestNicknameChangingEvent(req.userOid); res.status(200).send("User/editNickname : edit user nickname successful"); } else { @@ -70,9 +68,7 @@ const editAccountHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - contracts - ? await contracts.requestAccountChangingEvent(req.userOid) - : undefined; + await contracts?.requestAccountChangingEvent(req.userOid); res.status(200).send("User/editAccount : edit user account successful"); } else { From 431af7bb9067b4d185436184206de88ac74451f6 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 14:08:46 +0900 Subject: [PATCH 124/261] Refactor: store event data in 2023fall.js instead of database --- src/lottery/modules/contracts/2023fall.js | 72 ++++++++++++++++--- src/lottery/modules/events.js | 45 ++++-------- src/lottery/modules/populates/transactions.js | 1 - src/lottery/modules/stores/mongo.js | 32 ++------- src/lottery/routes/docs/globalState.js | 21 +----- src/lottery/routes/docs/transactions.js | 44 ++---------- src/lottery/services/globalState.js | 6 +- 7 files changed, 90 insertions(+), 131 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 8b40a706..4bd19fb0 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,6 +1,60 @@ const { eventHandler } = require("../events"); const mongoose = require("mongoose"); +const events = { + firstLogin: { + name: "이벤트 기간 첫 로그인", + rewardAmount: 150, + }, + payingAndSending: { + name: "2명 이상 탑승한 방에서 정산/송금 완료", + rewardAmount: 300, + maxCount: 3, + }, + firstRoomCreation: { + name: "첫 방 개설", + rewardAmount: 50, + }, + roomSharing: { + name: "방 공유하기", + rewardAmount: 50, + }, + paying: { + name: "2명 이상 탑승한 방에서 정산하기", + rewardAmount: 100, + maxCount: 3, + }, + sending: { + name: "2명 이상 탑승한 방에서 송금하기", + rewardAmount: 50, + maxCount: 3, + }, + nicknameChaning: { + name: "닉네임 변경", + rewardAmount: 50, + }, + accountChanging: { + name: "계좌 등록 또는 변경", + rewardAmount: 50, + }, + adPushAgreement: { + name: "광고성 푸시 알림 수신 동의", + rewardAmount: 50, + }, + eventSharingOnInstagram: { + name: "이벤트 인스타그램 스토리에 공유", + rewardAmount: 100, + }, + purchaseSharingOnInstagram: { + name: "아이템 구매 후 인스타그램 스토리에 공유", + rewardAmount: 100, + }, +}; + +for (const [id, event] of Object.entries(events)) { + event["id"] = id; // TODO: 외 event.id로는 않돼지???? +} + /** * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} @@ -8,7 +62,7 @@ const mongoose = require("mongoose"); * @usage auth/tryLogin, auth.mobile/tokenLoginHandler */ const requestFirstLoginEvent = async (userId) => { - return await eventHandler(userId, "이벤트 기간 첫 로그인"); + return await eventHandler(userId, events.firstLogin); }; /** @@ -26,10 +80,7 @@ const requestPayingAndSendingEvent = async (roomObject) => { return await Promise.all( roomObject.part.map( async (participant) => - await eventHandler( - participant.user._id, - "2명 이상 탑승한 방에서 정산/송금 완료" - ) + await eventHandler(participant.user._id, events.payingAndSending) ) ); }; @@ -41,7 +92,7 @@ const requestPayingAndSendingEvent = async (roomObject) => { * @usage rooms/createHandler */ const requestFirstRoomCreation = async (userId) => { - return await eventHandler(userId, "첫 방 개설"); + return await eventHandler(userId, events.firstRoomCreation); }; const requestRoomSharingEvent = async () => { @@ -59,7 +110,7 @@ const requestRoomSharingEvent = async () => { const requestPayingEvent = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await eventHandler(userId, "2명 이상 탑승한 방에서 정산하기"); + return await eventHandler(userId, events.paying); }; /** @@ -73,7 +124,7 @@ const requestPayingEvent = async (userId, roomObject) => { const requestSendingEvent = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await eventHandler(userId, "2명 이상 탑승한 방에서 송금하기"); + return await eventHandler(userId, events.sending); }; /** @@ -83,7 +134,7 @@ const requestSendingEvent = async (userId, roomObject) => { * @usage users/editNicknameHandler */ const requestNicknameChangingEvent = async (userId) => { - return await eventHandler(userId, "닉네임 변경"); + return await eventHandler(userId, events.nicknameChaning); }; /** @@ -93,7 +144,7 @@ const requestNicknameChangingEvent = async (userId) => { * @usage users/editAccountHandler */ const requestAccountChangingEvent = async (userId) => { - return await eventHandler(userId, "계좌 등록 또는 수정"); + return await eventHandler(userId, events.accountChanging); }; const requestAdPushAgreementEvent = async () => { @@ -109,6 +160,7 @@ const requestPurchaseSharingOnInstagram = async () => { }; module.exports = { + events, requestFirstLoginEvent, requestPayingAndSendingEvent, requestFirstRoomCreation, diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js index dc447930..18cbd9f2 100644 --- a/src/lottery/modules/events.js +++ b/src/lottery/modules/events.js @@ -5,41 +5,23 @@ const { } = require("./stores/mongo"); const logger = require("../../modules/logger"); -const eventHandler = async (userId, eventName) => { +const eventHandler = async (userId, event) => { try { - logger.info( - `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 호출되었습니다.` - ); - - const event = await eventModel.findOne({ name: eventName }).lean(); - if (!event) { - logger.error( - `eventHandler(userId=${userId}, eventName="${eventName}") 함수에서 예외가 발생했습니다: 알 수 없는 이벤트 이름입니다.` - ); - return null; - } else if (event.isDisabled) { - logger.info( - `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 달성할 수 없는 이벤트입니다.` - ); - return null; - } - const eventStatus = await eventStatusModel.findOne({ userId }).lean(); const eventCount = eventStatus.eventList.filter( - (achievedEventId) => achievedEventId.toString() === event._id.toString() + (achievedEventId) => achievedEventId === event.id ).length; - if (eventCount >= event.maxCount) { + const eventMaxCount = event.maxCount ? event.maxCount : 1; + if (eventCount >= eventMaxCount) { logger.info( - `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 이미 최대로 달성한 이벤트입니다.` + `User ${userId} already achieved ${event.id}Event ${eventCount} times` ); return null; } - const now = Date.now(); - if (now < event.startat || now > event.expireat) { - logger.info( - `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 달성할 수 있는 기간이 아닙니다.` - ); + const eventDoc = await eventModel.findOne({ id: event.id }).lean(); + if (eventDoc && eventDoc.isDisabled) { + logger.info(`User ${userId} failed to achieve disabled ${event.id}Event`); return null; } @@ -50,7 +32,7 @@ const eventHandler = async (userId, eventName) => { creditAmount: event.rewardAmount, }, $push: { - eventList: event._id, + eventList: event.id, }, } ); @@ -59,21 +41,20 @@ const eventHandler = async (userId, eventName) => { type: "get", amount: event.rewardAmount, userId, - event: event._id, + eventId: event.id, comment: `${event.name} 달성 - ${event.rewardAmount}개 획득`, }); await transaction.save(); - logger.info( - `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 성공했습니다.` - ); + logger.info(`User ${userId} successfully achieved ${event.id}Event`); return { event, transactionId: transaction._id, }; } catch (err) { + logger.error(err); logger.error( - `eventHandler(userId=${userId}, eventName="${eventName}") 함수에서 예외가 발생했습니다: ${err}` + `User ${userId} failed to achieve ${event.id}Event due to exception` ); return null; } diff --git a/src/lottery/modules/populates/transactions.js b/src/lottery/modules/populates/transactions.js index f4206d87..9f3a958e 100644 --- a/src/lottery/modules/populates/transactions.js +++ b/src/lottery/modules/populates/transactions.js @@ -1,5 +1,4 @@ const transactionPopulateOption = [ - { path: "event" }, { path: "item", select: "name imageUrl price description isDisabled stock itemType", diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index c2a6ab2c..cf695808 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -13,9 +13,8 @@ const eventStatusSchema = Schema({ required: true, }, eventList: { - type: [Schema.Types.ObjectId], + type: [String], default: [], - ref: "Event", }, creditAmount: { type: Number, @@ -26,34 +25,14 @@ const eventStatusSchema = Schema({ }); const eventSchema = Schema({ - name: { + id: { type: String, required: true, unique: true, }, - rewardAmount: { - type: Number, - required: true, - min: 0, - validate: integerValidator, - }, - maxCount: { - type: Number, - default: 1, - min: 0, - validate: integerValidator, - }, - startat: { - type: Date, - required: true, - }, - expireat: { - type: Date, - required: true, - }, isDisabled: { type: Boolean, - default: false, + required: true, }, }); @@ -120,9 +99,8 @@ const transactionSchema = Schema({ ref: "User", required: true, }, - event: { - type: Schema.Types.ObjectId, - ref: "Event", + eventId: { + type: String, }, item: { type: Schema.Types.ObjectId, diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index f236a7ed..d58c41ab 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -46,10 +46,10 @@ globalStateDocs[`${apiPrefix}/`] = { items: { type: "object", properties: { - _id: { + id: { type: "string", - description: "Event의 ObjectId", - example: "OBJECT ID", + description: "Event의 Id", + example: "EVENT ID", }, name: { type: "string", @@ -66,21 +66,6 @@ globalStateDocs[`${apiPrefix}/`] = { description: "최대 달성 가능 횟수", example: 1, }, - startat: { - type: "string", - description: "달성할 수 있는 처음 시각", - example: "2023-01-01 00:00:00", - }, - expireat: { - type: "string", - description: "달성할 수 있는 마지막 시각", - example: "2023-01-01 00:00:00", - }, - isDisabled: { - type: "boolean", - description: "달성 불가능 여부", - example: false, - }, }, }, }, diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index 5ceb7e01..3a804448 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -37,47 +37,11 @@ transactionsDocs[`${apiPrefix}/`] = { description: "재화의 변화량의 절댓값", example: 50, }, - event: { - type: "object", + eventId: { + type: "string", description: - "Transaction과 관련된 이벤트의 Object. 이벤트와 관련된 Transaction인 경우에만 포함됩니다.", - properties: { - _id: { - type: "string", - description: "Event의 ObjectId", - example: "OBJECT ID", - }, - name: { - type: "string", - description: "이벤트의 이름", - example: "최초 로그인 이벤트", - }, - rewardAmount: { - type: "number", - description: "달성 보상", - example: 100, - }, - maxCount: { - type: "number", - description: "최대 달성 가능 횟수", - example: 1, - }, - startat: { - type: "string", - description: "달성할 수 있는 처음 시각", - example: "2023-01-01 00:00:00", - }, - expireat: { - type: "string", - description: "달성할 수 있는 마지막 시각", - example: "2023-01-01 00:00:00", - }, - isDisabled: { - type: "boolean", - description: "달성 불가능 여부", - example: false, - }, - }, + "Transaction과 관련된 이벤트의 Id. 이벤트와 관련된 Transaction인 경우에만 포함됩니다.", + example: "EVENT ID", }, item: { type: "object", diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index b771430e..d566e610 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -1,11 +1,12 @@ const { eventStatusModel, - eventModel, transactionModel, - itemModel, } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); +const { eventMode } = require("../../../loadenv"); +const { events } = require(`../modules/contracts/${eventMode}`); + const getUserGlobalStateHandler = async (req, res) => { try { let eventStatus = await eventStatusModel @@ -38,7 +39,6 @@ const getUserGlobalStateHandler = async (req, res) => { }, itemType: 2, }); - const events = await eventModel.find({}, "-__v").lean(); res.json({ creditAmount: eventStatus.creditAmount, From 103b46a173163a9758ad93783d846d901b5d44db Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 14:57:59 +0900 Subject: [PATCH 125/261] Fix: error occuring when eventMode === undefined --- src/lottery/modules/contracts/2023fall.js | 10 +++++++++- src/lottery/modules/events.js | 3 +-- src/lottery/routes/docs/globalState.js | 8 ++++++++ src/lottery/services/globalState.js | 4 +++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 330747ab..51fe1e1f 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -7,6 +7,7 @@ const events = { description: "", imageUrl: "", rewardAmount: 150, + maxCount: 1, }, payingAndSending: { name: "2명 이상 탑승한 방에서 정산/송금 완료", @@ -20,12 +21,14 @@ const events = { description: "", imageUrl: "", rewardAmount: 50, + maxCount: 1, }, roomSharing: { name: "방 공유하기", description: "", imageUrl: "", rewardAmount: 50, + maxCount: 1, }, paying: { name: "2명 이상 탑승한 방에서 정산하기", @@ -46,35 +49,40 @@ const events = { description: "", imageUrl: "", rewardAmount: 50, + maxCount: 1, }, accountChanging: { name: "계좌 등록 또는 변경", description: "", imageUrl: "", rewardAmount: 50, + maxCount: 1, }, adPushAgreement: { name: "광고성 푸시 알림 수신 동의", description: "", imageUrl: "", rewardAmount: 50, + maxCount: 1, }, eventSharingOnInstagram: { name: "이벤트 인스타그램 스토리에 공유", description: "", imageUrl: "", rewardAmount: 100, + maxCount: 1, }, purchaseSharingOnInstagram: { name: "아이템 구매 후 인스타그램 스토리에 공유", description: "", imageUrl: "", rewardAmount: 100, + maxCount: 1, }, }; for (const [id, event] of Object.entries(events)) { - event["id"] = id; // TODO: 외 event.id로는 않돼지???? + event.id = id; } /** diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js index 18cbd9f2..7880d53a 100644 --- a/src/lottery/modules/events.js +++ b/src/lottery/modules/events.js @@ -11,8 +11,7 @@ const eventHandler = async (userId, event) => { const eventCount = eventStatus.eventList.filter( (achievedEventId) => achievedEventId === event.id ).length; - const eventMaxCount = event.maxCount ? event.maxCount : 1; - if (eventCount >= eventMaxCount) { + if (eventCount >= event.maxCount) { logger.info( `User ${userId} already achieved ${event.id}Event ${eventCount} times` ); diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 5a3f1240..01d65fa2 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -53,6 +53,14 @@ globalStateDocs[`${apiPrefix}/`] = { description: "Event의 배열", items: { type: "object", + required: [ + "id", + "name", + "description", + "imageUrl", + "rewardAmount", + "maxCount", + ], properties: { id: { type: "string", diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index d566e610..f9680bd2 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -5,7 +5,9 @@ const { const logger = require("../../modules/logger"); const { eventMode } = require("../../../loadenv"); -const { events } = require(`../modules/contracts/${eventMode}`); +const events = eventMode + ? Object.values(require(`../modules/contracts/${eventMode}`).events) + : undefined; const getUserGlobalStateHandler = async (req, res) => { try { From ed146b3655ef85df17246c080a6ab73c4fd4507c Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 16:42:30 +0900 Subject: [PATCH 126/261] Fix: invalid comparator for sort function --- src/lottery/services/publicNotice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 29deb5be..51d12600 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -36,7 +36,7 @@ const getTicketLeaderboardHandler = async (req, res) => { userId: user.userId.toString(), weight: user.ticket1Amount + 5 * user.ticket2Amount, })) - .sort((a, b) => a.weight > b.weight); + .sort((a, b) => -(a.weight - b.weight)); let rank = -1; const userId = login(req); From 54827372358abd2ce19a15365a05412bae0e5f02 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 17:28:41 +0900 Subject: [PATCH 127/261] Add: profileImageUrl field to the response of leaderboard endpoint --- src/lottery/routes/docs/publicNotice.js | 17 ++++++++++++++++- src/lottery/services/publicNotice.js | 5 +++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js index c2b702e3..827cd260 100644 --- a/src/lottery/routes/docs/publicNotice.js +++ b/src/lottery/routes/docs/publicNotice.js @@ -22,13 +22,28 @@ publicNoticeDocs[`${apiPrefix}/leaderboard`] = { description: "상위 20명만 포함된 리더보드", items: { type: "object", - required: ["nickname", "probability"], + required: ["ticket1Amount", "ticket2Amount", "probability"], properties: { nickname: { type: "string", description: "유저의 닉네임", example: "asdf", }, + profileImageUrl: { + type: "string", + description: "프로필 이미지 URL", + example: "IMAGE URL", + }, + ticket1Amount: { + type: "number", + description: "일반 티켓의 개수. 0 이상입니다.", + example: 10, + }, + ticket2Amount: { + type: "number", + description: "고급 티켓의 개수. 0 이상입니다.", + example: 10, + }, probability: { type: "number", description: "1등 당첨 확률", diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 51d12600..b2a683bc 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -34,6 +34,8 @@ const getTicketLeaderboardHandler = async (req, res) => { const sortedUsers = users .map((user) => ({ userId: user.userId.toString(), + ticket1Amount: user.ticket1Amount, + ticket2Amount: user.ticket2Amount, weight: user.ticket1Amount + 5 * user.ticket2Amount, })) .sort((a, b) => -(a.weight - b.weight)); @@ -52,6 +54,9 @@ const getTicketLeaderboardHandler = async (req, res) => { const userInfo = await userModel.findOne({ _id: user.userId }).lean(); return { nickname: userInfo?.nickname, + profileImageUrl: userInfo?.profileImageUrl, + ticket1Amount: user.ticket1Amount, + ticket2Amount: user.ticket2Amount, probability: user.weight / weightSum, }; }) From 5c41e8768f871df744a859806d854c07335ed715 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 18:25:22 +0900 Subject: [PATCH 128/261] Refactor: change the parameter of updateEventStatus function --- src/lottery/services/items.js | 20 ++++++++++---- src/lottery/services/publicNotice.js | 40 +++++++++++----------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index cc44fca9..63023eff 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -5,14 +5,17 @@ const { } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); -const updateEventStatus = async (userId, creditDelta, itemType) => +const updateEventStatus = async ( + userId, + { creditDelta = 0, ticket1Delta = 0, ticket2Delta = 0 } = {} +) => await eventStatusModel.updateOne( { userId }, { $inc: { creditAmount: creditDelta, - ticket1Amount: itemType === 1 ? 1 : 0, - ticket2Amount: itemType === 2 ? 1 : 0, + ticket1Amount: ticket1Delta, + ticket2Amount: ticket2Delta, }, } ); @@ -70,7 +73,10 @@ const getRandomItem = async (req, depth) => { .lean(); // 2단계: 유저 정보를 업데이트합니다. - await updateEventStatus(req.userOid, 0, randomItem.itemType); + await updateEventStatus(req.userOid, { + ticket1Delta: randomItem.itemType === 1 ? 1 : 0, + ticket2Delta: randomItem.itemType === 2 ? 1 : 0, + }); // 3단계: Transaction을 추가합니다. const transaction = new transactionModel({ @@ -144,7 +150,11 @@ const purchaseHandler = async (req, res) => { ); // 2단계: 유저 정보를 업데이트합니다. - await updateEventStatus(req.userOid, -item.price, item.itemType); + await updateEventStatus(req.userOid, { + creditDelta: -item.price, + ticket1Delta: item.itemType === 1 ? 1 : 0, + ticket2Delta: item.itemType === 2 ? 1 : 0, + }); // 3단계: Transaction을 추가합니다. const transaction = new transactionModel({ diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index b2a683bc..f58a683c 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -3,33 +3,23 @@ const { userModel } = require("../../modules/stores/mongo"); const logger = require("../../modules/logger"); const { isLogin, getLoginInfo } = require("../../modules/auths/login"); -const login = (req) => { - if (isLogin(req)) { - const { oid } = getLoginInfo(req); - return oid; - } else return null; -}; - const getTicketLeaderboardHandler = async (req, res) => { try { const users = await eventStatusModel - .find( - { - $or: [ - { - ticket1Amount: { - $gt: 0, - }, + .find({ + $or: [ + { + ticket1Amount: { + $gt: 0, }, - { - ticket2Amount: { - $gt: 0, - }, + }, + { + ticket2Amount: { + $gt: 0, }, - ], - }, - "userId ticket1Amount ticket2Amount" - ) + }, + ], + }) .lean(); const sortedUsers = users .map((user) => ({ @@ -40,15 +30,15 @@ const getTicketLeaderboardHandler = async (req, res) => { })) .sort((a, b) => -(a.weight - b.weight)); + const userId = isLogin(req) ? getLoginInfo(req).oid : null; let rank = -1; - const userId = login(req); + const weightSum = sortedUsers.reduce((before, user, index) => { - if (user.userId === userId) { + if (rank < 0 && user.userId === userId) { rank = index; } return before + user.weight; }, 0); - const leaderboard = await Promise.all( sortedUsers.slice(0, 20).map(async (user) => { const userInfo = await userModel.findOne({ _id: user.userId }).lean(); From ccd8ddf57095b1c038a9bed267e19cd5bb96a2fe Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 18:37:48 +0900 Subject: [PATCH 129/261] Add: occur error in leaderboard endpoint when failing to find user --- src/lottery/routes/docs/publicNotice.js | 8 +++++++- src/lottery/services/publicNotice.js | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js index 827cd260..cda93fa1 100644 --- a/src/lottery/routes/docs/publicNotice.js +++ b/src/lottery/routes/docs/publicNotice.js @@ -22,7 +22,13 @@ publicNoticeDocs[`${apiPrefix}/leaderboard`] = { description: "상위 20명만 포함된 리더보드", items: { type: "object", - required: ["ticket1Amount", "ticket2Amount", "probability"], + required: [ + "nickname", + "profileImageUrl", + "ticket1Amount", + "ticket2Amount", + "probability", + ], properties: { nickname: { type: "string", diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index f58a683c..a58083f9 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -39,18 +39,28 @@ const getTicketLeaderboardHandler = async (req, res) => { } return before + user.weight; }, 0); + const leaderboard = await Promise.all( sortedUsers.slice(0, 20).map(async (user) => { const userInfo = await userModel.findOne({ _id: user.userId }).lean(); + if (!userInfo) { + logger.error(`Fail to find user ${user.userId}`); + return null; + } + return { - nickname: userInfo?.nickname, - profileImageUrl: userInfo?.profileImageUrl, + nickname: userInfo.nickname, + profileImageUrl: userInfo.profileImageUrl, ticket1Amount: user.ticket1Amount, ticket2Amount: user.ticket2Amount, probability: user.weight / weightSum, }; }) ); + if (leaderboard.includes(null)) + return res + .status(500) + .json({ error: "PublicNotice/Leaderboard : internal server error" }); if (rank >= 0) res.json({ From ef05ce1cbb70230f68a7aabe0e35838dbd970c21 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 18:50:30 +0900 Subject: [PATCH 130/261] Refactor: remove unnecessary line breakings --- src/lottery/services/publicNotice.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index a58083f9..ac4b2b9d 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -7,18 +7,7 @@ const getTicketLeaderboardHandler = async (req, res) => { try { const users = await eventStatusModel .find({ - $or: [ - { - ticket1Amount: { - $gt: 0, - }, - }, - { - ticket2Amount: { - $gt: 0, - }, - }, - ], + $or: [{ ticket1Amount: { $gt: 0 } }, { ticket2Amount: { $gt: 0 } }], }) .lean(); const sortedUsers = users From b5a1f773e81803b23b75fa04cb9d50f58d343c73 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 21:17:02 +0900 Subject: [PATCH 131/261] Fix: rename event to quest --- src/lottery/index.js | 4 +- src/lottery/modules/contracts/2023fall.js | 80 +++++++++++------------ src/lottery/modules/events.js | 64 ------------------ src/lottery/modules/quests.js | 66 +++++++++++++++++++ src/lottery/modules/stores/mongo.js | 16 ++--- src/lottery/routes/docs/globalState.js | 28 ++++---- src/lottery/routes/docs/transactions.js | 6 +- src/lottery/services/globalState.js | 8 +-- src/services/auth.js | 2 +- src/services/auth.mobile.js | 2 +- src/services/rooms.js | 10 +-- src/services/users.js | 4 +- 12 files changed, 142 insertions(+), 148 deletions(-) delete mode 100644 src/lottery/modules/events.js create mode 100644 src/lottery/modules/quests.js diff --git a/src/lottery/index.js b/src/lottery/index.js index 1ecef91d..71f5cf94 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -1,7 +1,7 @@ const express = require("express"); const { eventStatusModel, - eventModel, + questModel, itemModel, transactionModel, } = require("./modules/stores/mongo"); @@ -24,7 +24,7 @@ lotteryRouter.use("/items", require("./routes/items")); const resources = [ eventStatusModel, - eventModel, + questModel, itemModel, transactionModel, ].map(buildResource()); diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 51fe1e1f..01b05186 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,7 +1,7 @@ -const { eventHandler } = require("../events"); +const { completeQuest } = require("../quests"); const mongoose = require("mongoose"); -const events = { +const quests = { firstLogin: { name: "이벤트 기간 첫 로그인", description: "", @@ -81,18 +81,18 @@ const events = { }, }; -for (const [id, event] of Object.entries(events)) { - event.id = id; +for (const [id, quest] of Object.entries(quests)) { + quest.id = id; } /** - * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 로그인할 때마다 호출해 주세요. * @usage auth/tryLogin, auth.mobile/tokenLoginHandler */ -const requestFirstLoginEvent = async (userId) => { - return await eventHandler(userId, events.firstLogin); +const completeFirstLoginQuest = async (userId) => { + return await completeQuest(userId, quests.firstLogin); }; /** @@ -103,103 +103,103 @@ const requestFirstLoginEvent = async (userId) => { * @description 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. * @usage rooms/commitPaymentHandler, rooms/settlementHandler */ -const requestPayingAndSendingEvent = async (roomObject) => { +const completePayingAndSendingQuest = async (roomObject) => { if (roomObject.part.length < 2) return null; if (roomObject.part.length > roomObject.settlementTotal) return null; return await Promise.all( roomObject.part.map( async (participant) => - await eventHandler(participant.user._id, events.payingAndSending) + await completeQuest(participant.user._id, quests.payingAndSending) ) ); }; /** - * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 방을 만들 때마다 호출해 주세요. * @usage rooms/createHandler */ -const requestFirstRoomCreation = async (userId) => { - return await eventHandler(userId, events.firstRoomCreation); +const completeFirstRoomCreationQuest = async (userId) => { + return await completeQuest(userId, quests.firstRoomCreation); }; -const requestRoomSharingEvent = async () => { +const completeRoomSharingQuest = async () => { // TODO }; /** - * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} * @description 정산 요청이 이루어질 때마다 호출해 주세요. * @usage rooms/commitPaymentHandler */ -const requestPayingEvent = async (userId, roomObject) => { +const completePayingQuest = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await eventHandler(userId, events.paying); + return await completeQuest(userId, quests.paying); }; /** - * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} * @description 송금이 이루어질 때마다 호출해 주세요. * @usage rooms/settlementHandler */ -const requestSendingEvent = async (userId, roomObject) => { +const completeSendingQuest = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await eventHandler(userId, events.sending); + return await completeQuest(userId, quests.sending); }; /** - * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 닉네임을 변경할 때마다 호출해 주세요. * @usage users/editNicknameHandler */ -const requestNicknameChangingEvent = async (userId) => { - return await eventHandler(userId, events.nicknameChaning); +const completeNicknameChangingQuest = async (userId) => { + return await completeQuest(userId, quests.nicknameChaning); }; /** - * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 계좌를 변경할 때마다 호출해 주세요. * @usage users/editAccountHandler */ -const requestAccountChangingEvent = async (userId) => { - return await eventHandler(userId, events.accountChanging); +const completeAccountChangingQuest = async (userId) => { + return await completeQuest(userId, quests.accountChanging); }; -const requestAdPushAgreementEvent = async () => { +const completeAdPushAgreementQuest = async () => { // TODO }; -const requestEventSharingOnInstagram = async () => { +const completeEventSharingOnInstagramQuest = async () => { // TODO }; -const requestPurchaseSharingOnInstagram = async () => { +const completePurchaseSharingOnInstagramQuest = async () => { // TODO }; module.exports = { - events, - requestFirstLoginEvent, - requestPayingAndSendingEvent, - requestFirstRoomCreation, - requestRoomSharingEvent, - requestPayingEvent, - requestSendingEvent, - requestNicknameChangingEvent, - requestAccountChangingEvent, - requestAdPushAgreementEvent, - requestEventSharingOnInstagram, - requestPurchaseSharingOnInstagram, + quests, + completeFirstLoginQuest, + completePayingAndSendingQuest, + completeFirstRoomCreationQuest, + completeRoomSharingQuest, + completePayingQuest, + completeSendingQuest, + completeNicknameChangingQuest, + completeAccountChangingQuest, + completeAdPushAgreementQuest, + completeEventSharingOnInstagramQuest, + completePurchaseSharingOnInstagramQuest, }; diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js deleted file mode 100644 index 7880d53a..00000000 --- a/src/lottery/modules/events.js +++ /dev/null @@ -1,64 +0,0 @@ -const { - eventStatusModel, - eventModel, - transactionModel, -} = require("./stores/mongo"); -const logger = require("../../modules/logger"); - -const eventHandler = async (userId, event) => { - try { - const eventStatus = await eventStatusModel.findOne({ userId }).lean(); - const eventCount = eventStatus.eventList.filter( - (achievedEventId) => achievedEventId === event.id - ).length; - if (eventCount >= event.maxCount) { - logger.info( - `User ${userId} already achieved ${event.id}Event ${eventCount} times` - ); - return null; - } - - const eventDoc = await eventModel.findOne({ id: event.id }).lean(); - if (eventDoc && eventDoc.isDisabled) { - logger.info(`User ${userId} failed to achieve disabled ${event.id}Event`); - return null; - } - - await eventStatusModel.updateOne( - { userId }, - { - $inc: { - creditAmount: event.rewardAmount, - }, - $push: { - eventList: event.id, - }, - } - ); - - const transaction = new transactionModel({ - type: "get", - amount: event.rewardAmount, - userId, - eventId: event.id, - comment: `${event.name} 달성 - ${event.rewardAmount}개 획득`, - }); - await transaction.save(); - - logger.info(`User ${userId} successfully achieved ${event.id}Event`); - return { - event, - transactionId: transaction._id, - }; - } catch (err) { - logger.error(err); - logger.error( - `User ${userId} failed to achieve ${event.id}Event due to exception` - ); - return null; - } -}; - -module.exports = { - eventHandler, -}; diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js new file mode 100644 index 00000000..33ec9d5f --- /dev/null +++ b/src/lottery/modules/quests.js @@ -0,0 +1,66 @@ +const { + eventStatusModel, + questModel, + transactionModel, +} = require("./stores/mongo"); +const logger = require("../../modules/logger"); + +const completeQuest = async (userId, quest) => { + try { + const eventStatus = await eventStatusModel.findOne({ userId }).lean(); + const questCount = eventStatus.completedQuests.filter( + (completedQuestId) => completedQuestId === quest.id + ).length; + if (questCount >= quest.maxCount) { + logger.info( + `User ${userId} already completed ${quest.id}Quest ${questCount} times` + ); + return null; + } + + const questDoc = await questModel.findOne({ id: quest.id }).lean(); + if (questDoc && questDoc.isDisabled) { + logger.info( + `User ${userId} failed to complete disabled ${quest.id}Quest` + ); + return null; + } + + await eventStatusModel.updateOne( + { userId }, + { + $inc: { + creditAmount: quest.rewardAmount, + }, + $push: { + completedQuests: quest.id, + }, + } + ); + + const transaction = new transactionModel({ + type: "get", + amount: quest.rewardAmount, + userId, + questId: quest.id, + comment: `${quest.name} 달성 - ${quest.rewardAmount}개 획득`, + }); + await transaction.save(); + + logger.info(`User ${userId} successfully completed ${quest.id}Quest`); + return { + quest, + transactionId: transaction._id, + }; + } catch (err) { + logger.error(err); + logger.error( + `User ${userId} failed to complete ${quest.id}Quest due to exception` + ); + return null; + } +}; + +module.exports = { + completeQuest, +}; diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 13ce77eb..2be710b8 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -12,7 +12,7 @@ const eventStatusSchema = Schema({ ref: "User", required: true, }, - eventList: { + completedQuests: { type: [String], default: [], }, @@ -24,7 +24,7 @@ const eventStatusSchema = Schema({ }, }); -const eventSchema = Schema({ +const questSchema = Schema({ id: { type: String, required: true, @@ -34,14 +34,6 @@ const eventSchema = Schema({ type: Boolean, required: true, }, - imageUrl: { - type: String, - required: true, - }, - description: { - type: String, - required: true, - }, }); const itemSchema = Schema({ @@ -107,7 +99,7 @@ const transactionSchema = Schema({ ref: "User", required: true, }, - eventId: { + questId: { type: String, }, item: { @@ -130,7 +122,7 @@ transactionSchema.set("timestamps", { module.exports = { eventStatusModel: mongoose.model("EventStatus", eventStatusSchema), - eventModel: mongoose.model("Event", eventSchema), + questModel: mongoose.model("Quest", questSchema), itemModel: mongoose.model("Item", itemSchema), transactionModel: mongoose.model("Transaction", transactionSchema), }; diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 01d65fa2..296e0de9 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -7,7 +7,7 @@ globalStateDocs[`${apiPrefix}/`] = { tags: [`${apiPrefix}`], summary: "Frontend에서 Global state로 관리하는 정보 반환", description: - "유저의 재화 개수, 이벤트 달성 상태, 추첨권 개수 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성하며, 유일한 생성 지점입니다.", + "유저의 재화 개수, 퀘스트 달성 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성하며, 유일한 생성 지점입니다.", responses: { 200: { description: "", @@ -17,10 +17,10 @@ globalStateDocs[`${apiPrefix}/`] = { type: "object", required: [ "creditAmount", - "eventStatus", + "completedQuests", "ticket1Amount", "ticket2Amount", - "events", + "quests", ], properties: { creditAmount: { @@ -28,14 +28,14 @@ globalStateDocs[`${apiPrefix}/`] = { description: "재화 개수. 0 이상입니다.", example: 10000, }, - eventStatus: { + completedQuests: { type: "array", description: - "유저가 달성한 이벤트의 배열. 여러 번 달성할 수 있는 이벤트의 경우 배열 내에 같은 이벤트가 여러 번 포함될 수 있습니다.", + "유저가 달성한 퀘스트의 배열. 여러 번 달성할 수 있는 퀘스트의 경우 배열 내에 같은 퀘스트가 여러 번 포함됩니다.", items: { type: "string", - description: "Event의 ObjectId", - example: "OBJECT ID", + description: "Quest의 Id", + example: "QUEST ID", }, }, ticket1Amount: { @@ -48,9 +48,9 @@ globalStateDocs[`${apiPrefix}/`] = { description: "고급 티켓의 개수. 0 이상입니다.", example: 10, }, - events: { + quests: { type: "array", - description: "Event의 배열", + description: "Quest의 배열", items: { type: "object", required: [ @@ -64,17 +64,17 @@ globalStateDocs[`${apiPrefix}/`] = { properties: { id: { type: "string", - description: "Event의 Id", - example: "EVENT ID", + description: "Quest의 Id", + example: "QUEST ID", }, name: { type: "string", - description: "이벤트의 이름", - example: "최초 로그인 이벤트", + description: "퀘스트의 이름", + example: "최초 로그인 퀘스트", }, description: { type: "string", - description: "이벤트의 설명", + description: "퀘스트의 설명", example: "처음으로 이벤트 기간 중 Taxi에 로그인하면 송편을 드립니다.", }, diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index 936b925a..42545722 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -39,11 +39,11 @@ transactionsDocs[`${apiPrefix}/`] = { description: "재화의 변화량의 절댓값", example: 50, }, - eventId: { + questId: { type: "string", description: - "Transaction과 관련된 이벤트의 Id. 이벤트와 관련된 Transaction인 경우에만 포함됩니다.", - example: "EVENT ID", + "Transaction과 관련된 퀘스트의 Id. 퀘스트와 관련된 Transaction인 경우에만 포함됩니다.", + example: "QUEST ID", }, item: { $ref: "#/components/schemas/relatedItem", diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index f9680bd2..79c391fd 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -5,8 +5,8 @@ const { const logger = require("../../modules/logger"); const { eventMode } = require("../../../loadenv"); -const events = eventMode - ? Object.values(require(`../modules/contracts/${eventMode}`).events) +const quests = eventMode + ? Object.values(require(`../modules/contracts/${eventMode}`).quests) : undefined; const getUserGlobalStateHandler = async (req, res) => { @@ -44,10 +44,10 @@ const getUserGlobalStateHandler = async (req, res) => { res.json({ creditAmount: eventStatus.creditAmount, - eventStatus: eventStatus.eventList.map((id) => id.toString()), + completedQuests: eventStatus.completedQuests, ticket1Amount, ticket2Amount, - events, + quests, }); } catch (err) { logger.error(err); diff --git a/src/services/auth.js b/src/services/auth.js index 7b0851f1..6e6b09ca 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -96,7 +96,7 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { login(req, userData.sid, user.id, user._id, user.name); // 이벤트 코드입니다. - await contracts?.requestFirstLoginEvent(user._id); + await contracts?.completeFirstLoginQuest(user._id); res.redirect(new URL(redirectPath, redirectOrigin).href); } catch (err) { diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index e2303961..90c9a337 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -41,7 +41,7 @@ const tokenLoginHandler = async (req, res) => { req.session.deviceToken = deviceToken; // 이벤트 코드입니다. - await contracts?.requestFirstLoginEvent(user._id); + await contracts?.completeFirstLoginQuest(user._id); return res.status(200).json({ message: "success" }); } catch (e) { diff --git a/src/services/rooms.js b/src/services/rooms.js index 08f0776e..eb158278 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -87,7 +87,7 @@ const createHandler = async (req, res) => { const roomObjectFormated = formatSettlement(roomObject); // 이벤트 코드입니다. - await contracts?.requestFirstRoomCreation(user._id); + await contracts?.completeFirstRoomCreationQuest(user._id); return res.send(roomObjectFormated); } catch (err) { @@ -492,8 +492,8 @@ const commitPaymentHandler = async (req, res) => { }); // 이벤트 코드입니다. - await contracts?.requestPayingEvent(user._id, roomObject); - await contracts?.requestPayingAndSendingEvent(roomObject); + await contracts?.completePayingQuest(user._id, roomObject); + await contracts?.completePayingAndSendingQuest(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); @@ -562,8 +562,8 @@ const settlementHandler = async (req, res) => { }); // 이벤트 코드입니다. - await contracts?.requestSendingEvent(user._id, roomObject); - await contracts?.requestPayingAndSendingEvent(roomObject); + await contracts?.completeSendingQuest(user._id, roomObject); + await contracts?.completePayingAndSendingQuest(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); diff --git a/src/services/users.js b/src/services/users.js index d37bf502..7eada946 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -46,7 +46,7 @@ const editNicknameHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await contracts?.requestNicknameChangingEvent(req.userOid); + await contracts?.completeNicknameChangingQuest(req.userOid); res.status(200).send("User/editNickname : edit user nickname successful"); } else { @@ -68,7 +68,7 @@ const editAccountHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await contracts?.requestAccountChangingEvent(req.userOid); + await contracts?.completeAccountChangingQuest(req.userOid); res.status(200).send("User/editAccount : edit user account successful"); } else { From c0d6649bdf31fc3778d6de974cef9985b1276ce1 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 21:21:56 +0900 Subject: [PATCH 132/261] Refactor: rename doneat to createAt in transactionSchema --- src/lottery/modules/stores/mongo.js | 2 +- src/lottery/routes/docs/transactions.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 2be710b8..53db8155 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -116,7 +116,7 @@ const transactionSchema = Schema({ }, }); transactionSchema.set("timestamps", { - createdAt: "doneat", + createdAt: "createAt", updatedAt: false, }); diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index 42545722..60e7435b 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -21,7 +21,7 @@ transactionsDocs[`${apiPrefix}/`] = { description: "유저의 재화 입출금 기록의 배열", items: { type: "object", - required: ["_id", "type", "amount", "comment", "doneat"], + required: ["_id", "type", "amount", "comment", "createAt"], properties: { _id: { type: "string", @@ -53,7 +53,7 @@ transactionsDocs[`${apiPrefix}/`] = { description: "입출금 내역에 대한 설명", example: "랜덤 상자 구입 - 50개 차감", }, - doneat: { + createAt: { type: "string", description: "입출금이 일어난 시각", example: "2023-01-01 00:00:00", From 5c2ac3c4587fc4ae84ef3b58042c9674796920fc Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 21:35:45 +0900 Subject: [PATCH 133/261] Docs: add description about completeQuest function --- src/lottery/modules/contracts/2023fall.js | 19 +++++++++++++------ src/lottery/modules/quests.js | 11 +++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 01b05186..935d0a1e 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -86,7 +86,8 @@ for (const [id, quest] of Object.entries(quests)) { } /** - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. + * firstLogin 퀘스트의 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} * @description 로그인할 때마다 호출해 주세요. * @usage auth/tryLogin, auth.mobile/tokenLoginHandler @@ -96,6 +97,7 @@ const completeFirstLoginQuest = async (userId) => { }; /** + * payingAndSending 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이거나, 모든 참가자가 정산 또는 송금을 완료하지 않았다면 요청하지 않습니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @param {number} roomObject.settlementTotal - 정산 또는 송금이 완료된 참여자 수입니다. @@ -116,7 +118,8 @@ const completePayingAndSendingQuest = async (roomObject) => { }; /** - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. + * firstRoomCreation 퀘스트의 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} * @description 방을 만들 때마다 호출해 주세요. * @usage rooms/createHandler @@ -130,7 +133,8 @@ const completeRoomSharingQuest = async () => { }; /** - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. + * paying 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} @@ -144,7 +148,8 @@ const completePayingQuest = async (userId, roomObject) => { }; /** - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. + * sending 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} @@ -158,7 +163,8 @@ const completeSendingQuest = async (userId, roomObject) => { }; /** - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. + * nicknameChaning 퀘스트의 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} * @description 닉네임을 변경할 때마다 호출해 주세요. * @usage users/editNicknameHandler @@ -168,7 +174,8 @@ const completeNicknameChangingQuest = async (userId) => { }; /** - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. + * accountChanging 퀘스트의 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} * @description 계좌를 변경할 때마다 호출해 주세요. * @usage users/editAccountHandler diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index 33ec9d5f..b1ebf390 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -4,7 +4,18 @@ const { transactionModel, } = require("./stores/mongo"); const logger = require("../../modules/logger"); +const mongoose = require("mongoose"); +/** + * 퀘스트 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {Object} quest - 퀘스트의 정보입니다. + * @param {string} quest.id - 퀘스트의 Id입니다. + * @param {string} quest.name - 퀘스트의 이름입니다. + * @param {number} quest.rewardAmount - 퀘스트의 완료 보상입니다. + * @param {number} quest.maxCount - 퀘스트의 최대 완료 가능 횟수입니다. + * @returns {Object|null} 성공한 경우 Object를, 실패한 경우 null을 반환합니다. 이미 최대 완료 횟수에 도달했거나, 퀘스트가 원격으로 비활성화 된 경우에도 실패로 처리됩니다. + */ const completeQuest = async (userId, quest) => { try { const eventStatus = await eventStatusModel.findOne({ userId }).lean(); From e514be4fd6cbc9ad875b5e5ef49aa91dc1b1cd5c Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 22:13:35 +0900 Subject: [PATCH 134/261] Add: check current time in some functions --- src/lottery/modules/contracts/2023fall.js | 7 +++++++ src/lottery/modules/quests.js | 13 ++++++++++++ src/lottery/services/items.js | 24 +++++++++++++++++------ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 935d0a1e..9332937f 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,6 +1,7 @@ const { completeQuest } = require("../quests"); const mongoose = require("mongoose"); +/** 전체 퀘스트 목록입니다. */ const quests = { firstLogin: { name: "이벤트 기간 첫 로그인", @@ -85,6 +86,11 @@ for (const [id, quest] of Object.entries(quests)) { quest.id = id; } +const eventPeriod = { + start: new Date("2023-09-25T00:00:00+09:00"), // Inclusive + end: new Date("2023-10-10T00:00:00+09:00"), // Exclusive +}; + /** * firstLogin 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. @@ -198,6 +204,7 @@ const completePurchaseSharingOnInstagramQuest = async () => { module.exports = { quests, + eventPeriod, completeFirstLoginQuest, completePayingAndSendingQuest, completeFirstRoomCreationQuest, diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index b1ebf390..a67881e4 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -6,6 +6,11 @@ const { const logger = require("../../modules/logger"); const mongoose = require("mongoose"); +const { eventMode } = require("../../../loadenv"); +const eventPeriod = eventMode + ? require(`../modules/contracts/${eventMode}`).eventPeriod + : undefined; + /** * 퀘스트 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. @@ -18,6 +23,14 @@ const mongoose = require("mongoose"); */ const completeQuest = async (userId, quest) => { try { + const now = Date.now(); + if (now >= eventPeriod.end || now < eventPeriod.start) { + logger.info( + `User ${userId} failed to complete auto-disabled ${quest.id}Quest` + ); + return null; + } + const eventStatus = await eventStatusModel.findOne({ userId }).lean(); const questCount = eventStatus.completedQuests.filter( (completedQuestId) => completedQuestId === quest.id diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index d47c2479..78cc79db 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -2,8 +2,16 @@ const { itemModel, transactionModel } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); const { useUserCreditAmount } = require("../modules/credit"); +const { eventMode } = require("../../../loadenv"); +const eventPeriod = eventMode + ? require(`../modules/contracts/${eventMode}`).eventPeriod + : undefined; + const getRandomItem = async (req, depth) => { - if (depth >= 10) return null; + if (depth >= 10) { + logger.error(`User ${req.userOid} failed to open random box`); + return null; + } const items = await itemModel .find({ @@ -22,10 +30,9 @@ const getRandomItem = async (req, depth) => { .join(","); logger.info( - `[RandomBox] getRandomItem(depth=${depth}) is called by the user(id=${req.userOid}).` - ); - logger.info( - `[RandomBox] randomItems of the user(id=${req.userOid}) is [${dumpRandomItems}].` + `User ${req.userOid}'s ${ + depth + 1 + }th random box probability is: [${dumpRandomItems}]` ); if (randomItems.length === 0) return null; @@ -65,8 +72,9 @@ const getRandomItem = async (req, depth) => { return newRandomItem; } catch (err) { + logger.error(err); logger.warn( - `[RandomBox] getRandomItem(depth=${depth}) by the user(id=${req.userOid}) failed due to ${err}.` + `User ${req.userOid}'s ${depth + 1}th random box failed due to exception` ); return await getRandomItem(req, depth + 1); @@ -87,6 +95,10 @@ const listHandler = async (_, res) => { const purchaseHandler = async (req, res) => { try { + const now = Date.now(); + if (now >= eventPeriod.end || now < eventPeriod.start) + return res.status(400).json({ error: "Items/Purchase : out of date" }); + const { itemId } = req.params; const item = await itemModel.findOne({ _id: itemId }).lean(); if (!item) From 54457fd4664b958a15e1d03fca7145fc922baa5b Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 23:09:26 +0900 Subject: [PATCH 135/261] Fix: bug related to firstLoginEvent --- src/lottery/modules/contracts/2023fall.js | 18 +++++++++++------- src/lottery/modules/quests.js | 17 ++++++++++------- src/lottery/routes/docs/globalState.js | 2 +- src/lottery/services/globalState.js | 2 -- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 9332937f..c8f4774a 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -99,7 +99,7 @@ const eventPeriod = { * @usage auth/tryLogin, auth.mobile/tokenLoginHandler */ const completeFirstLoginQuest = async (userId) => { - return await completeQuest(userId, quests.firstLogin); + return await completeQuest(userId, eventPeriod, quests.firstLogin); }; /** @@ -118,7 +118,11 @@ const completePayingAndSendingQuest = async (roomObject) => { return await Promise.all( roomObject.part.map( async (participant) => - await completeQuest(participant.user._id, quests.payingAndSending) + await completeQuest( + participant.user._id, + eventPeriod, + quests.payingAndSending + ) ) ); }; @@ -131,7 +135,7 @@ const completePayingAndSendingQuest = async (roomObject) => { * @usage rooms/createHandler */ const completeFirstRoomCreationQuest = async (userId) => { - return await completeQuest(userId, quests.firstRoomCreation); + return await completeQuest(userId, eventPeriod, quests.firstRoomCreation); }; const completeRoomSharingQuest = async () => { @@ -150,7 +154,7 @@ const completeRoomSharingQuest = async () => { const completePayingQuest = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await completeQuest(userId, quests.paying); + return await completeQuest(userId, eventPeriod, quests.paying); }; /** @@ -165,7 +169,7 @@ const completePayingQuest = async (userId, roomObject) => { const completeSendingQuest = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await completeQuest(userId, quests.sending); + return await completeQuest(userId, eventPeriod, quests.sending); }; /** @@ -176,7 +180,7 @@ const completeSendingQuest = async (userId, roomObject) => { * @usage users/editNicknameHandler */ const completeNicknameChangingQuest = async (userId) => { - return await completeQuest(userId, quests.nicknameChaning); + return await completeQuest(userId, eventPeriod, quests.nicknameChaning); }; /** @@ -187,7 +191,7 @@ const completeNicknameChangingQuest = async (userId) => { * @usage users/editAccountHandler */ const completeAccountChangingQuest = async (userId) => { - return await completeQuest(userId, quests.accountChanging); + return await completeQuest(userId, eventPeriod, quests.accountChanging); }; const completeAdPushAgreementQuest = async () => { diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index a67881e4..3b98811d 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -6,14 +6,12 @@ const { const logger = require("../../modules/logger"); const mongoose = require("mongoose"); -const { eventMode } = require("../../../loadenv"); -const eventPeriod = eventMode - ? require(`../modules/contracts/${eventMode}`).eventPeriod - : undefined; - /** * 퀘스트 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {Object} eventPeriod - 이벤트의 기간입니다. + * @param {Date} eventPeriod.start - 이벤트의 시작 시각(Inclusive)입니다. + * @param {Date} eventPeriod.end - 이벤트의 종료 시각(Exclusive)입니다. * @param {Object} quest - 퀘스트의 정보입니다. * @param {string} quest.id - 퀘스트의 Id입니다. * @param {string} quest.name - 퀘스트의 이름입니다. @@ -21,7 +19,7 @@ const eventPeriod = eventMode * @param {number} quest.maxCount - 퀘스트의 최대 완료 가능 횟수입니다. * @returns {Object|null} 성공한 경우 Object를, 실패한 경우 null을 반환합니다. 이미 최대 완료 횟수에 도달했거나, 퀘스트가 원격으로 비활성화 된 경우에도 실패로 처리됩니다. */ -const completeQuest = async (userId, quest) => { +const completeQuest = async (userId, eventPeriod, quest) => { try { const now = Date.now(); if (now >= eventPeriod.end || now < eventPeriod.start) { @@ -31,7 +29,12 @@ const completeQuest = async (userId, quest) => { return null; } - const eventStatus = await eventStatusModel.findOne({ userId }).lean(); + let eventStatus = await eventStatusModel.findOne({ userId }).lean(); + if (!eventStatus) { + eventStatus = new eventStatusModel({ userId }); + await eventStatus.save(); + } + const questCount = eventStatus.completedQuests.filter( (completedQuestId) => completedQuestId === quest.id ).length; diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 296e0de9..f05630db 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -7,7 +7,7 @@ globalStateDocs[`${apiPrefix}/`] = { tags: [`${apiPrefix}`], summary: "Frontend에서 Global state로 관리하는 정보 반환", description: - "유저의 재화 개수, 퀘스트 달성 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성하며, 유일한 생성 지점입니다.", + "유저의 재화 개수, 퀘스트 달성 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성합니다.", responses: { 200: { description: "", diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 79c391fd..7f801cb8 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -15,8 +15,6 @@ const getUserGlobalStateHandler = async (req, res) => { .findOne({ userId: req.userOid }) .lean(); if (!eventStatus) { - // User마다 EventStatus를 가져야 하고, 현재 Taxi에는 회원 탈퇴 시스템이 없으므로, EventStatus가 없으면 새롭게 생성하도록 구현합니다. - // EventStatus의 생성은 이곳에서만 이루어집니다!! eventStatus = new eventStatusModel({ userId: req.userOid, }); From 39816d20d408b3493b8a9a889382bf06f78d4896 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 23:30:33 +0900 Subject: [PATCH 136/261] Add: buildQuests function in modules/quests.js --- src/lottery/modules/contracts/2023fall.js | 20 ++++---------------- src/lottery/modules/quests.js | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index c8f4774a..0121c3c1 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,14 +1,13 @@ -const { completeQuest } = require("../quests"); +const { buildQuests, completeQuest } = require("../quests"); const mongoose = require("mongoose"); /** 전체 퀘스트 목록입니다. */ -const quests = { +const quests = buildQuests({ firstLogin: { name: "이벤트 기간 첫 로그인", description: "", imageUrl: "", rewardAmount: 150, - maxCount: 1, }, payingAndSending: { name: "2명 이상 탑승한 방에서 정산/송금 완료", @@ -22,14 +21,12 @@ const quests = { description: "", imageUrl: "", rewardAmount: 50, - maxCount: 1, }, roomSharing: { name: "방 공유하기", description: "", imageUrl: "", rewardAmount: 50, - maxCount: 1, }, paying: { name: "2명 이상 탑승한 방에서 정산하기", @@ -50,44 +47,35 @@ const quests = { description: "", imageUrl: "", rewardAmount: 50, - maxCount: 1, }, accountChanging: { name: "계좌 등록 또는 변경", description: "", imageUrl: "", rewardAmount: 50, - maxCount: 1, }, adPushAgreement: { name: "광고성 푸시 알림 수신 동의", description: "", imageUrl: "", rewardAmount: 50, - maxCount: 1, }, eventSharingOnInstagram: { name: "이벤트 인스타그램 스토리에 공유", description: "", imageUrl: "", rewardAmount: 100, - maxCount: 1, }, purchaseSharingOnInstagram: { name: "아이템 구매 후 인스타그램 스토리에 공유", description: "", imageUrl: "", rewardAmount: 100, - maxCount: 1, }, -}; - -for (const [id, quest] of Object.entries(quests)) { - quest.id = id; -} +}); const eventPeriod = { - start: new Date("2023-09-25T00:00:00+09:00"), // Inclusive + start: new Date("2023-09-10T00:00:00+09:00"), // Inclusive end: new Date("2023-10-10T00:00:00+09:00"), // Exclusive }; diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index 3b98811d..3303fa05 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -6,6 +6,27 @@ const { const logger = require("../../modules/logger"); const mongoose = require("mongoose"); +const requiredQuestFields = ["name", "description", "imageUrl", "rewardAmount"]; +const buildQuests = (quests) => { + for (const [id, quest] of Object.entries(quests)) { + const hasError = requiredQuestFields.reduce((before, field) => { + if (quest[field] !== undefined) return before; + + logger.error(`There is no ${field} field in ${id}Quest`); + return true; + }, false); + if (hasError) return null; + + quest.id = id; + + if (!quest.maxCount) { + quest.maxCount = 1; + } + } + + return quests; +}; + /** * 퀘스트 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. @@ -89,5 +110,6 @@ const completeQuest = async (userId, eventPeriod, quest) => { }; module.exports = { + buildQuests, completeQuest, }; From 5f5b10345b755c7c9c7ab037a62e4cc641db4f65 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 17 Sep 2023 17:28:30 +0900 Subject: [PATCH 137/261] Fix: accountChangingQuest completion condition --- src/lottery/modules/contracts/2023fall.js | 7 ++++--- src/services/auth.js | 6 ------ src/services/auth.mobile.js | 6 ------ src/services/users.js | 2 +- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 0121c3c1..4e4f9846 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -83,8 +83,6 @@ const eventPeriod = { * firstLogin 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} - * @description 로그인할 때마다 호출해 주세요. - * @usage auth/tryLogin, auth.mobile/tokenLoginHandler */ const completeFirstLoginQuest = async (userId) => { return await completeQuest(userId, eventPeriod, quests.firstLogin); @@ -174,11 +172,14 @@ const completeNicknameChangingQuest = async (userId) => { /** * accountChanging 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {string} newAccount - 변경된 계좌입니다. * @returns {Promise} * @description 계좌를 변경할 때마다 호출해 주세요. * @usage users/editAccountHandler */ -const completeAccountChangingQuest = async (userId) => { +const completeAccountChangingQuest = async (userId, newAccount) => { + if (newAccount === "") return null; + return await completeQuest(userId, eventPeriod, quests.accountChanging); }; diff --git a/src/services/auth.js b/src/services/auth.js index 6e6b09ca..83b598de 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -16,9 +16,6 @@ const { const jwt = require("../modules/auths/jwt"); const logger = require("../modules/logger"); -// 이벤트 코드입니다. -const { contracts } = require("../lottery"); - // SPARCS SSO const Client = require("../modules/auths/sparcssso"); const client = new Client(sparcsssoEnv?.id, sparcsssoEnv?.key); @@ -95,9 +92,6 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { login(req, userData.sid, user.id, user._id, user.name); - // 이벤트 코드입니다. - await contracts?.completeFirstLoginQuest(user._id); - res.redirect(new URL(redirectPath, redirectOrigin).href); } catch (err) { logger.error(err); diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index 90c9a337..0e537b33 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -10,9 +10,6 @@ const logger = require("../modules/logger"); const { TOKEN_EXPIRED, TOKEN_INVALID } = require("../../loadenv").jwt; -// 이벤트 코드입니다. -const { contracts } = require("../lottery"); - const tokenLoginHandler = async (req, res) => { const { accessToken, deviceToken } = req.query; try { @@ -40,9 +37,6 @@ const tokenLoginHandler = async (req, res) => { req.session.isApp = true; req.session.deviceToken = deviceToken; - // 이벤트 코드입니다. - await contracts?.completeFirstLoginQuest(user._id); - return res.status(200).json({ message: "success" }); } catch (e) { logger.error(e); diff --git a/src/services/users.js b/src/services/users.js index 7eada946..b7b47e41 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -68,7 +68,7 @@ const editAccountHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await contracts?.completeAccountChangingQuest(req.userOid); + await contracts?.completeAccountChangingQuest(req.userOid, newAccount); res.status(200).send("User/editAccount : edit user account successful"); } else { From 23ed5d9fbc3eab9225e90b1d4842f71b93cce164 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 18 Sep 2023 01:10:35 +0900 Subject: [PATCH 138/261] Add: ticket1 can be the reward of quests --- src/lottery/modules/contracts/2023fall.js | 26 ++++---- src/lottery/modules/credit.js | 24 ------- src/lottery/modules/quests.js | 77 ++++++++++++++++++----- src/lottery/services/items.js | 4 +- 4 files changed, 78 insertions(+), 53 deletions(-) delete mode 100644 src/lottery/modules/credit.js diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 4e4f9846..f5994d51 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -7,75 +7,77 @@ const quests = buildQuests({ name: "이벤트 기간 첫 로그인", description: "", imageUrl: "", - rewardAmount: 150, + reward: { + ticket1: 1, + }, }, payingAndSending: { name: "2명 이상 탑승한 방에서 정산/송금 완료", description: "", imageUrl: "", - rewardAmount: 300, + reward: 300, maxCount: 3, }, firstRoomCreation: { name: "첫 방 개설", description: "", imageUrl: "", - rewardAmount: 50, + reward: 50, }, roomSharing: { name: "방 공유하기", description: "", imageUrl: "", - rewardAmount: 50, + reward: 50, }, paying: { name: "2명 이상 탑승한 방에서 정산하기", description: "", imageUrl: "", - rewardAmount: 100, + reward: 100, maxCount: 3, }, sending: { name: "2명 이상 탑승한 방에서 송금하기", description: "", imageUrl: "", - rewardAmount: 50, + reward: 50, maxCount: 3, }, nicknameChaning: { name: "닉네임 변경", description: "", imageUrl: "", - rewardAmount: 50, + reward: 50, }, accountChanging: { name: "계좌 등록 또는 변경", description: "", imageUrl: "", - rewardAmount: 50, + reward: 50, }, adPushAgreement: { name: "광고성 푸시 알림 수신 동의", description: "", imageUrl: "", - rewardAmount: 50, + reward: 50, }, eventSharingOnInstagram: { name: "이벤트 인스타그램 스토리에 공유", description: "", imageUrl: "", - rewardAmount: 100, + reward: 100, }, purchaseSharingOnInstagram: { name: "아이템 구매 후 인스타그램 스토리에 공유", description: "", imageUrl: "", - rewardAmount: 100, + reward: 100, }, }); const eventPeriod = { - start: new Date("2023-09-10T00:00:00+09:00"), // Inclusive + start: new Date("2023-09-25T00:00:00+09:00"), // Inclusive end: new Date("2023-10-10T00:00:00+09:00"), // Exclusive }; diff --git a/src/lottery/modules/credit.js b/src/lottery/modules/credit.js deleted file mode 100644 index 2349d0c4..00000000 --- a/src/lottery/modules/credit.js +++ /dev/null @@ -1,24 +0,0 @@ -const { eventStatusModel } = require("../modules/stores/mongo"); - -const useUserCreditAmount = async (userId) => { - const eventStatus = await eventStatusModel.findOne({ userId }).lean(); - if (!eventStatus) return null; - - return { - amount: eventStatus.creditAmount, - update: async (delta) => { - await eventStatusModel.updateOne( - { _id: eventStatus._id }, - { - $inc: { - creditAmount: delta, - }, - } - ); - }, - }; -}; - -module.exports = { - useUserCreditAmount, -}; diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index 3303fa05..ebc0df05 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -1,14 +1,16 @@ const { eventStatusModel, questModel, + itemModel, transactionModel, } = require("./stores/mongo"); const logger = require("../../modules/logger"); const mongoose = require("mongoose"); -const requiredQuestFields = ["name", "description", "imageUrl", "rewardAmount"]; +const requiredQuestFields = ["name", "description", "imageUrl", "reward"]; const buildQuests = (quests) => { for (const [id, quest] of Object.entries(quests)) { + // quest에 필수 필드가 모두 포함되어 있는지 확인합니다. const hasError = requiredQuestFields.reduce((before, field) => { if (quest[field] !== undefined) return before; @@ -17,11 +19,23 @@ const buildQuests = (quests) => { }, false); if (hasError) return null; + // quest.id 필드를 설정합니다. quest.id = id; - if (!quest.maxCount) { - quest.maxCount = 1; + // quest.reward가 number인 경우, object로 변환합니다. + if (typeof quest.reward === "number") { + const credit = quest.reward; + quest.reward = { + credit, + }; } + + // quest.reward에 누락된 필드가 있는 경우, 기본값(0)으로 설정합니다. + quest.reward.credit = quest.reward.credit || 0; + quest.reward.ticket1 = quest.reward.ticket1 || 0; + + // quest.maxCount가 없는 경우, 기본값(1)으로 설정합니다. + quest.maxCount = quest.maxCount || 1; } return quests; @@ -36,12 +50,15 @@ const buildQuests = (quests) => { * @param {Object} quest - 퀘스트의 정보입니다. * @param {string} quest.id - 퀘스트의 Id입니다. * @param {string} quest.name - 퀘스트의 이름입니다. - * @param {number} quest.rewardAmount - 퀘스트의 완료 보상입니다. + * @param {Object} quest.reward - 퀘스트의 완료 보상입니다. + * @param {number} quest.reward.credit - 퀘스트의 완료 보상 중 재화의 양입니다. + * @param {number} quest.reward.ticket1 - 퀘스트의 완료 보상 중 일반 티켓의 개수입니다. * @param {number} quest.maxCount - 퀘스트의 최대 완료 가능 횟수입니다. * @returns {Object|null} 성공한 경우 Object를, 실패한 경우 null을 반환합니다. 이미 최대 완료 횟수에 도달했거나, 퀘스트가 원격으로 비활성화 된 경우에도 실패로 처리됩니다. */ const completeQuest = async (userId, eventPeriod, quest) => { try { + // 1단계: 이벤트 기간인지 확인합니다. const now = Date.now(); if (now >= eventPeriod.end || now < eventPeriod.start) { logger.info( @@ -50,12 +67,14 @@ const completeQuest = async (userId, eventPeriod, quest) => { return null; } + // 2단계: 유저의 EventStatus를 가져옵니다. 없으면 새롭게 생성합니다. let eventStatus = await eventStatusModel.findOne({ userId }).lean(); if (!eventStatus) { eventStatus = new eventStatusModel({ userId }); await eventStatus.save(); } + // 3단계: 유저의 퀘스트 달성 횟수를 확인합니다. const questCount = eventStatus.completedQuests.filter( (completedQuestId) => completedQuestId === quest.id ).length; @@ -66,19 +85,28 @@ const completeQuest = async (userId, eventPeriod, quest) => { return null; } + // 4단계: 원격으로 비활성화된 퀘스트인지 확인합니다. + // 비활성화된 퀘스트만 DB에 저장할 것이기 때문에, questDoc이 null이어도 오류를 발생시키면 안됩니다. const questDoc = await questModel.findOne({ id: quest.id }).lean(); - if (questDoc && questDoc.isDisabled) { + if (questDoc?.isDisabled) { logger.info( `User ${userId} failed to complete disabled ${quest.id}Quest` ); return null; } + // 5단계: 완료 보상 중 티켓이 있는 경우, 티켓 정보를 가져옵니다. + const ticket1 = + quest.reward.ticket1 && (await itemModel.findOne({ itemType: 1 }).lean()); + if (quest.reward.ticket1 && !ticket1) throw "Fail to find ticket1"; + + // 6단계: 유저의 EventStatus를 업데이트합니다. await eventStatusModel.updateOne( { userId }, { $inc: { - creditAmount: quest.rewardAmount, + creditAmount: quest.reward.credit, + ticket1Amount: quest.reward.ticket1, }, $push: { completedQuests: quest.id, @@ -86,19 +114,38 @@ const completeQuest = async (userId, eventPeriod, quest) => { } ); - const transaction = new transactionModel({ - type: "get", - amount: quest.rewardAmount, - userId, - questId: quest.id, - comment: `${quest.name} 달성 - ${quest.rewardAmount}개 획득`, - }); - await transaction.save(); + // 7단계: Transaction을 생성합니다. + const transactionsId = []; + if (quest.reward.credit) { + const transaction = new transactionModel({ + type: "get", + amount: quest.reward.credit, + userId, + questId: quest.id, + comment: `"${quest.name}" 퀘스트를 완료해 송편 ${quest.reward.credit}개를 획득했습니다.`, + }); + await transaction.save(); + + transactionsId.push(transaction._id); + } + if (quest.reward.ticket1) { + const transaction = new transactionModel({ + type: "use", + amount: 0, + userId, + questId: quest.id, + item: ticket1._id, + comment: `"${quest.name}" 퀘스트를 완료해 "${ticket1.name}" ${quest.reward.ticket1}개를 획득했습니다.`, + }); + await transaction.save(); + + transactionsId.push(transaction._id); + } logger.info(`User ${userId} successfully completed ${quest.id}Quest`); return { quest, - transactionId: transaction._id, + transactionsId, }; } catch (err) { logger.error(err); diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 4c124eb2..1a11e5ba 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -92,7 +92,7 @@ const getRandomItem = async (req, depth) => { userId: req.userOid, item: randomItem._id, itemType: randomItem.itemType, - comment: `랜덤박스에서 ${randomItem.name} 획득 - 0개 차감`, + comment: `랜덤 박스에서 "${randomItem.name}" 1개를 획득했습니다.`, }); await transaction.save(); @@ -175,7 +175,7 @@ const purchaseHandler = async (req, res) => { userId: req.userOid, item: item._id, itemType: item.itemType, - comment: `${item.name} 구입 - ${item.price}개 차감`, + comment: `송편 ${item.price}개를 사용해 "${item.name}" 1개를 획득했습니다.`, }); await transaction.save(); From fce261fbee41e5cb9909be6468c6c5c2a1d05480 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 18 Sep 2023 01:16:37 +0900 Subject: [PATCH 139/261] Docs: describe reward field --- src/lottery/modules/quests.js | 2 +- src/lottery/routes/docs/globalState.js | 28 ++++++++++++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index ebc0df05..50371daa 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -74,7 +74,7 @@ const completeQuest = async (userId, eventPeriod, quest) => { await eventStatus.save(); } - // 3단계: 유저의 퀘스트 달성 횟수를 확인합니다. + // 3단계: 유저의 퀘스트 완료 횟수를 확인합니다. const questCount = eventStatus.completedQuests.filter( (completedQuestId) => completedQuestId === quest.id ).length; diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index f05630db..29173691 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -7,7 +7,7 @@ globalStateDocs[`${apiPrefix}/`] = { tags: [`${apiPrefix}`], summary: "Frontend에서 Global state로 관리하는 정보 반환", description: - "유저의 재화 개수, 퀘스트 달성 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성합니다.", + "유저의 재화 개수, 퀘스트 완료 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성합니다.", responses: { 200: { description: "", @@ -31,7 +31,7 @@ globalStateDocs[`${apiPrefix}/`] = { completedQuests: { type: "array", description: - "유저가 달성한 퀘스트의 배열. 여러 번 달성할 수 있는 퀘스트의 경우 배열 내에 같은 퀘스트가 여러 번 포함됩니다.", + "유저가 완료한 퀘스트의 배열. 여러 번 완료할 수 있는 퀘스트의 경우 배열 내에 같은 퀘스트가 여러 번 포함됩니다.", items: { type: "string", description: "Quest의 Id", @@ -58,7 +58,7 @@ globalStateDocs[`${apiPrefix}/`] = { "name", "description", "imageUrl", - "rewardAmount", + "reward", "maxCount", ], properties: { @@ -83,14 +83,26 @@ globalStateDocs[`${apiPrefix}/`] = { description: "이미지 썸네일 URL", example: "THUMBNAIL URL", }, - rewardAmount: { - type: "number", - description: "달성 보상", - example: 100, + reward: { + type: "object", + description: "완료 보상", + required: ["credit", "ticket1"], + properties: { + credit: { + type: "number", + description: "완료 보상 중 재화의 개수입니다.", + example: 100, + }, + ticket1: { + type: "number", + description: "완료 보상 중 일반 티켓의 개수입니다.", + example: 1, + }, + }, }, maxCount: { type: "number", - description: "최대 달성 가능 횟수", + description: "최대 완료 가능 횟수", example: 1, }, }, From 8092fa1723db2a27de6a2ce714afa0385efc3343 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Sun, 17 Sep 2023 16:26:22 +0000 Subject: [PATCH 140/261] Fix: fix format --- src/modules/socket.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/socket.js b/src/modules/socket.js index 1e04cfcd..8e897c3b 100644 --- a/src/modules/socket.js +++ b/src/modules/socket.js @@ -246,7 +246,7 @@ const startSocketServer = (server) => { setHeader(key, values) { req.cookieHolder = values[0]; }, - writeHead() { }, + writeHead() {}, }; sessionMiddleware(req, fakeRes, () => { if (req.session) { @@ -283,7 +283,7 @@ const startSocketServer = (server) => { socket.join(`user-${userOid}`); }); - socket.on("disconnect", () => { }); + socket.on("disconnect", () => {}); } catch (err) { logger.error(err); } From 784cad3feccbbb0319a021b5b262ac46f218e655 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Sun, 17 Sep 2023 16:27:02 +0000 Subject: [PATCH 141/261] Remove: lastMsgDate in readChatHandler --- src/routes/chats.js | 1 - src/services/chats.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/routes/chats.js b/src/routes/chats.js index 9c0bb56b..f689348c 100644 --- a/src/routes/chats.js +++ b/src/routes/chats.js @@ -61,7 +61,6 @@ router.post( router.post( "/read", body("roomId").isMongoId(), - body("lastMsgDate").isISO8601(), validator, chatsHandlers.readChatHandler ); diff --git a/src/services/chats.js b/src/services/chats.js index f26facde..8973c24f 100644 --- a/src/services/chats.js +++ b/src/services/chats.js @@ -179,7 +179,7 @@ const readChatHandler = async (req, res) => { try { const io = req.app.get("io"); const { userId } = req; - const { roomId, lastMsgDate } = req.body; + const { roomId } = req.body; const user = await userModel.findOne({ id: userId }); if (!userId || !user) { @@ -206,7 +206,7 @@ const readChatHandler = async (req, res) => { }, }, { - $set: { "part.$[updater].readAt": lastMsgDate }, + $set: { "part.$[updater].readAt": Date.now() }, }, { new: true, From 86babb6bbd171b1eaa08e9a1cf9c0fe48bd079e6 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Sun, 17 Sep 2023 16:28:03 +0000 Subject: [PATCH 142/261] Refactor: change readAt field to optional --- src/modules/populates/rooms.js | 3 ++- src/modules/stores/mongo.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/populates/rooms.js b/src/modules/populates/rooms.js index d051a1f3..7243d648 100644 --- a/src/modules/populates/rooms.js +++ b/src/modules/populates/rooms.js @@ -29,13 +29,14 @@ const formatSettlement = ( roomObject.part = roomObject.part.map((participantSubDocument) => { const { _id, name, nickname, profileImageUrl } = participantSubDocument.user; - const { settlementStatus } = participantSubDocument; + const { settlementStatus, readAt } = participantSubDocument; return { _id, name, nickname, profileImageUrl, isSettlement: includeSettlement ? settlementStatus : undefined, + readAt: readAt ?? roomObject.madeat, }; }); roomObject.settlementTotal = includeSettlement diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 7223e368..0d29108c 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -34,7 +34,7 @@ const participantSchema = Schema({ enum: ["not-departed", "paid", "send-required", "sent"], default: "not-departed", }, - readAt: { type: Date, required: true, default: () => Date.now() }, + readAt: { type: Date, default: () => Date.now() }, }); const deviceTokenSchema = Schema({ From fdcf58217da17ca06f4527afeedde2f9174030aa Mon Sep 17 00:00:00 2001 From: static Date: Mon, 18 Sep 2023 01:40:31 +0900 Subject: [PATCH 143/261] Remove: itemType field in transactionSchema --- src/lottery/modules/stores/mongo.js | 4 ---- src/lottery/services/items.js | 2 -- src/lottery/services/transactions.js | 2 +- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 2e32978d..296db681 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -118,10 +118,6 @@ const transactionSchema = Schema({ type: Schema.Types.ObjectId, ref: "Item", }, - itemType: { - type: Number, - enum: [0, 1, 2, 3], - }, comment: { type: String, required: true, diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 1a11e5ba..36b4bf10 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -91,7 +91,6 @@ const getRandomItem = async (req, depth) => { amount: 0, userId: req.userOid, item: randomItem._id, - itemType: randomItem.itemType, comment: `랜덤 박스에서 "${randomItem.name}" 1개를 획득했습니다.`, }); await transaction.save(); @@ -174,7 +173,6 @@ const purchaseHandler = async (req, res) => { amount: item.price, userId: req.userOid, item: item._id, - itemType: item.itemType, comment: `송편 ${item.price}개를 사용해 "${item.name}" 1개를 획득했습니다.`, }); await transaction.save(); diff --git a/src/lottery/services/transactions.js b/src/lottery/services/transactions.js index 2009e540..c151d81f 100644 --- a/src/lottery/services/transactions.js +++ b/src/lottery/services/transactions.js @@ -8,7 +8,7 @@ const getUserTransactionsHandler = async (req, res) => { try { // userId는 이미 Frontend에서 알고 있고, 중복되는 값이므로 제외합니다. const transactions = await transactionModel - .find({ userId: req.userOid }, "-userId -itemType -__v") + .find({ userId: req.userOid }, "-userId -__v") .populate(transactionPopulateOption) .lean(); if (transactions) From 3cc48b99ccf5843e687257559e0f16f66ed89b41 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 18 Sep 2023 13:35:13 +0900 Subject: [PATCH 144/261] Add: /events/2023fall/global-state/create endpoint --- src/lottery/modules/quests.js | 13 ++++----- src/lottery/routes/globalState.js | 1 + src/lottery/services/globalState.js | 44 +++++++++++++++++++++++------ src/lottery/services/items.js | 12 ++++---- 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index 50371daa..cd3a36c1 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -58,7 +58,11 @@ const buildQuests = (quests) => { */ const completeQuest = async (userId, eventPeriod, quest) => { try { - // 1단계: 이벤트 기간인지 확인합니다. + // 1단계: 유저의 EventStatus를 가져옵니다. + const eventStatus = await eventStatusModel.findOne({ userId }).lean(); + if (!eventStatus) return null; + + // 2단계: 이벤트 기간인지 확인합니다. const now = Date.now(); if (now >= eventPeriod.end || now < eventPeriod.start) { logger.info( @@ -67,13 +71,6 @@ const completeQuest = async (userId, eventPeriod, quest) => { return null; } - // 2단계: 유저의 EventStatus를 가져옵니다. 없으면 새롭게 생성합니다. - let eventStatus = await eventStatusModel.findOne({ userId }).lean(); - if (!eventStatus) { - eventStatus = new eventStatusModel({ userId }); - await eventStatus.save(); - } - // 3단계: 유저의 퀘스트 완료 횟수를 확인합니다. const questCount = eventStatus.completedQuests.filter( (completedQuestId) => completedQuestId === quest.id diff --git a/src/lottery/routes/globalState.js b/src/lottery/routes/globalState.js index f0f75406..4cb17a4a 100644 --- a/src/lottery/routes/globalState.js +++ b/src/lottery/routes/globalState.js @@ -7,5 +7,6 @@ const globalStateHandlers = require("../services/globalState"); router.use(require("../../middlewares/auth")); router.get("/", globalStateHandlers.getUserGlobalStateHandler); +router.post("/create", globalStateHandlers.createUserGlobalStateHandler); module.exports = router; diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index ff55dc22..f4caa970 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -2,21 +2,20 @@ const { eventStatusModel } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); const { eventMode } = require("../../../loadenv"); -const quests = eventMode - ? Object.values(require(`../modules/contracts/${eventMode}`).quests) +const contract = eventMode + ? require(`../modules/contracts/${eventMode}`) : undefined; +const quests = contract ? Object.values(contract.quests) : undefined; const getUserGlobalStateHandler = async (req, res) => { try { - let eventStatus = await eventStatusModel + const eventStatus = await eventStatusModel .findOne({ userId: req.userOid }) .lean(); - if (!eventStatus) { - eventStatus = new eventStatusModel({ - userId: req.userOid, - }); - await eventStatus.save(); - } + if (!eventStatus) + return res + .status(400) + .json({ error: "GlobalState/ : nonexistent eventStatus" }); res.json({ creditAmount: eventStatus.creditAmount, @@ -31,6 +30,33 @@ const getUserGlobalStateHandler = async (req, res) => { } }; +const createUserGlobalStateHandler = async (req, res) => { + try { + let eventStatus = await eventStatusModel + .findOne({ userId: req.userOid }) + .lean(); + if (eventStatus) + return res + .status(400) + .json({ error: "GlobalState/Create : already created" }); + + eventStatus = new eventStatusModel({ + userId: req.userOid, + }); + await eventStatus.save(); + + await contract.completeFirstLoginQuest(req.userOid); + + res.json({ result: true }); + } catch (err) { + logger.error(err); + res + .status(500) + .json({ error: "GlobalState/Create : internal server error" }); + } +}; + module.exports = { getUserGlobalStateHandler, + createUserGlobalStateHandler, }; diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 36b4bf10..1eb1cd49 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -120,6 +120,12 @@ const listHandler = async (_, res) => { const purchaseHandler = async (req, res) => { try { + const eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); + if (!eventStatus) + return res + .status(400) + .json({ error: "Items/Purchase : nonexistent eventStatus" }); + const now = Date.now(); if (now >= eventPeriod.end || now < eventPeriod.start) return res.status(400).json({ error: "Items/Purchase : out of date" }); @@ -129,12 +135,6 @@ const purchaseHandler = async (req, res) => { if (!item) return res.status(400).json({ error: "Items/Purchase : invalid Item" }); - const eventStatus = await eventStatusModel.find({ userId: req.userOid }); - if (!eventStatus) - return res - .status(400) - .json({ error: "Items/Purchase : invalid EventStatus" }); - // 구매 가능 조건: 크레딧이 충분하며, 재고가 남아있으며, 판매 중인 아이템이어야 합니다. if (item.isDisabled) return res.status(400).json({ error: "Items/Purchase : disabled item" }); From b7c28d6e7915576dcfcff3c43089effbd30a7ff9 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 18 Sep 2023 13:37:44 +0900 Subject: [PATCH 145/261] Docs: describe /events/2023fall/global-state/create endpoint --- src/lottery/routes/docs/globalState.js | 30 +++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 29173691..63825433 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -7,7 +7,7 @@ globalStateDocs[`${apiPrefix}/`] = { tags: [`${apiPrefix}`], summary: "Frontend에서 Global state로 관리하는 정보 반환", description: - "유저의 재화 개수, 퀘스트 완료 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성합니다.", + "유저의 재화 개수, 퀘스트 완료 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다.", responses: { 200: { description: "", @@ -116,5 +116,33 @@ globalStateDocs[`${apiPrefix}/`] = { }, }, }; +globalStateDocs[`${apiPrefix}/create`] = { + get: { + tags: [`${apiPrefix}`], + summary: "Frontend에서 Global state로 관리하는 정보 생성", + description: + "유저의 재화 개수, 퀘스트 완료 상태 등 Frontend에서 Global state로 관리할 정보를 생성합니다.", + responses: { + 200: { + description: "", + content: { + "application/json": { + schema: { + type: "object", + required: ["result"], + properties: { + result: { + type: "boolean", + description: "성공 여부. 항상 true입니다.", + example: true, + }, + }, + }, + }, + }, + }, + }, + }, +}; module.exports = globalStateDocs; From 73f82b3345f48564ffa60bdaaec9ae9e0b966e0b Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 14:38:22 +0900 Subject: [PATCH 146/261] Remove: routes/publicNotice.js auth middle ware deleted. --- src/lottery/modules/contracts/2023fall.js | 8 ++--- src/lottery/routes/events.js | 7 +++-- src/lottery/routes/publicNotice.js | 3 +- src/lottery/services/events.js | 37 +++++++++++++++-------- src/lottery/services/publicNotice.js | 1 - 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 6cd91fe1..6840d502 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -106,12 +106,12 @@ const requestAdPushAgreementEvent = async () => { // TODO }; -const requestEventSharingOnInstagram = async () => { - // TODO +const requestEventSharingOnInstagram = async (userId) => { + return eventHandler(userId, "이벤트 인스타그램 스토리에 공유"); }; -const requestPurchaseSharingOnInstagram = async () => { - // TODO +const requestPurchaseSharingOnInstagram = async (userId) => { + return eventHandler(userId, "아이템 구매 후 인스타그램 스토리에 공유"); }; module.exports = { diff --git a/src/lottery/routes/events.js b/src/lottery/routes/events.js index 9f2e6b18..38d77cbd 100644 --- a/src/lottery/routes/events.js +++ b/src/lottery/routes/events.js @@ -3,7 +3,10 @@ const router = express.Router(); const events = require("../services/events"); router.use(require("../../middlewares/auth")); -router.post("/instagram/share-event", events.instagramEventShareHandler); -router.post("/instagram/share-purchase", events.instagramPurchaseShareHandler); +router.post("/instagram/share-event", events.instagramEventShareHandler()); +router.post( + "/instagram/share-purchase", + events.instagramPurchaseShareHandler() +); module.exports = router; diff --git a/src/lottery/routes/publicNotice.js b/src/lottery/routes/publicNotice.js index 6f5f20c3..7176b20c 100644 --- a/src/lottery/routes/publicNotice.js +++ b/src/lottery/routes/publicNotice.js @@ -2,8 +2,7 @@ const express = require("express"); const router = express.Router(); const publicNotice = require("../services/publicNotice"); -// 상점이용은 로그인을 요구합니다. -router.use(require("../../middlewares/auth")); +// 로그인 없이 공지를 볼 수 있습니다. router.get("/get-recent-transaction", publicNotice.getRecentTransaction); module.exports = router; diff --git a/src/lottery/services/events.js b/src/lottery/services/events.js index 8ea9bedc..4b4b3943 100644 --- a/src/lottery/services/events.js +++ b/src/lottery/services/events.js @@ -1,28 +1,39 @@ const logger = require("../../modules/logger"); -const eventHandler = require("../modules/events"); +const contracts = require("../modules/contracts/2023fall"); + // 인스타그램 스토리에 이벤트를 공유했을 때. const instagramEventShareHandler = async (req, res) => { try { - const userId = req.userOid; - const eventId = "650302f799c6f338d5ea5427"; - const transactionResult = await eventHandler(userId, eventId); - res.json({ result: !!transactionResult ? true : false }); + const contractsResult = contracts.requestEventSharingOnInstagram(); + if (contractsResult) { + res.json({ contractsResult }); + } else if (contractsResult === null) { + res.json({}); + } else { + logger.error(err); + res.json({ error: "Events/Insagram/Share-Event" }); + } } catch (err) { - logger.err(err); - res.status(500).json({ error: "Events/Insagram/Share-Event" }); + logger.error(err); + res.json({ error: "Events/Insagram/Share-Event" }); } }; // 인스타그램 스토리에 아이템 구매 내역을 공유했을 때. const instagramPurchaseShareHandler = async (req, res) => { try { - const userId = req.userOid; - const eventId = "6503030e99c6f338d5ea5433"; - const transactionResult = await eventHandler(userId, eventId); - res.json({ result: !!transactionResult ? true : false }); + const contractsResult = contracts.requestEventSharingOnInstagram(); + if (contractsResult) { + res.json({ contractsResult }); + } else if (contractsResult === null) { + res.json({}); + } else { + logger.error(err); + return { error: "Events/Insagram/Share-Purchase" }; + } } catch (err) { - logger.err(err); - res.status(500).json({ error: "Events/Insagram/Share-Purchase" }); + logger.error(err); + return { error: "Events/Insagram/Share-Purchase" }; } }; diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 4e229962..4863aa1f 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -54,7 +54,6 @@ const getRecentTransaction = async (req, res) => { item.item.name }을(를) ${purchaceMessage}`; }); - console.log(transactionListString); res.json({ transactionListString, }); From 2a8adf08f10d024324736a47e94d35331b0d8e0d Mon Sep 17 00:00:00 2001 From: static Date: Mon, 18 Sep 2023 15:28:27 +0900 Subject: [PATCH 147/261] Add: agreement field in the response of global-state endpoint --- src/lottery/modules/contracts/2023fall.js | 1 + src/lottery/routes/docs/globalState.js | 6 +++++ src/lottery/services/globalState.js | 30 ++++++++++++++--------- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index f5994d51..a15caf90 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -85,6 +85,7 @@ const eventPeriod = { * firstLogin 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} + * @usage lottery/globalState/createUserGlobalStateHandler */ const completeFirstLoginQuest = async (userId) => { return await completeQuest(userId, eventPeriod, quests.firstLogin); diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 63825433..8a2afa77 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -16,6 +16,7 @@ globalStateDocs[`${apiPrefix}/`] = { schema: { type: "object", required: [ + "agreement", "creditAmount", "completedQuests", "ticket1Amount", @@ -23,6 +24,11 @@ globalStateDocs[`${apiPrefix}/`] = { "quests", ], properties: { + agreement: { + type: "boolean", + description: "유저의 이벤트 참여 동의 여부", + example: true, + }, creditAmount: { type: "number", description: "재화 개수. 0 이상입니다.", diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index f4caa970..bd31cb04 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -12,18 +12,24 @@ const getUserGlobalStateHandler = async (req, res) => { const eventStatus = await eventStatusModel .findOne({ userId: req.userOid }) .lean(); - if (!eventStatus) - return res - .status(400) - .json({ error: "GlobalState/ : nonexistent eventStatus" }); - - res.json({ - creditAmount: eventStatus.creditAmount, - completedQuests: eventStatus.completedQuests, - ticket1Amount: eventStatus.ticket1Amount, - ticket2Amount: eventStatus.ticket2Amount, - quests, - }); + if (eventStatus) + res.json({ + agreement: true, + creditAmount: eventStatus.creditAmount, + completedQuests: eventStatus.completedQuests, + ticket1Amount: eventStatus.ticket1Amount, + ticket2Amount: eventStatus.ticket2Amount, + quests, + }); + else + res.json({ + agreement: false, + creditAmount: 0, + completedQuests: [], + ticket1Amount: 0, + ticket2Amount: 0, + quests, + }); } catch (err) { logger.error(err); res.status(500).json({ error: "GlobalState/ : internal server error" }); From e21e4a04e4a41ff70faa8aaacd5e05a9cf4f8849 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 18 Sep 2023 15:56:57 +0900 Subject: [PATCH 148/261] Refactor: use req.timestamp instead of Date.now() --- src/lottery/modules/quests.js | 3 +-- src/lottery/services/items.js | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index cd3a36c1..c25e4b7e 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -63,8 +63,7 @@ const completeQuest = async (userId, eventPeriod, quest) => { if (!eventStatus) return null; // 2단계: 이벤트 기간인지 확인합니다. - const now = Date.now(); - if (now >= eventPeriod.end || now < eventPeriod.start) { + if (req.timestamp >= eventPeriod.end || req.timestamp < eventPeriod.start) { logger.info( `User ${userId} failed to complete auto-disabled ${quest.id}Quest` ); diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 1eb1cd49..5e3a5a1c 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -126,8 +126,7 @@ const purchaseHandler = async (req, res) => { .status(400) .json({ error: "Items/Purchase : nonexistent eventStatus" }); - const now = Date.now(); - if (now >= eventPeriod.end || now < eventPeriod.start) + if (req.timestamp >= eventPeriod.end || req.timestamp < eventPeriod.start) return res.status(400).json({ error: "Items/Purchase : out of date" }); const { itemId } = req.params; From a8b685a3d68ece80b34999b8f41c72b4196b8982 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 18 Sep 2023 17:31:28 +0900 Subject: [PATCH 149/261] Add: timestamp parameter in completeQuest function --- src/lottery/modules/contracts/2023fall.js | 49 +++++++++++++++++------ src/lottery/modules/quests.js | 5 ++- src/lottery/services/globalState.js | 2 +- src/services/rooms.js | 18 ++++++--- src/services/users.js | 11 ++++- 5 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index a15caf90..ba656d4f 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -84,15 +84,17 @@ const eventPeriod = { /** * firstLogin 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @returns {Promise} * @usage lottery/globalState/createUserGlobalStateHandler */ -const completeFirstLoginQuest = async (userId) => { - return await completeQuest(userId, eventPeriod, quests.firstLogin); +const completeFirstLoginQuest = async (userId, timestamp) => { + return await completeQuest(userId, timestamp, eventPeriod, quests.firstLogin); }; /** * payingAndSending 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이거나, 모든 참가자가 정산 또는 송금을 완료하지 않았다면 요청하지 않습니다. + * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @param {number} roomObject.settlementTotal - 정산 또는 송금이 완료된 참여자 수입니다. @@ -100,7 +102,7 @@ const completeFirstLoginQuest = async (userId) => { * @description 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. * @usage rooms/commitPaymentHandler, rooms/settlementHandler */ -const completePayingAndSendingQuest = async (roomObject) => { +const completePayingAndSendingQuest = async (timestamp, roomObject) => { if (roomObject.part.length < 2) return null; if (roomObject.part.length > roomObject.settlementTotal) return null; @@ -109,6 +111,7 @@ const completePayingAndSendingQuest = async (roomObject) => { async (participant) => await completeQuest( participant.user._id, + timestamp, eventPeriod, quests.payingAndSending ) @@ -119,12 +122,18 @@ const completePayingAndSendingQuest = async (roomObject) => { /** * firstRoomCreation 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @returns {Promise} * @description 방을 만들 때마다 호출해 주세요. * @usage rooms/createHandler */ -const completeFirstRoomCreationQuest = async (userId) => { - return await completeQuest(userId, eventPeriod, quests.firstRoomCreation); +const completeFirstRoomCreationQuest = async (userId, timestamp) => { + return await completeQuest( + userId, + timestamp, + eventPeriod, + quests.firstRoomCreation + ); }; const completeRoomSharingQuest = async () => { @@ -134,56 +143,70 @@ const completeRoomSharingQuest = async () => { /** * paying 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} * @description 정산 요청이 이루어질 때마다 호출해 주세요. * @usage rooms/commitPaymentHandler */ -const completePayingQuest = async (userId, roomObject) => { +const completePayingQuest = async (userId, timestamp, roomObject) => { if (roomObject.part.length < 2) return null; - return await completeQuest(userId, eventPeriod, quests.paying); + return await completeQuest(userId, timestamp, eventPeriod, quests.paying); }; /** * sending 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} * @description 송금이 이루어질 때마다 호출해 주세요. * @usage rooms/settlementHandler */ -const completeSendingQuest = async (userId, roomObject) => { +const completeSendingQuest = async (userId, timestamp, roomObject) => { if (roomObject.part.length < 2) return null; - return await completeQuest(userId, eventPeriod, quests.sending); + return await completeQuest(userId, timestamp, eventPeriod, quests.sending); }; /** * nicknameChaning 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @returns {Promise} * @description 닉네임을 변경할 때마다 호출해 주세요. * @usage users/editNicknameHandler */ -const completeNicknameChangingQuest = async (userId) => { - return await completeQuest(userId, eventPeriod, quests.nicknameChaning); +const completeNicknameChangingQuest = async (userId, timestamp) => { + return await completeQuest( + userId, + timestamp, + eventPeriod, + quests.nicknameChaning + ); }; /** * accountChanging 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {string} newAccount - 변경된 계좌입니다. * @returns {Promise} * @description 계좌를 변경할 때마다 호출해 주세요. * @usage users/editAccountHandler */ -const completeAccountChangingQuest = async (userId, newAccount) => { +const completeAccountChangingQuest = async (userId, timestamp, newAccount) => { if (newAccount === "") return null; - return await completeQuest(userId, eventPeriod, quests.accountChanging); + return await completeQuest( + userId, + timestamp, + eventPeriod, + quests.accountChanging + ); }; const completeAdPushAgreementQuest = async () => { diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index c25e4b7e..4ec5c4f3 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -44,6 +44,7 @@ const buildQuests = (quests) => { /** * 퀘스트 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {Object} eventPeriod - 이벤트의 기간입니다. * @param {Date} eventPeriod.start - 이벤트의 시작 시각(Inclusive)입니다. * @param {Date} eventPeriod.end - 이벤트의 종료 시각(Exclusive)입니다. @@ -56,14 +57,14 @@ const buildQuests = (quests) => { * @param {number} quest.maxCount - 퀘스트의 최대 완료 가능 횟수입니다. * @returns {Object|null} 성공한 경우 Object를, 실패한 경우 null을 반환합니다. 이미 최대 완료 횟수에 도달했거나, 퀘스트가 원격으로 비활성화 된 경우에도 실패로 처리됩니다. */ -const completeQuest = async (userId, eventPeriod, quest) => { +const completeQuest = async (userId, timestamp, eventPeriod, quest) => { try { // 1단계: 유저의 EventStatus를 가져옵니다. const eventStatus = await eventStatusModel.findOne({ userId }).lean(); if (!eventStatus) return null; // 2단계: 이벤트 기간인지 확인합니다. - if (req.timestamp >= eventPeriod.end || req.timestamp < eventPeriod.start) { + if (timestamp >= eventPeriod.end || timestamp < eventPeriod.start) { logger.info( `User ${userId} failed to complete auto-disabled ${quest.id}Quest` ); diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index bd31cb04..425cb6d1 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -51,7 +51,7 @@ const createUserGlobalStateHandler = async (req, res) => { }); await eventStatus.save(); - await contract.completeFirstLoginQuest(req.userOid); + await contract.completeFirstLoginQuest(req.userOid, req.timestamp); res.json({ result: true }); } catch (err) { diff --git a/src/services/rooms.js b/src/services/rooms.js index eb158278..4ef35731 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -87,7 +87,7 @@ const createHandler = async (req, res) => { const roomObjectFormated = formatSettlement(roomObject); // 이벤트 코드입니다. - await contracts?.completeFirstRoomCreationQuest(user._id); + await contracts?.completeFirstRoomCreationQuest(req.userOid, req.timestamp); return res.send(roomObjectFormated); } catch (err) { @@ -492,8 +492,12 @@ const commitPaymentHandler = async (req, res) => { }); // 이벤트 코드입니다. - await contracts?.completePayingQuest(user._id, roomObject); - await contracts?.completePayingAndSendingQuest(roomObject); + await contracts?.completePayingQuest( + req.userOid, + req.timestamp, + roomObject + ); + await contracts?.completePayingAndSendingQuest(req.timestamp, roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); @@ -562,8 +566,12 @@ const settlementHandler = async (req, res) => { }); // 이벤트 코드입니다. - await contracts?.completeSendingQuest(user._id, roomObject); - await contracts?.completePayingAndSendingQuest(roomObject); + await contracts?.completeSendingQuest( + req.userOid, + req.timestamp, + roomObject + ); + await contracts?.completePayingAndSendingQuest(req.timestamp, roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); diff --git a/src/services/users.js b/src/services/users.js index b7b47e41..f4a00d6a 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -46,7 +46,10 @@ const editNicknameHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await contracts?.completeNicknameChangingQuest(req.userOid); + await contracts?.completeNicknameChangingQuest( + req.userOid, + req.timestamp + ); res.status(200).send("User/editNickname : edit user nickname successful"); } else { @@ -68,7 +71,11 @@ const editAccountHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await contracts?.completeAccountChangingQuest(req.userOid, newAccount); + await contracts?.completeAccountChangingQuest( + req.userOid, + req.timestamp, + newAccount + ); res.status(200).send("User/editAccount : edit user account successful"); } else { From 4a258ffebf333ffd81b63526c640a75d16dfbe23 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 18 Sep 2023 19:37:22 +0900 Subject: [PATCH 150/261] Add: add validator to /events/2023fall/items/purchase/:itemId endpoint --- src/lottery/routes/items.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lottery/routes/items.js b/src/lottery/routes/items.js index 539288bc..f4a39632 100644 --- a/src/lottery/routes/items.js +++ b/src/lottery/routes/items.js @@ -4,7 +4,16 @@ const router = express.Router(); const itemsHandlers = require("../services/items"); const auth = require("../../middlewares/auth"); +const { param } = require("express-validator"); +const validator = require("../../middlewares/validator"); + router.get("/list", itemsHandlers.listHandler); -router.post("/purchase/:itemId", auth, itemsHandlers.purchaseHandler); +router.post( + "/purchase/:itemId", + auth, + param("itemId").isMongoId(), + validator, + itemsHandlers.purchaseHandler +); module.exports = router; From d5d3450c57d203b05c49753c40899f538346aeb5 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 19:55:25 +0900 Subject: [PATCH 151/261] ADD: Merging process... --- src/lottery/index.js | 4 ---- src/lottery/routes/docs/swaggerDocs.js | 12 ------------ src/lottery/services/events.js | 4 ++-- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/src/lottery/index.js b/src/lottery/index.js index 2ad979ca..2329d28d 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -21,10 +21,6 @@ lotteryRouter.use(require("../middlewares/originValidator")); lotteryRouter.use("/events", require("./routes/events")); lotteryRouter.use("/global-state", require("./routes/globalState")); lotteryRouter.use("/items", require("./routes/items")); -<<<<<<< HEAD -lotteryRouter.use("/transactions", require("./routes/transactions")); -======= ->>>>>>> dev lotteryRouter.use("/public-notice", require("./routes/publicNotice")); const resources = [ diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index b0ec1912..a780293d 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -2,11 +2,7 @@ const { eventMode } = require("../../../../loadenv"); const globalStateDocs = require("./globalState"); const itemsDocs = require("./items"); const transactionsDocs = require("./transactions"); -<<<<<<< HEAD const eventsDocs = require("./events"); -======= -const itemsSchema = require("./itemsSchema"); ->>>>>>> dev const publicNoticeDocs = require("./publicNotice"); const apiPrefix = `/events/${eventMode}`; @@ -26,27 +22,19 @@ const eventSwaggerDocs = { description: "이벤트 - 입출금 내역 관련 API", }, { -<<<<<<< HEAD name: `${apiPrefix}/instagram`, description: "이벤트 - 인스타그램 이벤트 관련 API", }, { name: `${apiPrefix}/public-notice`, description: "이벤트 - 아이템 구매, 뽑기, 획득 공지 관련 API", -======= - name: `${apiPrefix}/public-notice`, - description: "이벤트 - 공지사항 관련 API", ->>>>>>> dev }, ], paths: { ...globalStateDocs, ...itemsDocs, ...transactionsDocs, -<<<<<<< HEAD ...eventsDocs, -======= ->>>>>>> dev ...publicNoticeDocs, }, components: { diff --git a/src/lottery/services/events.js b/src/lottery/services/events.js index 4b4b3943..8420fe15 100644 --- a/src/lottery/services/events.js +++ b/src/lottery/services/events.js @@ -4,7 +4,7 @@ const contracts = require("../modules/contracts/2023fall"); // 인스타그램 스토리에 이벤트를 공유했을 때. const instagramEventShareHandler = async (req, res) => { try { - const contractsResult = contracts.requestEventSharingOnInstagram(); + const contractsResult = contracts.completeEventSharingOnInstagramQuest(); if (contractsResult) { res.json({ contractsResult }); } else if (contractsResult === null) { @@ -22,7 +22,7 @@ const instagramEventShareHandler = async (req, res) => { // 인스타그램 스토리에 아이템 구매 내역을 공유했을 때. const instagramPurchaseShareHandler = async (req, res) => { try { - const contractsResult = contracts.requestEventSharingOnInstagram(); + const contractsResult = contracts.completePurchaseSharingOnInstagramQuest(); if (contractsResult) { res.json({ contractsResult }); } else if (contractsResult === null) { From f5fb681f5fb5812459421bcafc1c05e62b7640bf Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 19:57:47 +0900 Subject: [PATCH 152/261] ADD: Merging process... --- src/lottery/routes/docs/publicNotice.js | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js index 32d3b6a6..cda93fa1 100644 --- a/src/lottery/routes/docs/publicNotice.js +++ b/src/lottery/routes/docs/publicNotice.js @@ -2,28 +2,6 @@ const { eventMode } = require("../../../../loadenv"); const apiPrefix = `/events/${eventMode}/public-notice`; const publicNoticeDocs = {}; -<<<<<<< HEAD -publicNoticeDocs[`${apiPrefix}/get-recent-transaction`] = { - get: { - tags: [`${apiPrefix}`], - summary: "공지를 띄움", - description: "상품 구매, 뽑기 획득 등등에 대한 공지를 반환", - responses: { - 200: { - content: { - "application/json": { - schema: { - type: "array", - description: - "use 트랜젝션의 정보를 담은 string 배열. 구매자의 익명 이름과 구매 내역을 문자열로 변환 후 이를 배열로 담아 5개 반환", - example: [ - "su****님께서 매점2000원권을(를) 구입하셨습니다.", - "mo****님께서 치킨을(를) 구입하셨습니다.", - "tu*****님께서 매점2000원권을(를) 구입하셨습니다.", - "as**님께서 진짜송편을(를) 구입하셨습니다.", - "qw**님께서 전동킥보드을(를) 뽑았습니다.", - ], -======= publicNoticeDocs[`${apiPrefix}/leaderboard`] = { get: { tags: [`${apiPrefix}`], @@ -91,7 +69,6 @@ publicNoticeDocs[`${apiPrefix}/leaderboard`] = { example: 0.00003, }, }, ->>>>>>> dev }, }, }, From ac10f658565640a8befa011d1d3af99432d47aa5 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 20:05:24 +0900 Subject: [PATCH 153/261] ADD: Merging process... --- src/lottery/routes/docs/swaggerDocs.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index a780293d..f2a2e5ef 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -3,6 +3,7 @@ const globalStateDocs = require("./globalState"); const itemsDocs = require("./items"); const transactionsDocs = require("./transactions"); const eventsDocs = require("./events"); +const itemsSchema = require("./itemsSchema"); const publicNoticeDocs = require("./publicNotice"); const apiPrefix = `/events/${eventMode}`; @@ -29,6 +30,10 @@ const eventSwaggerDocs = { name: `${apiPrefix}/public-notice`, description: "이벤트 - 아이템 구매, 뽑기, 획득 공지 관련 API", }, + { + name: `${apiPrefix}/public-notice`, + description: "이벤트 - 공지사항 관련 API", + }, ], paths: { ...globalStateDocs, From c50556153f89d1fe5861ef853e9243a0697e8e5d Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 20:13:53 +0900 Subject: [PATCH 154/261] ADD: Mergin process... --- src/lottery/services/events.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lottery/services/events.js b/src/lottery/services/events.js index 8420fe15..6cba4de2 100644 --- a/src/lottery/services/events.js +++ b/src/lottery/services/events.js @@ -4,11 +4,11 @@ const contracts = require("../modules/contracts/2023fall"); // 인스타그램 스토리에 이벤트를 공유했을 때. const instagramEventShareHandler = async (req, res) => { try { - const contractsResult = contracts.completeEventSharingOnInstagramQuest(); + const contractsResult = contracts.completeEventSharingOnInstagramQuest(req.userOid); if (contractsResult) { res.json({ contractsResult }); } else if (contractsResult === null) { - res.json({}); + res.json({ contractsResult }); } else { logger.error(err); res.json({ error: "Events/Insagram/Share-Event" }); @@ -22,11 +22,11 @@ const instagramEventShareHandler = async (req, res) => { // 인스타그램 스토리에 아이템 구매 내역을 공유했을 때. const instagramPurchaseShareHandler = async (req, res) => { try { - const contractsResult = contracts.completePurchaseSharingOnInstagramQuest(); + const contractsResult = contracts.completePurchaseSharingOnInstagramQuest(req.userOid); if (contractsResult) { res.json({ contractsResult }); } else if (contractsResult === null) { - res.json({}); + res.json({ contractsResult }); } else { logger.error(err); return { error: "Events/Insagram/Share-Purchase" }; From fce0ceb3f0f8c8f83027139aa7505c66e72d64ad Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 21:13:36 +0900 Subject: [PATCH 155/261] Revert: "Remove: routes/publicNotice.js auth middle ware deleted." ADD: Merge process This reverts commit 73f82b3345f48564ffa60bdaaec9ae9e0b966e0b. --- src/lottery/routes/events.js | 9 ++++ src/lottery/routes/publicNotice.js | 4 +- src/lottery/services/events.js | 32 +++++++++++++ src/lottery/services/publicNotice.js | 72 +++++++++++++++++++++++++++- 4 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 src/lottery/routes/events.js create mode 100644 src/lottery/services/events.js diff --git a/src/lottery/routes/events.js b/src/lottery/routes/events.js new file mode 100644 index 00000000..9f2e6b18 --- /dev/null +++ b/src/lottery/routes/events.js @@ -0,0 +1,9 @@ +const express = require("express"); +const router = express.Router(); +const events = require("../services/events"); + +router.use(require("../../middlewares/auth")); +router.post("/instagram/share-event", events.instagramEventShareHandler); +router.post("/instagram/share-purchase", events.instagramPurchaseShareHandler); + +module.exports = router; diff --git a/src/lottery/routes/publicNotice.js b/src/lottery/routes/publicNotice.js index 2e09ee65..1adfc3fd 100644 --- a/src/lottery/routes/publicNotice.js +++ b/src/lottery/routes/publicNotice.js @@ -3,6 +3,8 @@ const express = require("express"); const router = express.Router(); const publicNoticeHandlers = require("../services/publicNotice"); -router.get("/leaderboard", publicNoticeHandlers.getTicketLeaderboardHandler); +// 상점이용은 로그인을 요구합니다. +router.use(require("../../middlewares/auth")); +router.get("/get-recent-transaction", publicNotice.getRecentTransaction); module.exports = router; diff --git a/src/lottery/services/events.js b/src/lottery/services/events.js new file mode 100644 index 00000000..8ea9bedc --- /dev/null +++ b/src/lottery/services/events.js @@ -0,0 +1,32 @@ +const logger = require("../../modules/logger"); +const eventHandler = require("../modules/events"); +// 인스타그램 스토리에 이벤트를 공유했을 때. +const instagramEventShareHandler = async (req, res) => { + try { + const userId = req.userOid; + const eventId = "650302f799c6f338d5ea5427"; + const transactionResult = await eventHandler(userId, eventId); + res.json({ result: !!transactionResult ? true : false }); + } catch (err) { + logger.err(err); + res.status(500).json({ error: "Events/Insagram/Share-Event" }); + } +}; + +// 인스타그램 스토리에 아이템 구매 내역을 공유했을 때. +const instagramPurchaseShareHandler = async (req, res) => { + try { + const userId = req.userOid; + const eventId = "6503030e99c6f338d5ea5433"; + const transactionResult = await eventHandler(userId, eventId); + res.json({ result: !!transactionResult ? true : false }); + } catch (err) { + logger.err(err); + res.status(500).json({ error: "Events/Insagram/Share-Purchase" }); + } +}; + +module.exports = { + instagramEventShareHandler, + instagramPurchaseShareHandler, +}; diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index ac4b2b9d..8e8227d9 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -1,7 +1,76 @@ +const { transactionModel } = require("../modules/stores/mongo"); const { eventStatusModel } = require("../modules/stores/mongo"); const { userModel } = require("../../modules/stores/mongo"); const logger = require("../../modules/logger"); -const { isLogin, getLoginInfo } = require("../../modules/auths/login"); +const { + publicNoticePopulateOption, +} = require("../modules/populates/publicNotice"); + +const getTransactions = async () => { + try { + const transactions = await transactionModel + .find({ type: "use" }) + .sort({ doneat: -1 }) + .limit(5) + .populate(publicNoticePopulateOption) + .lean(); + if (transactions) { + return await getTransactionsCallbackGetUser(transactions); + } else { + return undefined; + } + } catch (err) { + return undefined; + } +}; +const getTransactionsCallbackGetUser = async (transactions) => { + const users = await userModel.find(); + for (let user of users) { + for (let transaction of transactions) { + if (user._id.equals(transaction.userId)) { + transaction.id = user.id; + } + } + } + return transactions; +}; +const getRecentTransaction = async (req, res) => { + try { + let transactionListString = []; + await getTransactions(); + const transactions = await getTransactions(); + if (!!transactions) { + transactions.forEach((item, index) => { + let purchaceMessage = ""; + if (item.comment.includes("구매")) { + purchaceMessage = "구입하셨습니다."; + } else if (item.comment.includes("획득")) { + purchaceMessage = "뽑았습니다."; + } else { + purchaceMessage = "획득하셨습니다."; + } + transactionListString[index] = `${item.id + .toString() + .slice(0, 2)}${"*".repeat(item.id.length - 2)}님께서 ${ + item.item.name + }을(를) ${purchaceMessage}`; + }); + console.log(transactionListString); + res.json({ + transactionListString, + }); + } else { + res.status(500).json({ + error: "PublicNotice/get-recent-transaction : internal server error", + }); + } + } catch (err) { + logger.error(err); + res.status(500).json({ + error: "PublicNotice/get-recent-transaction : internal server error", + }); + } +}; const getTicketLeaderboardHandler = async (req, res) => { try { @@ -67,5 +136,6 @@ const getTicketLeaderboardHandler = async (req, res) => { }; module.exports = { + getRecentTransaction, getTicketLeaderboardHandler, }; From e017c782055d6f33a93db493c267c5a8cfec50ae Mon Sep 17 00:00:00 2001 From: static Date: Mon, 18 Sep 2023 21:45:18 +0900 Subject: [PATCH 156/261] Refactor: global-state endpoint can be accessed without session --- src/lottery/routes/docs/globalState.js | 4 +-- src/lottery/routes/globalState.js | 6 ++-- src/lottery/services/globalState.js | 43 +++++++++++++------------- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 8a2afa77..12fd93bb 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -16,7 +16,7 @@ globalStateDocs[`${apiPrefix}/`] = { schema: { type: "object", required: [ - "agreement", + "isAgree", "creditAmount", "completedQuests", "ticket1Amount", @@ -24,7 +24,7 @@ globalStateDocs[`${apiPrefix}/`] = { "quests", ], properties: { - agreement: { + isAgree: { type: "boolean", description: "유저의 이벤트 참여 동의 여부", example: true, diff --git a/src/lottery/routes/globalState.js b/src/lottery/routes/globalState.js index 4cb17a4a..e36828a1 100644 --- a/src/lottery/routes/globalState.js +++ b/src/lottery/routes/globalState.js @@ -2,11 +2,9 @@ const express = require("express"); const router = express.Router(); const globalStateHandlers = require("../services/globalState"); - -// 라우터 접근 시 로그인 필요 -router.use(require("../../middlewares/auth")); +const auth = require("../../middlewares/auth"); router.get("/", globalStateHandlers.getUserGlobalStateHandler); -router.post("/create", globalStateHandlers.createUserGlobalStateHandler); +router.post("/create", auth, globalStateHandlers.createUserGlobalStateHandler); module.exports = router; diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 425cb6d1..0b0a1cc1 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -1,5 +1,6 @@ const { eventStatusModel } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); +const { isLogin, getLoginInfo } = require("../../modules/auths/login"); const { eventMode } = require("../../../loadenv"); const contract = eventMode @@ -9,27 +10,27 @@ const quests = contract ? Object.values(contract.quests) : undefined; const getUserGlobalStateHandler = async (req, res) => { try { - const eventStatus = await eventStatusModel - .findOne({ userId: req.userOid }) - .lean(); - if (eventStatus) - res.json({ - agreement: true, - creditAmount: eventStatus.creditAmount, - completedQuests: eventStatus.completedQuests, - ticket1Amount: eventStatus.ticket1Amount, - ticket2Amount: eventStatus.ticket2Amount, - quests, - }); - else - res.json({ - agreement: false, - creditAmount: 0, - completedQuests: [], - ticket1Amount: 0, - ticket2Amount: 0, - quests, - }); + const result = { + isAgree: false, + creditAmount: 0, + completedQuests: [], + ticket1Amount: 0, + ticket2Amount: 0, + quests, + }; + + const userId = isLogin(req) ? getLoginInfo(req).oid : null; + if (!userId) return res.json(result); + + const eventStatus = await eventStatusModel.findOne({ userId }).lean(); + if (eventStatus) { + result.isAgree = true; + result.creditAmount = eventStatus.creditAmount; + result.ticket1Amount = eventStatus.ticket1Amount; + result.ticket2Amount = eventStatus.ticket2Amount; + } + + res.json(result); } catch (err) { logger.error(err); res.status(500).json({ error: "GlobalState/ : internal server error" }); From 6148984c820cfe64012d126689b03d75436e3718 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 22:18:32 +0900 Subject: [PATCH 157/261] ADD: Merging process... --- src/lottery/index.js | 1 + src/lottery/modules/contracts/2023fall.js | 6 ++-- src/lottery/modules/populates/publicNotice.js | 11 +++++++ src/lottery/routes/publicNotice.js | 6 ++-- src/lottery/routes/{events.js => quests.js} | 2 +- src/lottery/services/events.js | 32 ------------------- src/lottery/services/quests.js | 31 ++++++++++++++++++ 7 files changed, 50 insertions(+), 39 deletions(-) create mode 100644 src/lottery/modules/populates/publicNotice.js rename src/lottery/routes/{events.js => quests.js} (86%) delete mode 100644 src/lottery/services/events.js create mode 100644 src/lottery/services/quests.js diff --git a/src/lottery/index.js b/src/lottery/index.js index 7ca282fe..38a99dce 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -22,6 +22,7 @@ lotteryRouter.use("/global-state", require("./routes/globalState")); lotteryRouter.use("/transactions", require("./routes/transactions")); lotteryRouter.use("/items", require("./routes/items")); lotteryRouter.use("/public-notice", require("./routes/publicNotice")); +lotteryRouter.use("/quests", require("./routes/quests")); const resources = [ eventStatusModel, diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index f5994d51..ae27bbb9 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -189,12 +189,12 @@ const completeAdPushAgreementQuest = async () => { // TODO }; -const completeEventSharingOnInstagramQuest = async () => { - // TODO +const completeEventSharingOnInstagramQuest = async (userId) => { + return await completeQuest(userId, eventPeriod, quests.eventSharingOnInstagram); }; const completePurchaseSharingOnInstagramQuest = async () => { - // TODO + return await completeQuest(userId, eventPeriod, quests.purchaseSharingOnInstagram) }; module.exports = { diff --git a/src/lottery/modules/populates/publicNotice.js b/src/lottery/modules/populates/publicNotice.js new file mode 100644 index 00000000..5709ed7d --- /dev/null +++ b/src/lottery/modules/populates/publicNotice.js @@ -0,0 +1,11 @@ +const publicNoticePopulateOption = [ + { path: "event" }, + { + path: "item", + select: "name price description itemType", + }, +]; + +module.exports = { + publicNoticePopulateOption, +}; diff --git a/src/lottery/routes/publicNotice.js b/src/lottery/routes/publicNotice.js index 1adfc3fd..8f98e5bf 100644 --- a/src/lottery/routes/publicNotice.js +++ b/src/lottery/routes/publicNotice.js @@ -3,8 +3,8 @@ const express = require("express"); const router = express.Router(); const publicNoticeHandlers = require("../services/publicNotice"); -// 상점이용은 로그인을 요구합니다. -router.use(require("../../middlewares/auth")); -router.get("/get-recent-transaction", publicNotice.getRecentTransaction); +// 상점 공지는 로그인을 요구하지 않습니다. +router.get("/recent-transactions", publicNoticeHandlers.getRecentTransaction); +router.get("/leaderboard", publicNoticeHandlers.getTicketLeaderboardHandler); module.exports = router; diff --git a/src/lottery/routes/events.js b/src/lottery/routes/quests.js similarity index 86% rename from src/lottery/routes/events.js rename to src/lottery/routes/quests.js index 9f2e6b18..3a1a6f42 100644 --- a/src/lottery/routes/events.js +++ b/src/lottery/routes/quests.js @@ -1,6 +1,6 @@ const express = require("express"); const router = express.Router(); -const events = require("../services/events"); +const events = require("../services/quests"); router.use(require("../../middlewares/auth")); router.post("/instagram/share-event", events.instagramEventShareHandler); diff --git a/src/lottery/services/events.js b/src/lottery/services/events.js deleted file mode 100644 index 8ea9bedc..00000000 --- a/src/lottery/services/events.js +++ /dev/null @@ -1,32 +0,0 @@ -const logger = require("../../modules/logger"); -const eventHandler = require("../modules/events"); -// 인스타그램 스토리에 이벤트를 공유했을 때. -const instagramEventShareHandler = async (req, res) => { - try { - const userId = req.userOid; - const eventId = "650302f799c6f338d5ea5427"; - const transactionResult = await eventHandler(userId, eventId); - res.json({ result: !!transactionResult ? true : false }); - } catch (err) { - logger.err(err); - res.status(500).json({ error: "Events/Insagram/Share-Event" }); - } -}; - -// 인스타그램 스토리에 아이템 구매 내역을 공유했을 때. -const instagramPurchaseShareHandler = async (req, res) => { - try { - const userId = req.userOid; - const eventId = "6503030e99c6f338d5ea5433"; - const transactionResult = await eventHandler(userId, eventId); - res.json({ result: !!transactionResult ? true : false }); - } catch (err) { - logger.err(err); - res.status(500).json({ error: "Events/Insagram/Share-Purchase" }); - } -}; - -module.exports = { - instagramEventShareHandler, - instagramPurchaseShareHandler, -}; diff --git a/src/lottery/services/quests.js b/src/lottery/services/quests.js new file mode 100644 index 00000000..c1e6715e --- /dev/null +++ b/src/lottery/services/quests.js @@ -0,0 +1,31 @@ +const logger = require("../../modules/logger"); +const contracts = require("../modules/contracts/2023fall"); + +// 인스타그램 스토리에 이벤트를 공유했을 때. +const instagramEventShareHandler = async (req, res) => { + try { + const userId = req.userOid; + const contractResult = await contracts.completeEventSharingOnInstagramQuest(userId); + res.json({ result: !!contractResult ? true : false }); + } catch (err) { + logger.error(err); + res.status(500).json({ error: "Quests/Insagram/Share-Event" }); + } +}; + +// 인스타그램 스토리에 아이템 구매 내역을 공유했을 때. +const instagramPurchaseShareHandler = async (req, res) => { + try { + const userId = req.userOid; + const contractResult = await contracts.completePurchaseSharingOnInstagramQuest(userId); + res.json({ result: !!contractResult ? true : false }); + } catch (err) { + logger.error(err); + res.status(500).json({ error: "Quests/Insagram/Share-Purchase" }); + } +}; + +module.exports = { + instagramEventShareHandler, + instagramPurchaseShareHandler, +}; From 326d9d2dfe4176b882c53c36bdc0919e32ef5fc1 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 22:18:32 +0900 Subject: [PATCH 158/261] ADD: Merging process... --- src/lottery/index.js | 1 + src/lottery/modules/contracts/2023fall.js | 5 ++++ src/lottery/routes/publicNotice.js | 6 ++++ src/lottery/routes/{events.js => quests.js} | 2 +- src/lottery/services/quests.js | 31 +++++++++++++++++++++ 5 files changed, 44 insertions(+), 1 deletion(-) rename src/lottery/routes/{events.js => quests.js} (86%) create mode 100644 src/lottery/services/quests.js diff --git a/src/lottery/index.js b/src/lottery/index.js index 2329d28d..02a8cb23 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -22,6 +22,7 @@ lotteryRouter.use("/events", require("./routes/events")); lotteryRouter.use("/global-state", require("./routes/globalState")); lotteryRouter.use("/items", require("./routes/items")); lotteryRouter.use("/public-notice", require("./routes/publicNotice")); +lotteryRouter.use("/quests", require("./routes/quests")); const resources = [ eventStatusModel, diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 2665d84f..4899bf49 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -193,8 +193,13 @@ const completeEventSharingOnInstagramQuest = async (userId) => { return await completeQuest(userId, eventPeriod, quests.eventSharingOnInstagram); }; +<<<<<<< HEAD const completePurchaseSharingOnInstagramQuest = async (userId) => { return await completeQuest(userId, eventPeriod, quests.purchaseSharingOnInstagram); +======= +const completePurchaseSharingOnInstagramQuest = async () => { + return await completeQuest(userId, eventPeriod, quests.purchaseSharingOnInstagram) +>>>>>>> 6148984 (ADD: Merging process...) }; module.exports = { diff --git a/src/lottery/routes/publicNotice.js b/src/lottery/routes/publicNotice.js index dd86aeee..f8a8cade 100644 --- a/src/lottery/routes/publicNotice.js +++ b/src/lottery/routes/publicNotice.js @@ -10,7 +10,13 @@ router.get("/get-recent-transaction", publicNotice.getRecentTransaction); const router = express.Router(); const publicNoticeHandlers = require("../services/publicNotice"); +<<<<<<< HEAD router.get("/leaderboard", publicNoticeHandlers.getTicketLeaderboardHandler); >>>>>>> dev +======= +// 상점 공지는 로그인을 요구하지 않습니다. +router.get("/recent-transactions", publicNoticeHandlers.getRecentTransaction); +router.get("/leaderboard", publicNoticeHandlers.getTicketLeaderboardHandler); +>>>>>>> 6148984 (ADD: Merging process...) module.exports = router; diff --git a/src/lottery/routes/events.js b/src/lottery/routes/quests.js similarity index 86% rename from src/lottery/routes/events.js rename to src/lottery/routes/quests.js index 38d77cbd..5f6bfd50 100644 --- a/src/lottery/routes/events.js +++ b/src/lottery/routes/quests.js @@ -1,6 +1,6 @@ const express = require("express"); const router = express.Router(); -const events = require("../services/events"); +const events = require("../services/quests"); router.use(require("../../middlewares/auth")); router.post("/instagram/share-event", events.instagramEventShareHandler()); diff --git a/src/lottery/services/quests.js b/src/lottery/services/quests.js new file mode 100644 index 00000000..c1e6715e --- /dev/null +++ b/src/lottery/services/quests.js @@ -0,0 +1,31 @@ +const logger = require("../../modules/logger"); +const contracts = require("../modules/contracts/2023fall"); + +// 인스타그램 스토리에 이벤트를 공유했을 때. +const instagramEventShareHandler = async (req, res) => { + try { + const userId = req.userOid; + const contractResult = await contracts.completeEventSharingOnInstagramQuest(userId); + res.json({ result: !!contractResult ? true : false }); + } catch (err) { + logger.error(err); + res.status(500).json({ error: "Quests/Insagram/Share-Event" }); + } +}; + +// 인스타그램 스토리에 아이템 구매 내역을 공유했을 때. +const instagramPurchaseShareHandler = async (req, res) => { + try { + const userId = req.userOid; + const contractResult = await contracts.completePurchaseSharingOnInstagramQuest(userId); + res.json({ result: !!contractResult ? true : false }); + } catch (err) { + logger.error(err); + res.status(500).json({ error: "Quests/Insagram/Share-Purchase" }); + } +}; + +module.exports = { + instagramEventShareHandler, + instagramPurchaseShareHandler, +}; From 99cc4147e9fcc8152796d891469edb0a6f301aa4 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 22:18:32 +0900 Subject: [PATCH 159/261] ADD: Merging process... --- src/lottery/modules/contracts/2023fall.js | 18 ++++++++++-------- src/lottery/routes/publicNotice.js | 22 ++++++---------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 4899bf49..2b534a87 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -190,16 +190,18 @@ const completeAdPushAgreementQuest = async () => { }; const completeEventSharingOnInstagramQuest = async (userId) => { - return await completeQuest(userId, eventPeriod, quests.eventSharingOnInstagram); + return await completeQuest( + userId, + eventPeriod, + quests.eventSharingOnInstagram + ); }; - -<<<<<<< HEAD const completePurchaseSharingOnInstagramQuest = async (userId) => { - return await completeQuest(userId, eventPeriod, quests.purchaseSharingOnInstagram); -======= -const completePurchaseSharingOnInstagramQuest = async () => { - return await completeQuest(userId, eventPeriod, quests.purchaseSharingOnInstagram) ->>>>>>> 6148984 (ADD: Merging process...) + return await completeQuest( + userId, + eventPeriod, + quests.purchaseSharingOnInstagram + ); }; module.exports = { diff --git a/src/lottery/routes/publicNotice.js b/src/lottery/routes/publicNotice.js index f8a8cade..620d077a 100644 --- a/src/lottery/routes/publicNotice.js +++ b/src/lottery/routes/publicNotice.js @@ -1,22 +1,12 @@ const express = require("express"); -<<<<<<< HEAD -const router = express.Router(); -const publicNotice = require("../services/publicNotice"); - -// 로그인 없이 공지를 볼 수 있습니다. -router.get("/get-recent-transaction", publicNotice.getRecentTransaction); -======= - const router = express.Router(); const publicNoticeHandlers = require("../services/publicNotice"); -<<<<<<< HEAD -router.get("/leaderboard", publicNoticeHandlers.getTicketLeaderboardHandler); ->>>>>>> dev -======= -// 상점 공지는 로그인을 요구하지 않습니다. -router.get("/recent-transactions", publicNoticeHandlers.getRecentTransaction); -router.get("/leaderboard", publicNoticeHandlers.getTicketLeaderboardHandler); ->>>>>>> 6148984 (ADD: Merging process...) +// 상점이용은 로그인을 요구합니다. +router.use(require("../../middlewares/auth")); +router.get( + "/get-recent-transaction", + publicNoticeHandlers.getRecentTransaction +); module.exports = router; From 6fd370ab82a7a779064e77e9da05f8121ee17d6d Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 22:37:49 +0900 Subject: [PATCH 160/261] ADD: Merging process...... --- src/lottery/index.js | 1 - src/lottery/routes/publicNotice.js | 3 +-- src/lottery/routes/quests.js | 6 ++--- src/lottery/services/events.js | 43 ------------------------------ 4 files changed, 4 insertions(+), 49 deletions(-) delete mode 100644 src/lottery/services/events.js diff --git a/src/lottery/index.js b/src/lottery/index.js index 02a8cb23..5777b4e4 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -18,7 +18,6 @@ const lotteryRouter = express.Router(); lotteryRouter.use(require("../middlewares/originValidator")); // [Router] APIs -lotteryRouter.use("/events", require("./routes/events")); lotteryRouter.use("/global-state", require("./routes/globalState")); lotteryRouter.use("/items", require("./routes/items")); lotteryRouter.use("/public-notice", require("./routes/publicNotice")); diff --git a/src/lottery/routes/publicNotice.js b/src/lottery/routes/publicNotice.js index 620d077a..6481ae12 100644 --- a/src/lottery/routes/publicNotice.js +++ b/src/lottery/routes/publicNotice.js @@ -2,8 +2,7 @@ const express = require("express"); const router = express.Router(); const publicNoticeHandlers = require("../services/publicNotice"); -// 상점이용은 로그인을 요구합니다. -router.use(require("../../middlewares/auth")); +// 상점 공지 이용은 로그인을 요구하지 않습니다. router.get( "/get-recent-transaction", publicNoticeHandlers.getRecentTransaction diff --git a/src/lottery/routes/quests.js b/src/lottery/routes/quests.js index 5f6bfd50..45c662cd 100644 --- a/src/lottery/routes/quests.js +++ b/src/lottery/routes/quests.js @@ -1,12 +1,12 @@ const express = require("express"); const router = express.Router(); -const events = require("../services/quests"); +const quests = require("../services/quests"); router.use(require("../../middlewares/auth")); -router.post("/instagram/share-event", events.instagramEventShareHandler()); +router.post("/instagram/share-event", quests.instagramEventShareHandler()); router.post( "/instagram/share-purchase", - events.instagramPurchaseShareHandler() + quests.instagramPurchaseShareHandler() ); module.exports = router; diff --git a/src/lottery/services/events.js b/src/lottery/services/events.js deleted file mode 100644 index 6cba4de2..00000000 --- a/src/lottery/services/events.js +++ /dev/null @@ -1,43 +0,0 @@ -const logger = require("../../modules/logger"); -const contracts = require("../modules/contracts/2023fall"); - -// 인스타그램 스토리에 이벤트를 공유했을 때. -const instagramEventShareHandler = async (req, res) => { - try { - const contractsResult = contracts.completeEventSharingOnInstagramQuest(req.userOid); - if (contractsResult) { - res.json({ contractsResult }); - } else if (contractsResult === null) { - res.json({ contractsResult }); - } else { - logger.error(err); - res.json({ error: "Events/Insagram/Share-Event" }); - } - } catch (err) { - logger.error(err); - res.json({ error: "Events/Insagram/Share-Event" }); - } -}; - -// 인스타그램 스토리에 아이템 구매 내역을 공유했을 때. -const instagramPurchaseShareHandler = async (req, res) => { - try { - const contractsResult = contracts.completePurchaseSharingOnInstagramQuest(req.userOid); - if (contractsResult) { - res.json({ contractsResult }); - } else if (contractsResult === null) { - res.json({ contractsResult }); - } else { - logger.error(err); - return { error: "Events/Insagram/Share-Purchase" }; - } - } catch (err) { - logger.error(err); - return { error: "Events/Insagram/Share-Purchase" }; - } -}; - -module.exports = { - instagramEventShareHandler, - instagramPurchaseShareHandler, -}; From 5efa3eb3b0073cda9871d918c7d96ba7aa00c3e1 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 22:41:37 +0900 Subject: [PATCH 161/261] ADD: merging process --- loadenv.js | 2 +- src/lottery/modules/contracts/2023fall.js | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/loadenv.js b/loadenv.js index 2bce4fb9..715cbfeb 100644 --- a/loadenv.js +++ b/loadenv.js @@ -38,5 +38,5 @@ module.exports = { slackWebhookUrl: { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, - eventMode: undefined, + eventMode: "2023fall", }; diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index ae27bbb9..8bc6350a 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -190,11 +190,19 @@ const completeAdPushAgreementQuest = async () => { }; const completeEventSharingOnInstagramQuest = async (userId) => { - return await completeQuest(userId, eventPeriod, quests.eventSharingOnInstagram); + return await completeQuest( + userId, + eventPeriod, + quests.eventSharingOnInstagram + ); }; const completePurchaseSharingOnInstagramQuest = async () => { - return await completeQuest(userId, eventPeriod, quests.purchaseSharingOnInstagram) + return await completeQuest( + userId, + eventPeriod, + quests.purchaseSharingOnInstagram + ); }; module.exports = { From 89c96cfb0879a187205e4afb056e43b0d0396c3f Mon Sep 17 00:00:00 2001 From: static Date: Mon, 18 Sep 2023 22:44:28 +0900 Subject: [PATCH 162/261] Refactor: use ajv instead of express-validator --- src/lottery/routes/docs/itemsSchema.js | 11 +++++++++++ src/lottery/routes/items.js | 7 +++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/lottery/routes/docs/itemsSchema.js b/src/lottery/routes/docs/itemsSchema.js index 227f2b08..1601f237 100644 --- a/src/lottery/routes/docs/itemsSchema.js +++ b/src/lottery/routes/docs/itemsSchema.js @@ -75,6 +75,17 @@ const itemsSchema = { ...itemBase, description: "랜덤박스를 구입한 경우에만 포함됩니다.", }, + purchaseHandler: { + type: "object", + required: ["itemId"], + properties: { + itemId: { + type: "string", + pattern: "^[a-fA-F\\d]{24}$", + }, + }, + errorMessage: "validation: bad request", + }, }; module.exports = itemsSchema; diff --git a/src/lottery/routes/items.js b/src/lottery/routes/items.js index f4a39632..a87d0f78 100644 --- a/src/lottery/routes/items.js +++ b/src/lottery/routes/items.js @@ -4,15 +4,14 @@ const router = express.Router(); const itemsHandlers = require("../services/items"); const auth = require("../../middlewares/auth"); -const { param } = require("express-validator"); -const validator = require("../../middlewares/validator"); +const { validateParams } = require("../../middlewares/ajv"); +const itemsSchema = require("./docs/itemsSchema"); router.get("/list", itemsHandlers.listHandler); router.post( "/purchase/:itemId", auth, - param("itemId").isMongoId(), - validator, + validateParams(itemsSchema.purchaseHandler), itemsHandlers.purchaseHandler ); From fecbbc1ee5d3afd94b16b98fd219fa521b683663 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 22:46:12 +0900 Subject: [PATCH 163/261] ADD: Merging process! --- src/lottery/index.js | 1 + src/lottery/modules/contracts/2023fall.js | 3 ++- src/lottery/routes/publicNotice.js | 9 ++++----- src/lottery/services/quests.js | 7 +++++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/lottery/index.js b/src/lottery/index.js index 5777b4e4..38a99dce 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -19,6 +19,7 @@ lotteryRouter.use(require("../middlewares/originValidator")); // [Router] APIs lotteryRouter.use("/global-state", require("./routes/globalState")); +lotteryRouter.use("/transactions", require("./routes/transactions")); lotteryRouter.use("/items", require("./routes/items")); lotteryRouter.use("/public-notice", require("./routes/publicNotice")); lotteryRouter.use("/quests", require("./routes/quests")); diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 2b534a87..8bc6350a 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -196,7 +196,8 @@ const completeEventSharingOnInstagramQuest = async (userId) => { quests.eventSharingOnInstagram ); }; -const completePurchaseSharingOnInstagramQuest = async (userId) => { + +const completePurchaseSharingOnInstagramQuest = async () => { return await completeQuest( userId, eventPeriod, diff --git a/src/lottery/routes/publicNotice.js b/src/lottery/routes/publicNotice.js index 6481ae12..8f98e5bf 100644 --- a/src/lottery/routes/publicNotice.js +++ b/src/lottery/routes/publicNotice.js @@ -1,11 +1,10 @@ const express = require("express"); + const router = express.Router(); const publicNoticeHandlers = require("../services/publicNotice"); -// 상점 공지 이용은 로그인을 요구하지 않습니다. -router.get( - "/get-recent-transaction", - publicNoticeHandlers.getRecentTransaction -); +// 상점 공지는 로그인을 요구하지 않습니다. +router.get("/recent-transactions", publicNoticeHandlers.getRecentTransaction); +router.get("/leaderboard", publicNoticeHandlers.getTicketLeaderboardHandler); module.exports = router; diff --git a/src/lottery/services/quests.js b/src/lottery/services/quests.js index c1e6715e..ffe913cd 100644 --- a/src/lottery/services/quests.js +++ b/src/lottery/services/quests.js @@ -5,7 +5,9 @@ const contracts = require("../modules/contracts/2023fall"); const instagramEventShareHandler = async (req, res) => { try { const userId = req.userOid; - const contractResult = await contracts.completeEventSharingOnInstagramQuest(userId); + const contractResult = await contracts.completeEventSharingOnInstagramQuest( + userId + ); res.json({ result: !!contractResult ? true : false }); } catch (err) { logger.error(err); @@ -17,7 +19,8 @@ const instagramEventShareHandler = async (req, res) => { const instagramPurchaseShareHandler = async (req, res) => { try { const userId = req.userOid; - const contractResult = await contracts.completePurchaseSharingOnInstagramQuest(userId); + const contractResult = + await contracts.completePurchaseSharingOnInstagramQuest(userId); res.json({ result: !!contractResult ? true : false }); } catch (err) { logger.error(err); From 19e57970c3a0c829615fbaf4d4f2a1ed399d6ce9 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 22:47:55 +0900 Subject: [PATCH 164/261] ADD: Merging process!!! --- src/lottery/services/publicNotice.js | 72 +++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index ac4b2b9d..8e8227d9 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -1,7 +1,76 @@ +const { transactionModel } = require("../modules/stores/mongo"); const { eventStatusModel } = require("../modules/stores/mongo"); const { userModel } = require("../../modules/stores/mongo"); const logger = require("../../modules/logger"); -const { isLogin, getLoginInfo } = require("../../modules/auths/login"); +const { + publicNoticePopulateOption, +} = require("../modules/populates/publicNotice"); + +const getTransactions = async () => { + try { + const transactions = await transactionModel + .find({ type: "use" }) + .sort({ doneat: -1 }) + .limit(5) + .populate(publicNoticePopulateOption) + .lean(); + if (transactions) { + return await getTransactionsCallbackGetUser(transactions); + } else { + return undefined; + } + } catch (err) { + return undefined; + } +}; +const getTransactionsCallbackGetUser = async (transactions) => { + const users = await userModel.find(); + for (let user of users) { + for (let transaction of transactions) { + if (user._id.equals(transaction.userId)) { + transaction.id = user.id; + } + } + } + return transactions; +}; +const getRecentTransaction = async (req, res) => { + try { + let transactionListString = []; + await getTransactions(); + const transactions = await getTransactions(); + if (!!transactions) { + transactions.forEach((item, index) => { + let purchaceMessage = ""; + if (item.comment.includes("구매")) { + purchaceMessage = "구입하셨습니다."; + } else if (item.comment.includes("획득")) { + purchaceMessage = "뽑았습니다."; + } else { + purchaceMessage = "획득하셨습니다."; + } + transactionListString[index] = `${item.id + .toString() + .slice(0, 2)}${"*".repeat(item.id.length - 2)}님께서 ${ + item.item.name + }을(를) ${purchaceMessage}`; + }); + console.log(transactionListString); + res.json({ + transactionListString, + }); + } else { + res.status(500).json({ + error: "PublicNotice/get-recent-transaction : internal server error", + }); + } + } catch (err) { + logger.error(err); + res.status(500).json({ + error: "PublicNotice/get-recent-transaction : internal server error", + }); + } +}; const getTicketLeaderboardHandler = async (req, res) => { try { @@ -67,5 +136,6 @@ const getTicketLeaderboardHandler = async (req, res) => { }; module.exports = { + getRecentTransaction, getTicketLeaderboardHandler, }; From 47d9ce5964f5168d325ccf09c7abf29fe9ec41cd Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 22:54:27 +0900 Subject: [PATCH 165/261] ADD: Temporary commit --- src/lottery/routes/quests.js | 7 ++----- src/lottery/services/publicNotice.js | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lottery/routes/quests.js b/src/lottery/routes/quests.js index 45c662cd..a522596a 100644 --- a/src/lottery/routes/quests.js +++ b/src/lottery/routes/quests.js @@ -3,10 +3,7 @@ const router = express.Router(); const quests = require("../services/quests"); router.use(require("../../middlewares/auth")); -router.post("/instagram/share-event", quests.instagramEventShareHandler()); -router.post( - "/instagram/share-purchase", - quests.instagramPurchaseShareHandler() -); +router.post("/instagram/share-event", quests.instagramEventShareHandler); +router.post("/instagram/share-purchase", quests.instagramPurchaseShareHandler); module.exports = router; diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 8e8227d9..8d402799 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -36,6 +36,7 @@ const getTransactionsCallbackGetUser = async (transactions) => { }; const getRecentTransaction = async (req, res) => { try { + console.log("[CALLED]"); let transactionListString = []; await getTransactions(); const transactions = await getTransactions(); From a5d88f25d78227e00eecef4842e24161160e13d8 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 18 Sep 2023 23:31:03 +0900 Subject: [PATCH 166/261] ADD: Merging process~! --- src/lottery/modules/populates/publicNotice.js | 1 - src/lottery/services/publicNotice.js | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lottery/modules/populates/publicNotice.js b/src/lottery/modules/populates/publicNotice.js index 5709ed7d..a821bfd5 100644 --- a/src/lottery/modules/populates/publicNotice.js +++ b/src/lottery/modules/populates/publicNotice.js @@ -1,5 +1,4 @@ const publicNoticePopulateOption = [ - { path: "event" }, { path: "item", select: "name price description itemType", diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 8d402799..a2bc53bb 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -1,6 +1,7 @@ const { transactionModel } = require("../modules/stores/mongo"); const { eventStatusModel } = require("../modules/stores/mongo"); const { userModel } = require("../../modules/stores/mongo"); +const { isLogin, getLoginInfo } = require("../../modules/auths/login"); const logger = require("../../modules/logger"); const { publicNoticePopulateOption, @@ -36,7 +37,6 @@ const getTransactionsCallbackGetUser = async (transactions) => { }; const getRecentTransaction = async (req, res) => { try { - console.log("[CALLED]"); let transactionListString = []; await getTransactions(); const transactions = await getTransactions(); @@ -56,7 +56,6 @@ const getRecentTransaction = async (req, res) => { item.item.name }을(를) ${purchaceMessage}`; }); - console.log(transactionListString); res.json({ transactionListString, }); From 9632c46dc6f76e2f0fadfc26a902037f6b6ed4cf Mon Sep 17 00:00:00 2001 From: static Date: Tue, 19 Sep 2023 00:11:36 +0900 Subject: [PATCH 167/261] Fix: typo of the id of nicknameChangingQuest --- src/lottery/modules/contracts/2023fall.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index ba656d4f..6bbe0e43 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -44,7 +44,7 @@ const quests = buildQuests({ reward: 50, maxCount: 3, }, - nicknameChaning: { + nicknameChanging: { name: "닉네임 변경", description: "", imageUrl: "", @@ -173,7 +173,7 @@ const completeSendingQuest = async (userId, timestamp, roomObject) => { }; /** - * nicknameChaning 퀘스트의 완료를 요청합니다. + * nicknameChanging 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @returns {Promise} @@ -185,7 +185,7 @@ const completeNicknameChangingQuest = async (userId, timestamp) => { userId, timestamp, eventPeriod, - quests.nicknameChaning + quests.nicknameChanging ); }; From 52108698057515934415797b568f901fcd81263b Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 19 Sep 2023 00:47:29 +0900 Subject: [PATCH 168/261] Add: TransactionSchema itemType attribute added. --- src/lottery/modules/populates/publicNotice.js | 4 +++ src/lottery/modules/stores/mongo.js | 4 +++ .../routes/docs/{events.js => quests.js} | 0 src/lottery/routes/docs/swaggerDocs.js | 8 ++--- src/lottery/routes/quests.js | 1 + src/lottery/services/items.js | 2 ++ src/lottery/services/publicNotice.js | 35 ++++--------------- 7 files changed, 19 insertions(+), 35 deletions(-) rename src/lottery/routes/docs/{events.js => quests.js} (100%) diff --git a/src/lottery/modules/populates/publicNotice.js b/src/lottery/modules/populates/publicNotice.js index a821bfd5..d662e5fa 100644 --- a/src/lottery/modules/populates/publicNotice.js +++ b/src/lottery/modules/populates/publicNotice.js @@ -1,4 +1,8 @@ const publicNoticePopulateOption = [ + { + path: "userId", + select: "id", + }, { path: "item", select: "name price description itemType", diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 296db681..2e32978d 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -118,6 +118,10 @@ const transactionSchema = Schema({ type: Schema.Types.ObjectId, ref: "Item", }, + itemType: { + type: Number, + enum: [0, 1, 2, 3], + }, comment: { type: String, required: true, diff --git a/src/lottery/routes/docs/events.js b/src/lottery/routes/docs/quests.js similarity index 100% rename from src/lottery/routes/docs/events.js rename to src/lottery/routes/docs/quests.js diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index f2a2e5ef..8136a470 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -2,7 +2,7 @@ const { eventMode } = require("../../../../loadenv"); const globalStateDocs = require("./globalState"); const itemsDocs = require("./items"); const transactionsDocs = require("./transactions"); -const eventsDocs = require("./events"); +const questsDocs = require("./quests"); const itemsSchema = require("./itemsSchema"); const publicNoticeDocs = require("./publicNotice"); @@ -30,16 +30,12 @@ const eventSwaggerDocs = { name: `${apiPrefix}/public-notice`, description: "이벤트 - 아이템 구매, 뽑기, 획득 공지 관련 API", }, - { - name: `${apiPrefix}/public-notice`, - description: "이벤트 - 공지사항 관련 API", - }, ], paths: { ...globalStateDocs, ...itemsDocs, ...transactionsDocs, - ...eventsDocs, + ...questsDocs, ...publicNoticeDocs, }, components: { diff --git a/src/lottery/routes/quests.js b/src/lottery/routes/quests.js index a522596a..f8cf8177 100644 --- a/src/lottery/routes/quests.js +++ b/src/lottery/routes/quests.js @@ -3,6 +3,7 @@ const router = express.Router(); const quests = require("../services/quests"); router.use(require("../../middlewares/auth")); + router.post("/instagram/share-event", quests.instagramEventShareHandler); router.post("/instagram/share-purchase", quests.instagramPurchaseShareHandler); diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 36b4bf10..1a11e5ba 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -91,6 +91,7 @@ const getRandomItem = async (req, depth) => { amount: 0, userId: req.userOid, item: randomItem._id, + itemType: randomItem.itemType, comment: `랜덤 박스에서 "${randomItem.name}" 1개를 획득했습니다.`, }); await transaction.save(); @@ -173,6 +174,7 @@ const purchaseHandler = async (req, res) => { amount: item.price, userId: req.userOid, item: item._id, + itemType: item.itemType, comment: `송편 ${item.price}개를 사용해 "${item.name}" 1개를 획득했습니다.`, }); await transaction.save(); diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index a2bc53bb..b86406f7 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -7,39 +7,15 @@ const { publicNoticePopulateOption, } = require("../modules/populates/publicNotice"); -const getTransactions = async () => { +const getRecentTransaction = async (req, res) => { try { + let transactionListString = []; const transactions = await transactionModel .find({ type: "use" }) - .sort({ doneat: -1 }) + .sort({ createAt: -1 }) .limit(5) .populate(publicNoticePopulateOption) .lean(); - if (transactions) { - return await getTransactionsCallbackGetUser(transactions); - } else { - return undefined; - } - } catch (err) { - return undefined; - } -}; -const getTransactionsCallbackGetUser = async (transactions) => { - const users = await userModel.find(); - for (let user of users) { - for (let transaction of transactions) { - if (user._id.equals(transaction.userId)) { - transaction.id = user.id; - } - } - } - return transactions; -}; -const getRecentTransaction = async (req, res) => { - try { - let transactionListString = []; - await getTransactions(); - const transactions = await getTransactions(); if (!!transactions) { transactions.forEach((item, index) => { let purchaceMessage = ""; @@ -50,12 +26,13 @@ const getRecentTransaction = async (req, res) => { } else { purchaceMessage = "획득하셨습니다."; } - transactionListString[index] = `${item.id + transactionListString[index] = `${item.userId.id .toString() - .slice(0, 2)}${"*".repeat(item.id.length - 2)}님께서 ${ + .slice(0, 2)}${"*".repeat(item.userId.id.length - 2)}님께서 ${ item.item.name }을(를) ${purchaceMessage}`; }); + console.log(transactionListString); res.json({ transactionListString, }); From b40f2dcbf5ab11d3a41463eca9dd3d542c7f531f Mon Sep 17 00:00:00 2001 From: static Date: Tue, 19 Sep 2023 01:27:11 +0900 Subject: [PATCH 169/261] Refactor: apply @14KGun 's suggestions --- src/lottery/routes/globalState.js | 7 ++++-- src/lottery/routes/items.js | 6 +++-- src/lottery/services/globalState.js | 38 ++++++++++++++--------------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/lottery/routes/globalState.js b/src/lottery/routes/globalState.js index e36828a1..d0461218 100644 --- a/src/lottery/routes/globalState.js +++ b/src/lottery/routes/globalState.js @@ -2,9 +2,12 @@ const express = require("express"); const router = express.Router(); const globalStateHandlers = require("../services/globalState"); -const auth = require("../../middlewares/auth"); router.get("/", globalStateHandlers.getUserGlobalStateHandler); -router.post("/create", auth, globalStateHandlers.createUserGlobalStateHandler); + +// 아래의 Endpoint 접근 시 로그인 필요 +router.use(require("../../middlewares/auth")); + +router.post("/create", globalStateHandlers.createUserGlobalStateHandler); module.exports = router; diff --git a/src/lottery/routes/items.js b/src/lottery/routes/items.js index a87d0f78..95136a5f 100644 --- a/src/lottery/routes/items.js +++ b/src/lottery/routes/items.js @@ -2,15 +2,17 @@ const express = require("express"); const router = express.Router(); const itemsHandlers = require("../services/items"); -const auth = require("../../middlewares/auth"); const { validateParams } = require("../../middlewares/ajv"); const itemsSchema = require("./docs/itemsSchema"); router.get("/list", itemsHandlers.listHandler); + +// 아래의 Endpoint 접근 시 로그인 필요 +router.use(require("../../middlewares/auth")); + router.post( "/purchase/:itemId", - auth, validateParams(itemsSchema.purchaseHandler), itemsHandlers.purchaseHandler ); diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 0b0a1cc1..804cc160 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -10,27 +10,25 @@ const quests = contract ? Object.values(contract.quests) : undefined; const getUserGlobalStateHandler = async (req, res) => { try { - const result = { - isAgree: false, - creditAmount: 0, - completedQuests: [], - ticket1Amount: 0, - ticket2Amount: 0, - quests, - }; - const userId = isLogin(req) ? getLoginInfo(req).oid : null; - if (!userId) return res.json(result); - - const eventStatus = await eventStatusModel.findOne({ userId }).lean(); - if (eventStatus) { - result.isAgree = true; - result.creditAmount = eventStatus.creditAmount; - result.ticket1Amount = eventStatus.ticket1Amount; - result.ticket2Amount = eventStatus.ticket2Amount; - } - - res.json(result); + const eventStatus = + userId && + (await eventStatusModel.findOne({ userId }, "-_id -userId -__v").lean()); + if (eventStatus) + return res.json({ + isAgree: true, + ...eventStatus, + quests, + }); + else + return res.json({ + isAgree: false, + completedQuests: [], + creditAmount: 0, + ticket1Amount: 0, + ticket2Amount: 0, + quests, + }); } catch (err) { logger.error(err); res.status(500).json({ error: "GlobalState/ : internal server error" }); From 1c5850f179222da2aa68a903f904926abedd08aa Mon Sep 17 00:00:00 2001 From: static Date: Tue, 19 Sep 2023 01:49:02 +0900 Subject: [PATCH 170/261] Add: detect adPushAgreementQuest --- src/lottery/modules/contracts/2023fall.js | 24 +++++++++++++++++++++-- src/services/notifications.js | 10 ++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 6bbe0e43..4b29041e 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -209,8 +209,28 @@ const completeAccountChangingQuest = async (userId, timestamp, newAccount) => { ); }; -const completeAdPushAgreementQuest = async () => { - // TODO +/** + * adPushAgreementQuest 퀘스트의 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. + * @param {boolean} advertisement - 변경된 광고성 알림 수신 동의 여부입니다. + * @returns {Promise} + * @description 알림 옵션을 변경할 때마다 호출해 주세요. + * @usage notifications/editOptionsHandler + */ +const completeAdPushAgreementQuest = async ( + userId, + timestamp, + advertisement +) => { + if (!advertisement) return null; + + return await completeQuest( + userId, + timestamp, + eventPeriod, + quests.adPushAgreement + ); }; const completeEventSharingOnInstagramQuest = async () => { diff --git a/src/services/notifications.js b/src/services/notifications.js index 799c8bb7..633f6739 100644 --- a/src/services/notifications.js +++ b/src/services/notifications.js @@ -4,6 +4,9 @@ const logger = require("../modules/logger"); const { registerDeviceToken, validateDeviceToken } = require("../modules/fcm"); +// 이벤트 코드입니다. +const { contracts } = require("../lottery"); + const registerDeviceTokenHandler = async (req, res) => { try { // 해당 FCM device token이 유효한지 검사합니다. @@ -104,6 +107,13 @@ const editOptionsHandler = async (req, res) => { .send("Notification/editOptions: deviceToken not found"); } + // 이벤트 코드입니다. + await contracts?.completeAdPushAgreementQuest( + req.userOid, + req.timestamp, + options.advertisement + ); + res.status(200).json(updatedNotificationOptions); } catch (err) { logger.error(err); From cd6370a8e0d4c7c9bd6678072d07d58de908e856 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 19 Sep 2023 02:29:30 +0900 Subject: [PATCH 171/261] Docs: update type of timestamp parameter of completeQuest function --- src/lottery/modules/contracts/2023fall.js | 16 ++++++++-------- src/lottery/modules/quests.js | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 4b29041e..abe2f0d8 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -84,7 +84,7 @@ const eventPeriod = { /** * firstLogin 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. - * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. + * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @returns {Promise} * @usage lottery/globalState/createUserGlobalStateHandler */ @@ -94,7 +94,7 @@ const completeFirstLoginQuest = async (userId, timestamp) => { /** * payingAndSending 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이거나, 모든 참가자가 정산 또는 송금을 완료하지 않았다면 요청하지 않습니다. - * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. + * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @param {number} roomObject.settlementTotal - 정산 또는 송금이 완료된 참여자 수입니다. @@ -122,7 +122,7 @@ const completePayingAndSendingQuest = async (timestamp, roomObject) => { /** * firstRoomCreation 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. - * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. + * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @returns {Promise} * @description 방을 만들 때마다 호출해 주세요. * @usage rooms/createHandler @@ -143,7 +143,7 @@ const completeRoomSharingQuest = async () => { /** * paying 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. - * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. + * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} @@ -159,7 +159,7 @@ const completePayingQuest = async (userId, timestamp, roomObject) => { /** * sending 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. - * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. + * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} @@ -175,7 +175,7 @@ const completeSendingQuest = async (userId, timestamp, roomObject) => { /** * nicknameChanging 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. - * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. + * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @returns {Promise} * @description 닉네임을 변경할 때마다 호출해 주세요. * @usage users/editNicknameHandler @@ -192,7 +192,7 @@ const completeNicknameChangingQuest = async (userId, timestamp) => { /** * accountChanging 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. - * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. + * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {string} newAccount - 변경된 계좌입니다. * @returns {Promise} * @description 계좌를 변경할 때마다 호출해 주세요. @@ -212,7 +212,7 @@ const completeAccountChangingQuest = async (userId, timestamp, newAccount) => { /** * adPushAgreementQuest 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. - * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. + * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {boolean} advertisement - 변경된 광고성 알림 수신 동의 여부입니다. * @returns {Promise} * @description 알림 옵션을 변경할 때마다 호출해 주세요. diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index 4ec5c4f3..ce2d22d4 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -44,7 +44,7 @@ const buildQuests = (quests) => { /** * 퀘스트 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. - * @param {Date} timestamp - 퀘스트 완료를 요청한 시각입니다. + * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {Object} eventPeriod - 이벤트의 기간입니다. * @param {Date} eventPeriod.start - 이벤트의 시작 시각(Inclusive)입니다. * @param {Date} eventPeriod.end - 이벤트의 종료 시각(Exclusive)입니다. From 614a716ff088769f4db45e9980ec2825d1ddf446 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 19 Sep 2023 02:37:31 +0900 Subject: [PATCH 172/261] Refactor: rename isAgree to isAgreeOnTermsOfEvent --- src/lottery/routes/docs/globalState.js | 2 +- src/lottery/services/globalState.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 12fd93bb..6883ac33 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -24,7 +24,7 @@ globalStateDocs[`${apiPrefix}/`] = { "quests", ], properties: { - isAgree: { + isAgreeOnTermsOfEvent: { type: "boolean", description: "유저의 이벤트 참여 동의 여부", example: true, diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 804cc160..7c7f7c5f 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -16,13 +16,13 @@ const getUserGlobalStateHandler = async (req, res) => { (await eventStatusModel.findOne({ userId }, "-_id -userId -__v").lean()); if (eventStatus) return res.json({ - isAgree: true, + isAgreeOnTermsOfEvent: true, ...eventStatus, quests, }); else return res.json({ - isAgree: false, + isAgreeOnTermsOfEvent: false, completedQuests: [], creditAmount: 0, ticket1Amount: 0, From fdc18d78826e34579c932e658a209fe6b6de4055 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Tue, 19 Sep 2023 12:22:31 +0000 Subject: [PATCH 173/261] Refactor: chat.js --- src/modules/socket.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/modules/socket.js b/src/modules/socket.js index 8e897c3b..bca99161 100644 --- a/src/modules/socket.js +++ b/src/modules/socket.js @@ -219,14 +219,9 @@ const emitUpdateEvent = async (io, roomId) => { throw new IllegalArgumentsException(); } - const userIds = part.map((participant) => participant.user); - await Promise.all( - userIds.map(async (userId) => - io.in(`user-${userId}`).emit("chat_update", { - roomId, - }) - ) - ); + part.forEach(({ user }) => io.in(`user-${user}`).emit("chat_update"), { + roomId, + }); return true; } catch (err) { From 6f9d1a96a9fb3cdb0896b4f0321c7a6f0c14c7ce Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Tue, 19 Sep 2023 12:23:23 +0000 Subject: [PATCH 174/261] Remove: readAt default value --- src/modules/stores/mongo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 0d29108c..bd73367f 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -34,7 +34,7 @@ const participantSchema = Schema({ enum: ["not-departed", "paid", "send-required", "sent"], default: "not-departed", }, - readAt: { type: Date, default: () => Date.now() }, + readAt: { type: Date }, }); const deviceTokenSchema = Schema({ From 8f64026b2c4a02f4a00741a4c08741dc95d197eb Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Tue, 19 Sep 2023 12:24:22 +0000 Subject: [PATCH 175/261] Fix: change Date.now() and remove isPart --- src/services/chats.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/services/chats.js b/src/services/chats.js index 8973c24f..34c32fc0 100644 --- a/src/services/chats.js +++ b/src/services/chats.js @@ -188,12 +188,6 @@ const readChatHandler = async (req, res) => { if (!io) { return res.status(403).send("Chat/read : socket did not connected"); } - const isPart = await isUserInRoom(userId, roomId); - if (!isPart) { - return res - .status(403) - .send("Chat/read : user did not participated in the room"); - } const roomObject = await roomModel .findOneAndUpdate( @@ -206,7 +200,7 @@ const readChatHandler = async (req, res) => { }, }, { - $set: { "part.$[updater].readAt": Date.now() }, + $set: { "part.$[updater].readAt": req.timestamp }, }, { new: true, From 4a93b09bc5b492b4df6c0b8cfd9296795ff732ed Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 19 Sep 2023 22:05:44 +0900 Subject: [PATCH 176/261] Add: Resolve comments --- src/lottery/routes/docs/quests.js | 8 +++----- src/lottery/routes/publicNotice.js | 5 ++++- src/lottery/services/publicNotice.js | 8 ++++---- src/lottery/services/quests.js | 14 +++++++++----- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/lottery/routes/docs/quests.js b/src/lottery/routes/docs/quests.js index 9998e4ce..2663f277 100644 --- a/src/lottery/routes/docs/quests.js +++ b/src/lottery/routes/docs/quests.js @@ -1,8 +1,8 @@ const { eventMode } = require("../../../../loadenv"); -const apiPrefix = `/events/${eventMode}/instagram`; +const apiPrefix = `/events/${eventMode}/quests`; const eventsDocs = {}; -eventsDocs[`${apiPrefix}/share-event`] = { +eventsDocs[`${apiPrefix}/instagram/share-event`] = { post: { tags: [`${apiPrefix}`], summary: "이벤트 공유시 보상 반환", @@ -19,12 +19,11 @@ eventsDocs[`${apiPrefix}/share-event`] = { }, }, }, - 500: { error: "Events/Insagram/Share-Event" }, }, }, }; -eventsDocs[`${apiPrefix}/share-purchase`] = { +eventsDocs[`${apiPrefix}/instagram/share-purchase`] = { post: { tags: [`${apiPrefix}`], summary: "이벤트 공유시 보상 반환", @@ -41,7 +40,6 @@ eventsDocs[`${apiPrefix}/share-purchase`] = { }, }, }, - 500: { error: "Events/Insagram/Share-Purchase" }, }, }, }; diff --git a/src/lottery/routes/publicNotice.js b/src/lottery/routes/publicNotice.js index 8f98e5bf..79fc17ab 100644 --- a/src/lottery/routes/publicNotice.js +++ b/src/lottery/routes/publicNotice.js @@ -4,7 +4,10 @@ const router = express.Router(); const publicNoticeHandlers = require("../services/publicNotice"); // 상점 공지는 로그인을 요구하지 않습니다. -router.get("/recent-transactions", publicNoticeHandlers.getRecentTransaction); +router.get( + "/recent-transactions", + publicNoticeHandlers.getRecentPurchaceItemListHandler +); router.get("/leaderboard", publicNoticeHandlers.getTicketLeaderboardHandler); module.exports = router; diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index b86406f7..930df9e3 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -7,7 +7,7 @@ const { publicNoticePopulateOption, } = require("../modules/populates/publicNotice"); -const getRecentTransaction = async (req, res) => { +const getRecentPurchaceItemListHandler = async (req, res) => { try { let transactionListString = []; const transactions = await transactionModel @@ -19,9 +19,9 @@ const getRecentTransaction = async (req, res) => { if (!!transactions) { transactions.forEach((item, index) => { let purchaceMessage = ""; - if (item.comment.includes("구매")) { + if (item.comment.startsWith("송편")) { purchaceMessage = "구입하셨습니다."; - } else if (item.comment.includes("획득")) { + } else if (item.comment.startsWith("랜덤 박스")) { purchaceMessage = "뽑았습니다."; } else { purchaceMessage = "획득하셨습니다."; @@ -113,6 +113,6 @@ const getTicketLeaderboardHandler = async (req, res) => { }; module.exports = { - getRecentTransaction, + getRecentPurchaceItemListHandler, getTicketLeaderboardHandler, }; diff --git a/src/lottery/services/quests.js b/src/lottery/services/quests.js index ffe913cd..68be355a 100644 --- a/src/lottery/services/quests.js +++ b/src/lottery/services/quests.js @@ -4,14 +4,16 @@ const contracts = require("../modules/contracts/2023fall"); // 인스타그램 스토리에 이벤트를 공유했을 때. const instagramEventShareHandler = async (req, res) => { try { - const userId = req.userOid; + const { userOid: userId } = req; const contractResult = await contracts.completeEventSharingOnInstagramQuest( userId ); - res.json({ result: !!contractResult ? true : false }); + res.json({ result: !!contractResult }); } catch (err) { logger.error(err); - res.status(500).json({ error: "Quests/Insagram/Share-Event" }); + res + .status(500) + .json({ error: "Quests/Instagram/ShareEvent: internal server error" }); } }; @@ -21,10 +23,12 @@ const instagramPurchaseShareHandler = async (req, res) => { const userId = req.userOid; const contractResult = await contracts.completePurchaseSharingOnInstagramQuest(userId); - res.json({ result: !!contractResult ? true : false }); + res.json({ result: !!contractResult }); } catch (err) { logger.error(err); - res.status(500).json({ error: "Quests/Insagram/Share-Purchase" }); + res + .status(500) + .json({ error: "Quests/Instagram/SharePurchase: internal server error" }); } }; From 73c5068c2bd0a4fcd47cef897c67537de4fed5a8 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 19 Sep 2023 22:13:52 +0900 Subject: [PATCH 177/261] Add: eventConfig to .env --- app.js | 9 +++-- loadenv.js | 2 +- src/lottery/index.js | 7 ++-- src/lottery/modules/contracts/2023fall.js | 41 ++++------------------- src/lottery/modules/quests.js | 13 ++++--- src/lottery/routes/docs/globalState.js | 4 +-- src/lottery/routes/docs/items.js | 4 +-- src/lottery/routes/docs/publicNotice.js | 4 +-- src/lottery/routes/docs/swaggerDocs.js | 4 +-- src/lottery/routes/docs/transactions.js | 4 +-- src/lottery/services/globalState.js | 11 +++--- src/lottery/services/items.js | 14 +++++--- src/routes/admin.js | 4 +-- 13 files changed, 52 insertions(+), 69 deletions(-) diff --git a/app.js b/app.js index d8a1f0f3..689de7a9 100644 --- a/app.js +++ b/app.js @@ -1,7 +1,7 @@ // 모듈 require const express = require("express"); const http = require("http"); -const { port: httpPort, eventMode } = require("./loadenv"); +const { port: httpPort, eventConfig } = require("./loadenv"); const logger = require("./src/modules/logger"); const { connectDatabase } = require("./src/modules/stores/mongo"); const { startSocketServer } = require("./src/modules/socket"); @@ -43,8 +43,11 @@ app.use(require("./src/middlewares/limitRate")); app.use("/docs", require("./src/routes/docs")); // 2023 추석 이벤트 전용 라우터입니다. -eventMode && - app.use(`/events/${eventMode}`, require("./src/lottery").lotteryRouter); +eventConfig && + app.use( + `/events/${eventConfig.mode}`, + require("./src/lottery").lotteryRouter + ); // [Middleware] 모든 API 요청에 대하여 origin 검증 app.use(require("./src/middlewares/originValidator")); diff --git a/loadenv.js b/loadenv.js index 2bce4fb9..1f9528b6 100644 --- a/loadenv.js +++ b/loadenv.js @@ -38,5 +38,5 @@ module.exports = { slackWebhookUrl: { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, - eventMode: undefined, + eventConfig: process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG), // optional }; diff --git a/src/lottery/index.js b/src/lottery/index.js index 7ca282fe..faed346b 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -6,11 +6,11 @@ const { transactionModel, } = require("./modules/stores/mongo"); -const { eventMode } = require("../../loadenv"); +const { eventConfig } = require("../../loadenv"); const { buildResource } = require("../modules/adminResource"); // [Routes] 기존 docs 라우터의 docs extend -require("./routes/docs")(); +eventConfig && require("./routes/docs")(); const lotteryRouter = express.Router(); @@ -30,7 +30,8 @@ const resources = [ transactionModel, ].map(buildResource()); -const contracts = eventMode && require(`./modules/contracts/${eventMode}`); +const contracts = + eventConfig && require(`./modules/contracts/${eventConfig.mode}`); module.exports = { lotteryRouter, diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index abe2f0d8..650c0d76 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -76,11 +76,6 @@ const quests = buildQuests({ }, }); -const eventPeriod = { - start: new Date("2023-09-25T00:00:00+09:00"), // Inclusive - end: new Date("2023-10-10T00:00:00+09:00"), // Exclusive -}; - /** * firstLogin 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. @@ -89,7 +84,7 @@ const eventPeriod = { * @usage lottery/globalState/createUserGlobalStateHandler */ const completeFirstLoginQuest = async (userId, timestamp) => { - return await completeQuest(userId, timestamp, eventPeriod, quests.firstLogin); + return await completeQuest(userId, timestamp, quests.firstLogin); }; /** @@ -112,7 +107,6 @@ const completePayingAndSendingQuest = async (timestamp, roomObject) => { await completeQuest( participant.user._id, timestamp, - eventPeriod, quests.payingAndSending ) ) @@ -128,12 +122,7 @@ const completePayingAndSendingQuest = async (timestamp, roomObject) => { * @usage rooms/createHandler */ const completeFirstRoomCreationQuest = async (userId, timestamp) => { - return await completeQuest( - userId, - timestamp, - eventPeriod, - quests.firstRoomCreation - ); + return await completeQuest(userId, timestamp, quests.firstRoomCreation); }; const completeRoomSharingQuest = async () => { @@ -153,7 +142,7 @@ const completeRoomSharingQuest = async () => { const completePayingQuest = async (userId, timestamp, roomObject) => { if (roomObject.part.length < 2) return null; - return await completeQuest(userId, timestamp, eventPeriod, quests.paying); + return await completeQuest(userId, timestamp, quests.paying); }; /** @@ -169,7 +158,7 @@ const completePayingQuest = async (userId, timestamp, roomObject) => { const completeSendingQuest = async (userId, timestamp, roomObject) => { if (roomObject.part.length < 2) return null; - return await completeQuest(userId, timestamp, eventPeriod, quests.sending); + return await completeQuest(userId, timestamp, quests.sending); }; /** @@ -181,12 +170,7 @@ const completeSendingQuest = async (userId, timestamp, roomObject) => { * @usage users/editNicknameHandler */ const completeNicknameChangingQuest = async (userId, timestamp) => { - return await completeQuest( - userId, - timestamp, - eventPeriod, - quests.nicknameChanging - ); + return await completeQuest(userId, timestamp, quests.nicknameChanging); }; /** @@ -201,12 +185,7 @@ const completeNicknameChangingQuest = async (userId, timestamp) => { const completeAccountChangingQuest = async (userId, timestamp, newAccount) => { if (newAccount === "") return null; - return await completeQuest( - userId, - timestamp, - eventPeriod, - quests.accountChanging - ); + return await completeQuest(userId, timestamp, quests.accountChanging); }; /** @@ -225,12 +204,7 @@ const completeAdPushAgreementQuest = async ( ) => { if (!advertisement) return null; - return await completeQuest( - userId, - timestamp, - eventPeriod, - quests.adPushAgreement - ); + return await completeQuest(userId, timestamp, quests.adPushAgreement); }; const completeEventSharingOnInstagramQuest = async () => { @@ -243,7 +217,6 @@ const completePurchaseSharingOnInstagramQuest = async () => { module.exports = { quests, - eventPeriod, completeFirstLoginQuest, completePayingAndSendingQuest, completeFirstRoomCreationQuest, diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index ce2d22d4..9aa5c77f 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -7,6 +7,12 @@ const { const logger = require("../../modules/logger"); const mongoose = require("mongoose"); +const { eventConfig } = require("../../../loadenv"); +const eventPeriod = { + startAt: new Date(eventConfig.startAt), + endAt: new Date(eventConfig.endAt), +}; + const requiredQuestFields = ["name", "description", "imageUrl", "reward"]; const buildQuests = (quests) => { for (const [id, quest] of Object.entries(quests)) { @@ -45,9 +51,6 @@ const buildQuests = (quests) => { * 퀘스트 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. - * @param {Object} eventPeriod - 이벤트의 기간입니다. - * @param {Date} eventPeriod.start - 이벤트의 시작 시각(Inclusive)입니다. - * @param {Date} eventPeriod.end - 이벤트의 종료 시각(Exclusive)입니다. * @param {Object} quest - 퀘스트의 정보입니다. * @param {string} quest.id - 퀘스트의 Id입니다. * @param {string} quest.name - 퀘스트의 이름입니다. @@ -57,14 +60,14 @@ const buildQuests = (quests) => { * @param {number} quest.maxCount - 퀘스트의 최대 완료 가능 횟수입니다. * @returns {Object|null} 성공한 경우 Object를, 실패한 경우 null을 반환합니다. 이미 최대 완료 횟수에 도달했거나, 퀘스트가 원격으로 비활성화 된 경우에도 실패로 처리됩니다. */ -const completeQuest = async (userId, timestamp, eventPeriod, quest) => { +const completeQuest = async (userId, timestamp, quest) => { try { // 1단계: 유저의 EventStatus를 가져옵니다. const eventStatus = await eventStatusModel.findOne({ userId }).lean(); if (!eventStatus) return null; // 2단계: 이벤트 기간인지 확인합니다. - if (timestamp >= eventPeriod.end || timestamp < eventPeriod.start) { + if (timestamp >= eventPeriod.endAt || timestamp < eventPeriod.startAt) { logger.info( `User ${userId} failed to complete auto-disabled ${quest.id}Quest` ); diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 6883ac33..ffaddf3a 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -1,5 +1,5 @@ -const { eventMode } = require("../../../../loadenv"); -const apiPrefix = `/events/${eventMode}/global-state`; +const { eventConfig } = require("../../../../loadenv"); +const apiPrefix = `/events/${eventConfig.mode}/global-state`; const globalStateDocs = {}; globalStateDocs[`${apiPrefix}/`] = { diff --git a/src/lottery/routes/docs/items.js b/src/lottery/routes/docs/items.js index d1c0956e..4079795a 100644 --- a/src/lottery/routes/docs/items.js +++ b/src/lottery/routes/docs/items.js @@ -1,5 +1,5 @@ -const { eventMode } = require("../../../../loadenv"); -const apiPrefix = `/events/${eventMode}/items`; +const { eventConfig } = require("../../../../loadenv"); +const apiPrefix = `/events/${eventConfig.mode}/items`; const itemsDocs = {}; itemsDocs[`${apiPrefix}/list`] = { diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js index cda93fa1..649ac5a6 100644 --- a/src/lottery/routes/docs/publicNotice.js +++ b/src/lottery/routes/docs/publicNotice.js @@ -1,5 +1,5 @@ -const { eventMode } = require("../../../../loadenv"); -const apiPrefix = `/events/${eventMode}/public-notice`; +const { eventConfig } = require("../../../../loadenv"); +const apiPrefix = `/events/${eventConfig.mode}/public-notice`; const publicNoticeDocs = {}; publicNoticeDocs[`${apiPrefix}/leaderboard`] = { diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index 96c7fe86..12771ce7 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -1,11 +1,11 @@ -const { eventMode } = require("../../../../loadenv"); +const { eventConfig } = require("../../../../loadenv"); const globalStateDocs = require("./globalState"); const itemsDocs = require("./items"); const transactionsDocs = require("./transactions"); const itemsSchema = require("./itemsSchema"); const publicNoticeDocs = require("./publicNotice"); -const apiPrefix = `/events/${eventMode}`; +const apiPrefix = `/events/${eventConfig.mode}`; const eventSwaggerDocs = { tags: [ diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index 60e7435b..9bb82f41 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -1,5 +1,5 @@ -const { eventMode } = require("../../../../loadenv"); -const apiPrefix = `/events/${eventMode}/transactions`; +const { eventConfig } = require("../../../../loadenv"); +const apiPrefix = `/events/${eventConfig.mode}/transactions`; const transactionsDocs = {}; transactionsDocs[`${apiPrefix}/`] = { diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 7c7f7c5f..0cf4315c 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -2,11 +2,10 @@ const { eventStatusModel } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); const { isLogin, getLoginInfo } = require("../../modules/auths/login"); -const { eventMode } = require("../../../loadenv"); -const contract = eventMode - ? require(`../modules/contracts/${eventMode}`) - : undefined; -const quests = contract ? Object.values(contract.quests) : undefined; +const { eventConfig } = require("../../../loadenv"); +const contracts = + eventConfig && require(`../modules/contracts/${eventConfig.mode}`); +const quests = contracts ? Object.values(contracts.quests) : undefined; const getUserGlobalStateHandler = async (req, res) => { try { @@ -50,7 +49,7 @@ const createUserGlobalStateHandler = async (req, res) => { }); await eventStatus.save(); - await contract.completeFirstLoginQuest(req.userOid, req.timestamp); + await contracts.completeFirstLoginQuest(req.userOid, req.timestamp); res.json({ result: true }); } catch (err) { diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 5e3a5a1c..26afb6b0 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -20,10 +20,11 @@ const updateEventStatus = async ( } ); -const { eventMode } = require("../../../loadenv"); -const eventPeriod = eventMode - ? require(`../modules/contracts/${eventMode}`).eventPeriod - : undefined; +const { eventConfig } = require("../../../loadenv"); +const eventPeriod = eventConfig && { + startAt: new Date(eventConfig.startAt), + endAt: new Date(eventConfig.endAt), +}; const getRandomItem = async (req, depth) => { if (depth >= 10) { @@ -126,7 +127,10 @@ const purchaseHandler = async (req, res) => { .status(400) .json({ error: "Items/Purchase : nonexistent eventStatus" }); - if (req.timestamp >= eventPeriod.end || req.timestamp < eventPeriod.start) + if ( + req.timestamp >= eventPeriod.endAt || + req.timestamp < eventPeriod.startAt + ) return res.status(400).json({ error: "Items/Purchase : out of date" }); const { itemId } = req.params; diff --git a/src/routes/admin.js b/src/routes/admin.js index d476142e..e2ddaae4 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -13,7 +13,7 @@ const { deviceTokenModel, notificationOptionModel, } = require("../modules/stores/mongo"); -const { eventMode } = require("../../loadenv"); +const { eventConfig } = require("../../loadenv"); const { buildResource } = require("../modules/adminResource"); const router = express.Router(); @@ -37,7 +37,7 @@ const baseResources = [ notificationOptionModel, ].map(buildResource()); const resources = baseResources.concat( - eventMode === "2023fall" ? require("../lottery").resources : [] + eventConfig?.mode === "2023fall" ? require("../lottery").resources : [] ); // Create router for admin page From db0ed2ce95ef02d7729099b9aeae8ff0a9d8bf3b Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 19 Sep 2023 22:44:25 +0900 Subject: [PATCH 178/261] Add: Resolve comment --- src/lottery/modules/contracts/2023fall.js | 14 +++++++++++++ src/lottery/modules/populates/publicNotice.js | 14 ------------- src/lottery/modules/populates/transactions.js | 12 +++++++++++ src/lottery/routes/docs/quests.js | 20 +++++++++++++++---- src/lottery/routes/docs/swaggerDocs.js | 4 ++-- src/lottery/services/publicNotice.js | 13 ++++++------ 6 files changed, 50 insertions(+), 27 deletions(-) delete mode 100644 src/lottery/modules/populates/publicNotice.js diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 8bc6350a..86660420 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -189,6 +189,13 @@ const completeAdPushAgreementQuest = async () => { // TODO }; +/** + * eventSharingOnInstagram 퀘스트의 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @returns {Promise} + * @description 인스타그램 스토리에 추석 이벤트를 공유할 때마다 호출해 주세요. + * @usage users/editAccountHandler + */ const completeEventSharingOnInstagramQuest = async (userId) => { return await completeQuest( userId, @@ -197,6 +204,13 @@ const completeEventSharingOnInstagramQuest = async (userId) => { ); }; +/** + * purchaseSharingOnInstagram 퀘스트의 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @returns {Promise} + * @description 인스타그램 스토리에 구매한 아이템을 공유할 때마다 호출해 주세요. + * @usage users/editAccountHandler + */ const completePurchaseSharingOnInstagramQuest = async () => { return await completeQuest( userId, diff --git a/src/lottery/modules/populates/publicNotice.js b/src/lottery/modules/populates/publicNotice.js deleted file mode 100644 index d662e5fa..00000000 --- a/src/lottery/modules/populates/publicNotice.js +++ /dev/null @@ -1,14 +0,0 @@ -const publicNoticePopulateOption = [ - { - path: "userId", - select: "id", - }, - { - path: "item", - select: "name price description itemType", - }, -]; - -module.exports = { - publicNoticePopulateOption, -}; diff --git a/src/lottery/modules/populates/transactions.js b/src/lottery/modules/populates/transactions.js index 9f3a958e..bcf84863 100644 --- a/src/lottery/modules/populates/transactions.js +++ b/src/lottery/modules/populates/transactions.js @@ -5,6 +5,18 @@ const transactionPopulateOption = [ }, ]; +const publicNoticePopulateOption = [ + { + path: "userId", + select: "nickname", + }, + { + path: "item", + select: "name price description", + }, +]; + module.exports = { transactionPopulateOption, + publicNoticePopulateOption, }; diff --git a/src/lottery/routes/docs/quests.js b/src/lottery/routes/docs/quests.js index 2663f277..69b701c8 100644 --- a/src/lottery/routes/docs/quests.js +++ b/src/lottery/routes/docs/quests.js @@ -13,8 +13,14 @@ eventsDocs[`${apiPrefix}/instagram/share-event`] = { content: { "application/json": { schema: { - type: "boolean", - example: { result: true }, + type: "object", + required: ["result"], + properties: { + result: { + type: "boolean", + example: true, + }, + }, }, }, }, @@ -34,8 +40,14 @@ eventsDocs[`${apiPrefix}/instagram/share-purchase`] = { content: { "application/json": { schema: { - type: "boolean", - example: { result: true }, + type: "object", + required: ["result"], + properties: { + result: { + type: "boolean", + example: true, + }, + }, }, }, }, diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index 8136a470..0aac0bc0 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -23,8 +23,8 @@ const eventSwaggerDocs = { description: "이벤트 - 입출금 내역 관련 API", }, { - name: `${apiPrefix}/instagram`, - description: "이벤트 - 인스타그램 이벤트 관련 API", + name: `${apiPrefix}/quests`, + description: "이벤트 - 퀘스트 관련 API", }, { name: `${apiPrefix}/public-notice`, diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 930df9e3..afebe595 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -5,7 +5,7 @@ const { isLogin, getLoginInfo } = require("../../modules/auths/login"); const logger = require("../../modules/logger"); const { publicNoticePopulateOption, -} = require("../modules/populates/publicNotice"); +} = require("../modules/populates/transactions"); const getRecentPurchaceItemListHandler = async (req, res) => { try { @@ -16,7 +16,7 @@ const getRecentPurchaceItemListHandler = async (req, res) => { .limit(5) .populate(publicNoticePopulateOption) .lean(); - if (!!transactions) { + if (transactions) { transactions.forEach((item, index) => { let purchaceMessage = ""; if (item.comment.startsWith("송편")) { @@ -26,25 +26,24 @@ const getRecentPurchaceItemListHandler = async (req, res) => { } else { purchaceMessage = "획득하셨습니다."; } - transactionListString[index] = `${item.userId.id + transactionListString[index] = `${item.userId.nickname .toString() - .slice(0, 2)}${"*".repeat(item.userId.id.length - 2)}님께서 ${ + .slice(0, 2)}${"*".repeat(item.userId.nickname.length - 2)}님께서 ${ item.item.name }을(를) ${purchaceMessage}`; }); - console.log(transactionListString); res.json({ transactionListString, }); } else { res.status(500).json({ - error: "PublicNotice/get-recent-transaction : internal server error", + error: "PublicNotice/RecentTransaction : internal server error", }); } } catch (err) { logger.error(err); res.status(500).json({ - error: "PublicNotice/get-recent-transaction : internal server error", + error: "PublicNotice/RecentTransaction : internal server error", }); } }; From a4bcf6a9c6016fb41a9fe34107da38999499b20b Mon Sep 17 00:00:00 2001 From: static Date: Tue, 19 Sep 2023 23:06:01 +0900 Subject: [PATCH 179/261] Refactor: modules/quests.js --- src/lottery/modules/quests.js | 2 +- src/lottery/routes/docs/globalState.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index 9aa5c77f..26bf2e24 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -8,7 +8,7 @@ const logger = require("../../modules/logger"); const mongoose = require("mongoose"); const { eventConfig } = require("../../../loadenv"); -const eventPeriod = { +const eventPeriod = eventConfig && { startAt: new Date(eventConfig.startAt), endAt: new Date(eventConfig.endAt), }; diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index ffaddf3a..d5a52a16 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -16,7 +16,7 @@ globalStateDocs[`${apiPrefix}/`] = { schema: { type: "object", required: [ - "isAgree", + "isAgreeOnTermsOfEvent", "creditAmount", "completedQuests", "ticket1Amount", From 3fa55a3edb1482a64a6476c75f96424cd19d933b Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 19 Sep 2023 23:07:12 +0900 Subject: [PATCH 180/261] Add: Resolve comments --- src/lottery/services/publicNotice.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index afebe595..654afd15 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -7,6 +7,10 @@ const { publicNoticePopulateOption, } = require("../modules/populates/transactions"); +const returnAnonymousedNickname = (nickname) => { + return `${nickname.toString().slice(0, 2)}${"*".repeat(nickname.length - 2)}`; +}; + const getRecentPurchaceItemListHandler = async (req, res) => { try { let transactionListString = []; @@ -26,11 +30,9 @@ const getRecentPurchaceItemListHandler = async (req, res) => { } else { purchaceMessage = "획득하셨습니다."; } - transactionListString[index] = `${item.userId.nickname - .toString() - .slice(0, 2)}${"*".repeat(item.userId.nickname.length - 2)}님께서 ${ - item.item.name - }을(를) ${purchaceMessage}`; + transactionListString[index] = `${returnAnonymousedNickname( + item.userId.nickname + )}님께서 ${item.item.name}을(를) ${purchaceMessage}`; }); res.json({ transactionListString, @@ -81,9 +83,8 @@ const getTicketLeaderboardHandler = async (req, res) => { logger.error(`Fail to find user ${user.userId}`); return null; } - return { - nickname: userInfo.nickname, + nickname: returnAnonymousedNickname(userInfo.nickname), profileImageUrl: userInfo.profileImageUrl, ticket1Amount: user.ticket1Amount, ticket2Amount: user.ticket2Amount, @@ -95,7 +96,6 @@ const getTicketLeaderboardHandler = async (req, res) => { return res .status(500) .json({ error: "PublicNotice/Leaderboard : internal server error" }); - if (rank >= 0) res.json({ leaderboard, From 5163cf1e14e5cc590ebeecd1a872861638433ee4 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 19 Sep 2023 23:21:03 +0900 Subject: [PATCH 181/261] Add: Resolve commits --- src/lottery/modules/contracts/2023fall.js | 16 ++++++++-------- src/lottery/routes/docs/quests.js | 2 ++ src/lottery/services/publicNotice.js | 4 ++-- src/lottery/services/quests.js | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 86660420..2d6e6a39 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -97,7 +97,7 @@ const completeFirstLoginQuest = async (userId) => { * @param {number} roomObject.settlementTotal - 정산 또는 송금이 완료된 참여자 수입니다. * @returns {Promise} * @description 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. - * @usage rooms/commitPaymentHandler, rooms/settlementHandler + * @usage rooms - commitPaymentHandler, rooms - settlementHandler */ const completePayingAndSendingQuest = async (roomObject) => { if (roomObject.part.length < 2) return null; @@ -120,7 +120,7 @@ const completePayingAndSendingQuest = async (roomObject) => { * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} * @description 방을 만들 때마다 호출해 주세요. - * @usage rooms/createHandler + * @usage rooms - createHandler */ const completeFirstRoomCreationQuest = async (userId) => { return await completeQuest(userId, eventPeriod, quests.firstRoomCreation); @@ -137,7 +137,7 @@ const completeRoomSharingQuest = async () => { * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} * @description 정산 요청이 이루어질 때마다 호출해 주세요. - * @usage rooms/commitPaymentHandler + * @usage rooms - commitPaymentHandler */ const completePayingQuest = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; @@ -152,7 +152,7 @@ const completePayingQuest = async (userId, roomObject) => { * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} * @description 송금이 이루어질 때마다 호출해 주세요. - * @usage rooms/settlementHandler + * @usage rooms - settlementHandler */ const completeSendingQuest = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; @@ -165,7 +165,7 @@ const completeSendingQuest = async (userId, roomObject) => { * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} * @description 닉네임을 변경할 때마다 호출해 주세요. - * @usage users/editNicknameHandler + * @usage users - editNicknameHandler */ const completeNicknameChangingQuest = async (userId) => { return await completeQuest(userId, eventPeriod, quests.nicknameChaning); @@ -177,7 +177,7 @@ const completeNicknameChangingQuest = async (userId) => { * @param {string} newAccount - 변경된 계좌입니다. * @returns {Promise} * @description 계좌를 변경할 때마다 호출해 주세요. - * @usage users/editAccountHandler + * @usage users - editAccountHandler */ const completeAccountChangingQuest = async (userId, newAccount) => { if (newAccount === "") return null; @@ -194,7 +194,7 @@ const completeAdPushAgreementQuest = async () => { * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} * @description 인스타그램 스토리에 추석 이벤트를 공유할 때마다 호출해 주세요. - * @usage users/editAccountHandler + * @usage quests - instagramEventShareHandler */ const completeEventSharingOnInstagramQuest = async (userId) => { return await completeQuest( @@ -209,7 +209,7 @@ const completeEventSharingOnInstagramQuest = async (userId) => { * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} * @description 인스타그램 스토리에 구매한 아이템을 공유할 때마다 호출해 주세요. - * @usage users/editAccountHandler + * @usage quests - instagramPurchaseShareHandler */ const completePurchaseSharingOnInstagramQuest = async () => { return await completeQuest( diff --git a/src/lottery/routes/docs/quests.js b/src/lottery/routes/docs/quests.js index 69b701c8..39410352 100644 --- a/src/lottery/routes/docs/quests.js +++ b/src/lottery/routes/docs/quests.js @@ -17,6 +17,7 @@ eventsDocs[`${apiPrefix}/instagram/share-event`] = { required: ["result"], properties: { result: { + description: "성공 여부. 항상 true입니다.", type: "boolean", example: true, }, @@ -44,6 +45,7 @@ eventsDocs[`${apiPrefix}/instagram/share-purchase`] = { required: ["result"], properties: { result: { + description: "성공 여부. 항상 true입니다.", type: "boolean", example: true, }, diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 654afd15..fab01010 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -39,13 +39,13 @@ const getRecentPurchaceItemListHandler = async (req, res) => { }); } else { res.status(500).json({ - error: "PublicNotice/RecentTransaction : internal server error", + error: "PublicNotice/RecentTransactions : internal server error", }); } } catch (err) { logger.error(err); res.status(500).json({ - error: "PublicNotice/RecentTransaction : internal server error", + error: "PublicNotice/RecentTransactions : internal server error", }); } }; diff --git a/src/lottery/services/quests.js b/src/lottery/services/quests.js index 68be355a..90cbf8f9 100644 --- a/src/lottery/services/quests.js +++ b/src/lottery/services/quests.js @@ -20,7 +20,7 @@ const instagramEventShareHandler = async (req, res) => { // 인스타그램 스토리에 아이템 구매 내역을 공유했을 때. const instagramPurchaseShareHandler = async (req, res) => { try { - const userId = req.userOid; + const { userOid: userId } = req; const contractResult = await contracts.completePurchaseSharingOnInstagramQuest(userId); res.json({ result: !!contractResult }); From 5eff8894c231d0b96c7aadf641c8a866901b0107 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 19 Sep 2023 23:42:27 +0900 Subject: [PATCH 182/261] Add: Resolve comments --- src/lottery/routes/docs/publicNotice.js | 132 ++++++++++++++---------- src/lottery/routes/publicNotice.js | 2 +- src/lottery/services/publicNotice.js | 7 +- src/lottery/services/quests.js | 8 +- 4 files changed, 87 insertions(+), 62 deletions(-) diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js index cda93fa1..fbf94f5c 100644 --- a/src/lottery/routes/docs/publicNotice.js +++ b/src/lottery/routes/docs/publicNotice.js @@ -2,71 +2,92 @@ const { eventMode } = require("../../../../loadenv"); const apiPrefix = `/events/${eventMode}/public-notice`; const publicNoticeDocs = {}; -publicNoticeDocs[`${apiPrefix}/leaderboard`] = { +(publicNoticeDocs[`${apiPrefix}/recentTransactions`] = { get: { tags: [`${apiPrefix}`], - summary: "리더보드 반환", - description: - "티켓 개수(고급 티켓은 일반 티켓 5개와 등가입니다.) 기준의 리더보드와 관련된 정보를 가져옵니다.", + summary: "최근 트랜젝션의 명단 리스트 반환", + description: "", responses: { 200: { description: "", content: { "application/json": { schema: { - type: "object", - required: ["leaderboard"], - properties: { - leaderboard: { - type: "array", - description: "상위 20명만 포함된 리더보드", - items: { - type: "object", - required: [ - "nickname", - "profileImageUrl", - "ticket1Amount", - "ticket2Amount", - "probability", - ], - properties: { - nickname: { - type: "string", - description: "유저의 닉네임", - example: "asdf", - }, - profileImageUrl: { - type: "string", - description: "프로필 이미지 URL", - example: "IMAGE URL", - }, - ticket1Amount: { - type: "number", - description: "일반 티켓의 개수. 0 이상입니다.", - example: 10, - }, - ticket2Amount: { - type: "number", - description: "고급 티켓의 개수. 0 이상입니다.", - example: 10, - }, - probability: { - type: "number", - description: "1등 당첨 확률", - example: 0.001, + type: "array", + items: ["string"], + }, + }, + }, + }, + }, + }, +}), + (publicNoticeDocs[`${apiPrefix}/leaderboard`] = { + get: { + tags: [`${apiPrefix}`], + summary: "리더보드 반환", + description: + "티켓 개수(고급 티켓은 일반 티켓 5개와 등가입니다.) 기준의 리더보드와 관련된 정보를 가져옵니다.", + responses: { + 200: { + description: "", + content: { + "application/json": { + schema: { + type: "object", + required: ["leaderboard"], + properties: { + leaderboard: { + type: "array", + description: "상위 20명만 포함된 리더보드", + items: { + type: "object", + required: [ + "nickname", + "profileImageUrl", + "ticket1Amount", + "ticket2Amount", + "probability", + ], + properties: { + nickname: { + type: "string", + description: "유저의 닉네임", + example: "asdf", + }, + profileImageUrl: { + type: "string", + description: "프로필 이미지 URL", + example: "IMAGE URL", + }, + ticket1Amount: { + type: "number", + description: "일반 티켓의 개수. 0 이상입니다.", + example: 10, + }, + ticket2Amount: { + type: "number", + description: "고급 티켓의 개수. 0 이상입니다.", + example: 10, + }, + probability: { + type: "number", + description: "1등 당첨 확률", + example: 0.001, + }, }, }, }, - }, - rank: { - type: "number", - description: "유저의 리더보드 순위. 1부터 시작합니다.", - example: 30, - }, - probability: { - type: "number", - description: "1등 당첨 확률", - example: 0.00003, + rank: { + type: "number", + description: "유저의 리더보드 순위. 1부터 시작합니다.", + example: 30, + }, + probability: { + type: "number", + description: "1등 당첨 확률", + example: 0.00003, + }, }, }, }, @@ -74,7 +95,6 @@ publicNoticeDocs[`${apiPrefix}/leaderboard`] = { }, }, }, - }, -}; + }); module.exports = publicNoticeDocs; diff --git a/src/lottery/routes/publicNotice.js b/src/lottery/routes/publicNotice.js index 79fc17ab..ac17e481 100644 --- a/src/lottery/routes/publicNotice.js +++ b/src/lottery/routes/publicNotice.js @@ -5,7 +5,7 @@ const publicNoticeHandlers = require("../services/publicNotice"); // 상점 공지는 로그인을 요구하지 않습니다. router.get( - "/recent-transactions", + "/recentTransactions", publicNoticeHandlers.getRecentPurchaceItemListHandler ); router.get("/leaderboard", publicNoticeHandlers.getTicketLeaderboardHandler); diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index fab01010..0a62d3d2 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -7,7 +7,7 @@ const { publicNoticePopulateOption, } = require("../modules/populates/transactions"); -const returnAnonymousedNickname = (nickname) => { +const hideNickname = (nickname) => { return `${nickname.toString().slice(0, 2)}${"*".repeat(nickname.length - 2)}`; }; @@ -30,7 +30,7 @@ const getRecentPurchaceItemListHandler = async (req, res) => { } else { purchaceMessage = "획득하셨습니다."; } - transactionListString[index] = `${returnAnonymousedNickname( + transactionListString[index] = `${hideNickname( item.userId.nickname )}님께서 ${item.item.name}을(를) ${purchaceMessage}`; }); @@ -84,7 +84,7 @@ const getTicketLeaderboardHandler = async (req, res) => { return null; } return { - nickname: returnAnonymousedNickname(userInfo.nickname), + nickname: hideNickname(userInfo.nickname), profileImageUrl: userInfo.profileImageUrl, ticket1Amount: user.ticket1Amount, ticket2Amount: user.ticket2Amount, @@ -96,6 +96,7 @@ const getTicketLeaderboardHandler = async (req, res) => { return res .status(500) .json({ error: "PublicNotice/Leaderboard : internal server error" }); + if (rank >= 0) res.json({ leaderboard, diff --git a/src/lottery/services/quests.js b/src/lottery/services/quests.js index 90cbf8f9..b11b3a73 100644 --- a/src/lottery/services/quests.js +++ b/src/lottery/services/quests.js @@ -1,7 +1,9 @@ const logger = require("../../modules/logger"); const contracts = require("../modules/contracts/2023fall"); -// 인스타그램 스토리에 이벤트를 공유했을 때. +/** + * 인스타그램 스토리에 이벤트를 공유했을 때. + */ const instagramEventShareHandler = async (req, res) => { try { const { userOid: userId } = req; @@ -17,7 +19,9 @@ const instagramEventShareHandler = async (req, res) => { } }; -// 인스타그램 스토리에 아이템 구매 내역을 공유했을 때. +/** + * 인스타그램 스토리에 아이템 구매 내역을 공유했을 때. + */ const instagramPurchaseShareHandler = async (req, res) => { try { const { userOid: userId } = req; From 3b4f0d83be7c381a8deede1482acb7c92c892d62 Mon Sep 17 00:00:00 2001 From: withsang Date: Tue, 19 Sep 2023 23:48:34 +0900 Subject: [PATCH 183/261] Add: early return func if the item is sold out --- src/lottery/services/items.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 26afb6b0..aba495cd 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -62,14 +62,13 @@ const getRandomItem = async (req, depth) => { // 1단계: 재고를 차감합니다. const newRandomItem = await itemModel .findOneAndUpdate( - { _id: randomItem._id }, + { _id: randomItem._id, stock: { $gt: 0 } }, { $inc: { stock: -1, }, }, { - runValidators: true, new: true, fields: { itemType: 0, @@ -79,6 +78,9 @@ const getRandomItem = async (req, depth) => { } ) .lean(); + if (!newRandomItem) { + throw new Error("The item was already sold out"); + } // 2단계: 유저 정보를 업데이트합니다. await updateEventStatus(req.userOid, { From f0a83cfed18b02edfd4f0bf829638a41309916d2 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 19 Sep 2023 23:51:51 +0900 Subject: [PATCH 184/261] Add: Resolve comments --- src/lottery/routes/docs/publicNotice.js | 133 ++++++++++++------------ src/lottery/services/publicNotice.js | 2 +- 2 files changed, 68 insertions(+), 67 deletions(-) diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js index fbf94f5c..846b20d1 100644 --- a/src/lottery/routes/docs/publicNotice.js +++ b/src/lottery/routes/docs/publicNotice.js @@ -2,7 +2,7 @@ const { eventMode } = require("../../../../loadenv"); const apiPrefix = `/events/${eventMode}/public-notice`; const publicNoticeDocs = {}; -(publicNoticeDocs[`${apiPrefix}/recentTransactions`] = { +publicNoticeDocs[`${apiPrefix}/recentTransactions`] = { get: { tags: [`${apiPrefix}`], summary: "최근 트랜젝션의 명단 리스트 반환", @@ -21,73 +21,73 @@ const publicNoticeDocs = {}; }, }, }, -}), - (publicNoticeDocs[`${apiPrefix}/leaderboard`] = { - get: { - tags: [`${apiPrefix}`], - summary: "리더보드 반환", - description: - "티켓 개수(고급 티켓은 일반 티켓 5개와 등가입니다.) 기준의 리더보드와 관련된 정보를 가져옵니다.", - responses: { - 200: { - description: "", - content: { - "application/json": { - schema: { - type: "object", - required: ["leaderboard"], - properties: { - leaderboard: { - type: "array", - description: "상위 20명만 포함된 리더보드", - items: { - type: "object", - required: [ - "nickname", - "profileImageUrl", - "ticket1Amount", - "ticket2Amount", - "probability", - ], - properties: { - nickname: { - type: "string", - description: "유저의 닉네임", - example: "asdf", - }, - profileImageUrl: { - type: "string", - description: "프로필 이미지 URL", - example: "IMAGE URL", - }, - ticket1Amount: { - type: "number", - description: "일반 티켓의 개수. 0 이상입니다.", - example: 10, - }, - ticket2Amount: { - type: "number", - description: "고급 티켓의 개수. 0 이상입니다.", - example: 10, - }, - probability: { - type: "number", - description: "1등 당첨 확률", - example: 0.001, - }, +}; + +publicNoticeDocs[`${apiPrefix}/leaderboard`] = { + get: { + tags: [`${apiPrefix}`], + summary: "리더보드 반환", + description: + "티켓 개수(고급 티켓은 일반 티켓 5개와 등가입니다.) 기준의 리더보드와 관련된 정보를 가져옵니다.", + responses: { + 200: { + description: "", + content: { + "application/json": { + schema: { + type: "object", + required: ["leaderboard"], + properties: { + leaderboard: { + type: "array", + description: "상위 20명만 포함된 리더보드", + items: { + type: "object", + required: [ + "nickname", + "profileImageUrl", + "ticket1Amount", + "ticket2Amount", + "probability", + ], + properties: { + nickname: { + type: "string", + description: "유저의 닉네임", + example: "asdf", + }, + profileImageUrl: { + type: "string", + description: "프로필 이미지 URL", + example: "IMAGE URL", + }, + ticket1Amount: { + type: "number", + description: "일반 티켓의 개수. 0 이상입니다.", + example: 10, + }, + ticket2Amount: { + type: "number", + description: "고급 티켓의 개수. 0 이상입니다.", + example: 10, + }, + probability: { + type: "number", + description: "1등 당첨 확률", + example: 0.001, }, }, }, - rank: { - type: "number", - description: "유저의 리더보드 순위. 1부터 시작합니다.", - example: 30, - }, - probability: { - type: "number", - description: "1등 당첨 확률", - example: 0.00003, - }, + }, + rank: { + type: "number", + description: "유저의 리더보드 순위. 1부터 시작합니다.", + example: 30, + }, + probability: { + type: "number", + description: "1등 당첨 확률", + example: 0.00003, }, }, }, @@ -95,6 +95,7 @@ const publicNoticeDocs = {}; }, }, }, - }); + }, +}; module.exports = publicNoticeDocs; diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 0a62d3d2..32413b15 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -15,7 +15,7 @@ const getRecentPurchaceItemListHandler = async (req, res) => { try { let transactionListString = []; const transactions = await transactionModel - .find({ type: "use" }) + .find({ type: "use", itemType: 0 }) .sort({ createAt: -1 }) .limit(5) .populate(publicNoticePopulateOption) From d6a578bcadef69aa309665549ae61b8a7ac25a67 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 20 Sep 2023 00:01:13 +0900 Subject: [PATCH 185/261] Add: early return func if the item is sold out --- src/lottery/services/items.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index aba495cd..40482621 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -153,17 +153,15 @@ const purchaseHandler = async (req, res) => { .json({ error: "Items/Purchase : item out of stock" }); // 1단계: 재고를 차감합니다. - await itemModel.updateOne( - { _id: item._id }, + const { modifiedCount } = await itemModel.updateOne( + { _id: item._id, stock: { $gt: 0 } }, { $inc: { stock: -1, }, - }, - { - runValidators: true, } ); + if (modifiedCount === 0) throw new Error("The item was already sold out"); // 2단계: 유저 정보를 업데이트합니다. await updateEventStatus(req.userOid, { From c1d2eb3a1b242ed5ff911d784c77d5bfc5522a3b Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 20 Sep 2023 00:06:24 +0900 Subject: [PATCH 186/261] Add: resolve comment --- src/lottery/routes/docs/publicNotice.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js index 846b20d1..18f90cbf 100644 --- a/src/lottery/routes/docs/publicNotice.js +++ b/src/lottery/routes/docs/publicNotice.js @@ -14,7 +14,9 @@ publicNoticeDocs[`${apiPrefix}/recentTransactions`] = { "application/json": { schema: { type: "array", - items: ["string"], + items: { + type: "string", + }, }, }, }, From cc09f0b179d24f2c5256ba28dc0c423f69aaabda Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 20 Sep 2023 00:17:07 +0900 Subject: [PATCH 187/261] Add: resolve comments --- src/lottery/routes/docs/publicNotice.js | 15 ++++++++++++--- src/lottery/services/publicNotice.js | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js index 18f90cbf..58e4b7dd 100644 --- a/src/lottery/routes/docs/publicNotice.js +++ b/src/lottery/routes/docs/publicNotice.js @@ -13,9 +13,18 @@ publicNoticeDocs[`${apiPrefix}/recentTransactions`] = { content: { "application/json": { schema: { - type: "array", - items: { - type: "string", + type: "object", + description: "5개의 상점 공지", + required: ["transactions"], + properties: { + transactions: { + type: "array", + items: { + type: "string", + example: + "tu**************님께서 일반응모권을(를) 획득하셨습니다.", + }, + }, }, }, }, diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 32413b15..7f045aec 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -35,7 +35,7 @@ const getRecentPurchaceItemListHandler = async (req, res) => { )}님께서 ${item.item.name}을(를) ${purchaceMessage}`; }); res.json({ - transactionListString, + transactions: transactionListString, }); } else { res.status(500).json({ From 3086cd85b2eb7b302a3648f7c6b0b0da973b848c Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 20 Sep 2023 01:21:28 +0900 Subject: [PATCH 188/261] Add: resolve comments --- src/lottery/modules/contracts/2023fall.js | 14 +++---- src/lottery/services/publicNotice.js | 49 +++++++++-------------- src/lottery/services/quests.js | 8 +++- 3 files changed, 32 insertions(+), 39 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 7d1d4546..12a4d41d 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -210,29 +210,27 @@ const completeAdPushAgreementQuest = async ( /** * eventSharingOnInstagram 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @returns {Promise} * @description 인스타그램 스토리에 추석 이벤트를 공유할 때마다 호출해 주세요. * @usage quests - instagramEventShareHandler */ -const completeEventSharingOnInstagramQuest = async (userId) => { - return await completeQuest( - userId, - eventPeriod, - quests.eventSharingOnInstagram - ); +const completeEventSharingOnInstagramQuest = async (userId, timestamp) => { + return await completeQuest(userId, timestamp, quests.eventSharingOnInstagram); }; /** * purchaseSharingOnInstagram 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @returns {Promise} * @description 인스타그램 스토리에 구매한 아이템을 공유할 때마다 호출해 주세요. * @usage quests - instagramPurchaseShareHandler */ -const completePurchaseSharingOnInstagramQuest = async () => { +const completePurchaseSharingOnInstagramQuest = async (userId, timestamp) => { return await completeQuest( userId, - eventPeriod, + timestamp, quests.purchaseSharingOnInstagram ); }; diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 7f045aec..e19f577a 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -13,35 +13,26 @@ const hideNickname = (nickname) => { const getRecentPurchaceItemListHandler = async (req, res) => { try { - let transactionListString = []; - const transactions = await transactionModel - .find({ type: "use", itemType: 0 }) - .sort({ createAt: -1 }) - .limit(5) - .populate(publicNoticePopulateOption) - .lean(); - if (transactions) { - transactions.forEach((item, index) => { - let purchaceMessage = ""; - if (item.comment.startsWith("송편")) { - purchaceMessage = "구입하셨습니다."; - } else if (item.comment.startsWith("랜덤 박스")) { - purchaceMessage = "뽑았습니다."; - } else { - purchaceMessage = "획득하셨습니다."; - } - transactionListString[index] = `${hideNickname( - item.userId.nickname - )}님께서 ${item.item.name}을(를) ${purchaceMessage}`; - }); - res.json({ - transactions: transactionListString, - }); - } else { - res.status(500).json({ - error: "PublicNotice/RecentTransactions : internal server error", - }); - } + const transactions = ( + await transactionModel + .find({ type: "use", itemType: 0 }) + .sort({ createAt: -1 }) + .limit(5) + .populate(publicNoticePopulateOption) + .lean() + ).map( + ({ userId, item, comment }) => + `${hideNickname(userId.nickname)}님께서 ${item.name}을(를) ${ + comment.startsWith("송편") + ? "을(를) 구입하셨습니다." + : comment.startsWith("랜덤 박스") + ? "을(를) 뽑았습니다." + : "을(를) 획득하셨습니다." + }` + ); + res.json({ + transactions: transactions, + }); } catch (err) { logger.error(err); res.status(500).json({ diff --git a/src/lottery/services/quests.js b/src/lottery/services/quests.js index b11b3a73..7d8eefa0 100644 --- a/src/lottery/services/quests.js +++ b/src/lottery/services/quests.js @@ -8,7 +8,8 @@ const instagramEventShareHandler = async (req, res) => { try { const { userOid: userId } = req; const contractResult = await contracts.completeEventSharingOnInstagramQuest( - userId + userId, + req.timestamp ); res.json({ result: !!contractResult }); } catch (err) { @@ -26,7 +27,10 @@ const instagramPurchaseShareHandler = async (req, res) => { try { const { userOid: userId } = req; const contractResult = - await contracts.completePurchaseSharingOnInstagramQuest(userId); + await contracts.completePurchaseSharingOnInstagramQuest( + userId, + req.timestamp + ); res.json({ result: !!contractResult }); } catch (err) { logger.error(err); From e77a03f71f5ac84ea3bc56a748d1ebec8da79df8 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 20 Sep 2023 01:30:12 +0900 Subject: [PATCH 189/261] Refactor: publicNotice --- src/lottery/services/publicNotice.js | 4 +--- src/lottery/services/quests.js | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index e19f577a..2ce717af 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -30,9 +30,7 @@ const getRecentPurchaceItemListHandler = async (req, res) => { : "을(를) 획득하셨습니다." }` ); - res.json({ - transactions: transactions, - }); + res.json({ transactions }); } catch (err) { logger.error(err); res.status(500).json({ diff --git a/src/lottery/services/quests.js b/src/lottery/services/quests.js index 7d8eefa0..7212893f 100644 --- a/src/lottery/services/quests.js +++ b/src/lottery/services/quests.js @@ -6,9 +6,9 @@ const contracts = require("../modules/contracts/2023fall"); */ const instagramEventShareHandler = async (req, res) => { try { - const { userOid: userId } = req; + const { userOid } = req; const contractResult = await contracts.completeEventSharingOnInstagramQuest( - userId, + userOid, req.timestamp ); res.json({ result: !!contractResult }); @@ -25,10 +25,10 @@ const instagramEventShareHandler = async (req, res) => { */ const instagramPurchaseShareHandler = async (req, res) => { try { - const { userOid: userId } = req; + const { userOid } = req; const contractResult = await contracts.completePurchaseSharingOnInstagramQuest( - userId, + userOid, req.timestamp ); res.json({ result: !!contractResult }); From 7322b3e8882216edafa81f893a0531f7a77b3205 Mon Sep 17 00:00:00 2001 From: cokia Date: Wed, 20 Sep 2023 02:14:33 +0900 Subject: [PATCH 190/261] fix: add phoneNumber field to user scheme --- src/modules/stores/mongo.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index c255d7b6..d8af344d 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -12,6 +12,7 @@ const userSchema = Schema({ ongoingRoom: [{ type: Schema.Types.ObjectId, ref: "Room" }], // 참여중인 진행중인 방 배열 doneRoom: [{ type: Schema.Types.ObjectId, ref: "Room" }], // 참여중인 완료된 방 배열 withdraw: { type: Boolean, default: false }, + phoneNumber: { type: String, default: "" }, // 전화번호 (2023FALL 이벤트부터 추가) ban: { type: Boolean, default: false }, //계정 정지 여부 joinat: { type: Date, required: true }, //가입 시각 agreeOnTermsOfService: { type: Boolean, default: false }, //이용약관 동의 여부 From 450a4ceb57d9edf675f0828b5126cb1dd0e9e016 Mon Sep 17 00:00:00 2001 From: cokia Date: Wed, 20 Sep 2023 02:14:54 +0900 Subject: [PATCH 191/261] fix: collect phonenumber when event agree --- src/lottery/routes/globalState.js | 5 ++++- src/lottery/services/globalState.js | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lottery/routes/globalState.js b/src/lottery/routes/globalState.js index d0461218..54bb83d5 100644 --- a/src/lottery/routes/globalState.js +++ b/src/lottery/routes/globalState.js @@ -2,12 +2,15 @@ const express = require("express"); const router = express.Router(); const globalStateHandlers = require("../services/globalState"); +const { validateBody } = require("../../middlewares/ajv"); router.get("/", globalStateHandlers.getUserGlobalStateHandler); // 아래의 Endpoint 접근 시 로그인 필요 router.use(require("../../middlewares/auth")); -router.post("/create", globalStateHandlers.createUserGlobalStateHandler); +router.post("/create", + validateBody({phoneNumber: {required: true, type: "string", pattern: "/^010-?([0-9]{3,4})-?([0-9]{4})$/"}}), + globalStateHandlers.createUserGlobalStateHandler); module.exports = router; diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 0cf4315c..32ff76cb 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -3,6 +3,7 @@ const logger = require("../../modules/logger"); const { isLogin, getLoginInfo } = require("../../modules/auths/login"); const { eventConfig } = require("../../../loadenv"); +const { userModel } = require("../../modules/stores/mongo"); const contracts = eventConfig && require(`../modules/contracts/${eventConfig.mode}`); const quests = contracts ? Object.values(contracts.quests) : undefined; @@ -49,6 +50,11 @@ const createUserGlobalStateHandler = async (req, res) => { }); await eventStatus.save(); + //logic2. 수집한 유저 전화번호 user Scheme 에 저장 + const user = await userModel.findOne({ _id: req.userOid }); + user.phoneNumber = req.body.phoneNumber; + await user.save(); + await contracts.completeFirstLoginQuest(req.userOid, req.timestamp); res.json({ result: true }); From 784801d21ff0b439b303bf8f0849fe39c549fa3f Mon Sep 17 00:00:00 2001 From: cokia Date: Wed, 20 Sep 2023 02:28:10 +0900 Subject: [PATCH 192/261] fix: migrate scheme info to globalStateSchema --- src/lottery/routes/docs/globalState.js | 10 ++++++++++ src/lottery/routes/docs/globalStateSchema.js | 15 +++++++++++++++ src/lottery/routes/docs/swaggerDocs.js | 3 ++- src/lottery/routes/globalState.js | 2 +- 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 src/lottery/routes/docs/globalStateSchema.js diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index d5a52a16..4cf9630b 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -128,6 +128,16 @@ globalStateDocs[`${apiPrefix}/create`] = { summary: "Frontend에서 Global state로 관리하는 정보 생성", description: "유저의 재화 개수, 퀘스트 완료 상태 등 Frontend에서 Global state로 관리할 정보를 생성합니다.", + requestBody: { + description: "Update an existent user in the store", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/createUserGlobalStateHandler", + }, + }, + }, + }, responses: { 200: { description: "", diff --git a/src/lottery/routes/docs/globalStateSchema.js b/src/lottery/routes/docs/globalStateSchema.js new file mode 100644 index 00000000..7a9a5260 --- /dev/null +++ b/src/lottery/routes/docs/globalStateSchema.js @@ -0,0 +1,15 @@ +const globalStateSchema = { + createUserGlobalStateHandler: { + type: "object", + required: ["phoneNumber"], + properties: { + phoneNumber: { + type: "string", + pattern: "^010-?([0-9]{3,4})-?([0-9]{4})$", + }, + }, + errorMessage: "validation: bad request", + }, +}; + +module.exports = globalStateSchema; diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index ddd82098..1c87be93 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -5,7 +5,7 @@ const transactionsDocs = require("./transactions"); const questsDocs = require("./quests"); const itemsSchema = require("./itemsSchema"); const publicNoticeDocs = require("./publicNotice"); - +const globalStateSchema=require("./globalStateSchema"); const apiPrefix = `/events/${eventConfig.mode}`; const eventSwaggerDocs = { @@ -41,6 +41,7 @@ const eventSwaggerDocs = { components: { schemas: { ...itemsSchema, + ...globalStateSchema, }, }, }; diff --git a/src/lottery/routes/globalState.js b/src/lottery/routes/globalState.js index 54bb83d5..2118c895 100644 --- a/src/lottery/routes/globalState.js +++ b/src/lottery/routes/globalState.js @@ -10,7 +10,7 @@ router.get("/", globalStateHandlers.getUserGlobalStateHandler); router.use(require("../../middlewares/auth")); router.post("/create", - validateBody({phoneNumber: {required: true, type: "string", pattern: "/^010-?([0-9]{3,4})-?([0-9]{4})$/"}}), + validateBody(globalStateHandlers.createUserGlobalStateSchema), globalStateHandlers.createUserGlobalStateHandler); module.exports = router; From 506bf00c794cd59ff52163cedd6391d2054e8a66 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 20 Sep 2023 01:50:21 +0900 Subject: [PATCH 193/261] Refactor: apply prettier --- src/lottery/routes/docs/swaggerDocs.js | 2 +- src/lottery/routes/globalState.js | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index 1c87be93..77a85f61 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -5,7 +5,7 @@ const transactionsDocs = require("./transactions"); const questsDocs = require("./quests"); const itemsSchema = require("./itemsSchema"); const publicNoticeDocs = require("./publicNotice"); -const globalStateSchema=require("./globalStateSchema"); +const globalStateSchema = require("./globalStateSchema"); const apiPrefix = `/events/${eventConfig.mode}`; const eventSwaggerDocs = { diff --git a/src/lottery/routes/globalState.js b/src/lottery/routes/globalState.js index 2118c895..be63bb3f 100644 --- a/src/lottery/routes/globalState.js +++ b/src/lottery/routes/globalState.js @@ -3,14 +3,17 @@ const express = require("express"); const router = express.Router(); const globalStateHandlers = require("../services/globalState"); const { validateBody } = require("../../middlewares/ajv"); +const globalStateSchema = require("./docs/globalStateSchema"); router.get("/", globalStateHandlers.getUserGlobalStateHandler); // 아래의 Endpoint 접근 시 로그인 필요 router.use(require("../../middlewares/auth")); -router.post("/create", - validateBody(globalStateHandlers.createUserGlobalStateSchema), - globalStateHandlers.createUserGlobalStateHandler); +router.post( + "/create", + validateBody(globalStateSchema.createUserGlobalStateHandler), + globalStateHandlers.createUserGlobalStateHandler +); module.exports = router; From 2b65567e32e142772bba169f14fa4f221affae00 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 20 Sep 2023 01:55:17 +0900 Subject: [PATCH 194/261] Docs: update some descriptions --- src/lottery/routes/docs/publicNotice.js | 7 +++---- src/lottery/routes/docs/quests.js | 14 +++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js index 36f28b79..ca30cfb9 100644 --- a/src/lottery/routes/docs/publicNotice.js +++ b/src/lottery/routes/docs/publicNotice.js @@ -5,8 +5,8 @@ const publicNoticeDocs = {}; publicNoticeDocs[`${apiPrefix}/recentTransactions`] = { get: { tags: [`${apiPrefix}`], - summary: "최근 트랜젝션의 명단 리스트 반환", - description: "", + summary: "최근의 유의미한 상품 획득 기록 반환", + description: "모든 유저의 상품 획득 내역 중 유의미한 기록을 가져옵니다.", responses: { 200: { description: "", @@ -14,11 +14,11 @@ publicNoticeDocs[`${apiPrefix}/recentTransactions`] = { "application/json": { schema: { type: "object", - description: "5개의 상점 공지", required: ["transactions"], properties: { transactions: { type: "array", + description: "상품 획득 기록의 배열", items: { type: "string", example: @@ -33,7 +33,6 @@ publicNoticeDocs[`${apiPrefix}/recentTransactions`] = { }, }, }; - publicNoticeDocs[`${apiPrefix}/leaderboard`] = { get: { tags: [`${apiPrefix}`], diff --git a/src/lottery/routes/docs/quests.js b/src/lottery/routes/docs/quests.js index 39410352..e85f934c 100644 --- a/src/lottery/routes/docs/quests.js +++ b/src/lottery/routes/docs/quests.js @@ -1,12 +1,12 @@ -const { eventMode } = require("../../../../loadenv"); -const apiPrefix = `/events/${eventMode}/quests`; +const { eventConfig } = require("../../../../loadenv"); +const apiPrefix = `/events/${eventConfig.mode}/quests`; const eventsDocs = {}; eventsDocs[`${apiPrefix}/instagram/share-event`] = { post: { tags: [`${apiPrefix}`], - summary: "이벤트 공유시 보상 반환", - description: "인스타그램 스토리에 이벤트를 공유하면 보상 반환", + summary: "eventSharingOnInstagram 퀘스트 완료 요청", + description: "eventSharingOnInstagram 퀘스트의 완료를 요청합니다.", responses: { 200: { description: "", @@ -17,8 +17,8 @@ eventsDocs[`${apiPrefix}/instagram/share-event`] = { required: ["result"], properties: { result: { - description: "성공 여부. 항상 true입니다.", type: "boolean", + description: "성공 여부. 항상 true입니다.", example: true, }, }, @@ -33,8 +33,8 @@ eventsDocs[`${apiPrefix}/instagram/share-event`] = { eventsDocs[`${apiPrefix}/instagram/share-purchase`] = { post: { tags: [`${apiPrefix}`], - summary: "이벤트 공유시 보상 반환", - description: "인스타그램 스토리에 구매내역을 공유하면 보상 반환", + summary: "purchaseSharingOnInstagram 퀘스트 완료 요청", + description: "purchaseSharingOnInstagram 퀘스트의 완료를 요청합니다.", responses: { 200: { description: "", From 66969ad08c096a0cfd72d0e921a4e9934fe18cf2 Mon Sep 17 00:00:00 2001 From: cokia Date: Wed, 20 Sep 2023 02:41:00 +0900 Subject: [PATCH 195/261] fix: change user.phoneNumber default to undefiend --- src/modules/stores/mongo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index d8af344d..998aecdd 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -12,7 +12,7 @@ const userSchema = Schema({ ongoingRoom: [{ type: Schema.Types.ObjectId, ref: "Room" }], // 참여중인 진행중인 방 배열 doneRoom: [{ type: Schema.Types.ObjectId, ref: "Room" }], // 참여중인 완료된 방 배열 withdraw: { type: Boolean, default: false }, - phoneNumber: { type: String, default: "" }, // 전화번호 (2023FALL 이벤트부터 추가) + phoneNumber: { type: String }, // 전화번호 (2023FALL 이벤트부터 추가) ban: { type: Boolean, default: false }, //계정 정지 여부 joinat: { type: Date, required: true }, //가입 시각 agreeOnTermsOfService: { type: Boolean, default: false }, //이용약관 동의 여부 From 91490156dce716b451448773e2209dacc868653e Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Wed, 20 Sep 2023 05:58:00 +0900 Subject: [PATCH 196/261] Add: detail in contracts/2023fall.js --- src/lottery/modules/contracts/2023fall.js | 86 ++++++++++++++--------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 12a4d41d..295ae4eb 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -4,74 +4,96 @@ const mongoose = require("mongoose"); /** 전체 퀘스트 목록입니다. */ const quests = buildQuests({ firstLogin: { - name: "이벤트 기간 첫 로그인", - description: "", - imageUrl: "", + name: "첫 발걸음", + description: + "로그인만 해도 송편을 얻을 수 있다고?? 이벤트 기간에 처음으로 SPARCS Taxi 서비스에 로그인하여 송편을 받아보세요.", + imageUrl: + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_firstLogin.png", reward: { ticket1: 1, }, }, payingAndSending: { - name: "2명 이상 탑승한 방에서 정산/송금 완료", - description: "", - imageUrl: "", + name: "함께하는 택시의 여정", + description: + "2명 이상과 함께 택시를 타고 정산/송금까지 완료해보세요. 최대 3번까지 송편을 받을 수 있어요. 정산/송금 버튼은 채팅 페이지 좌측 하단의 +버튼을 눌러 확인할 수 있어요.", + imageUrl: + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_payingAndSending.png", reward: 300, maxCount: 3, }, firstRoomCreation: { name: "첫 방 개설", - description: "", - imageUrl: "", + description: + "원하는 택시팟을 찾을 수 없다면? 원하는 조건으로 방 개설 페이지에서 방을 직접 개설해보세요.", + imageUrl: + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_firstRoomCreation.png", reward: 50, }, roomSharing: { - name: "방 공유하기", - description: "", - imageUrl: "", + name: "Taxi로 모여라", + description: + "방을 공유해 친구들을 택시에 초대해보세요. 채팅창 상단의 햄버거 버튼을 누르면 공유하기 버튼을 찾을 수 있어요.", + imageUrl: + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_roomSharing.png", reward: 50, }, paying: { - name: "2명 이상 탑승한 방에서 정산하기", - description: "", - imageUrl: "", + name: "정산해요 택시의 숲", + description: + "2명 이상과 함께 택시를 타고 택시비를 결제한 후 정산하기를 요청해보세요. 정산하기 버튼은 채팅 페이지 좌측 하단의 +버튼을 눌러 확인할 수 있어요.", + imageUrl: + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_paying.png", reward: 100, maxCount: 3, }, sending: { - name: "2명 이상 탑승한 방에서 송금하기", - description: "", - imageUrl: "", + name: "송금 완료! 친구야 고마워", + description: + "2명 이상과 함께 택시를 타고 택시비를 결제한 분께 송금해주세요. 송금하기 버튼은 채팅 페이지 좌측 하단의 +버튼을 눌러 확인할 수 있어요.", + imageUrl: + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_sending.png", reward: 50, maxCount: 3, }, nicknameChanging: { - name: "닉네임 변경", - description: "", - imageUrl: "", + name: "닉네임 변신", + description: + "닉네임을 변경하여 자신을 표현하세요. 마이페이지의 수정하기 버튼을 눌러 닉네임을 수정할 수 있어요.", + imageUrl: + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_nicknameChanging.png", reward: 50, }, accountChanging: { - name: "계좌 등록 또는 변경", - description: "", - imageUrl: "", + name: "계좌 등록은 정산의 시작", + description: + "정산하기 기능을 더욱 빠르고 이용할 수 있다고? 계좌번호를 등록하면 정산하기를 할 때 계좌가 자동으로 입력돼요. 마이페이지수정하기 버튼을 눌러 계좌번호를 등록 또는 수정할 수 있어요.", + imageUrl: + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_accountChanging.png", reward: 50, }, adPushAgreement: { - name: "광고성 푸시 알림 수신 동의", - description: "", - imageUrl: "", + name: "Taxi의 소울메이트", + description: + "Taxi 서비스를 잊지 않도록 가끔 찾아갈게요! 광고성 푸시 알림 수신 동의를 해주시면 방이 많이 모이는 시즌, 주변에 택시앱 사용자가 있을 때 알려드릴 수 있어요.", + imageUrl: + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_adPushAgreement.png", reward: 50, }, eventSharingOnInstagram: { - name: "이벤트 인스타그램 스토리에 공유", - description: "", - imageUrl: "", + name: "나만 알기에는 아까운 이벤트", + description: + "추석에 맞춰 쏟아지는 혜택들. 나만 알 순 없죠. 인스타그램 친구들에게 스토리로 공유해보아요. 이벤트 안내 페이지에서 인스타그램 스토리로 이벤트 공유하기 버튼을 눌러보세요.", + imageUrl: + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_eventSharingOnInstagram.png", reward: 100, }, purchaseSharingOnInstagram: { - name: "아이템 구매 후 인스타그램 스토리에 공유", - description: "", - imageUrl: "", + name: "상품 획득을 축하합니다", + description: + "이벤트를 열심히 즐긴 당신. 그 상품 획득을 축하 받을 자격이 충분합니다. 달토끼 상점에서 상품 구매 후 뜨는 버튼을 눌러 상품 획득을 공유하세요.", + imageUrl: + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_purchaseSharingOnInstagram.png", reward: 100, }, }); From ec8183a0031f33ebabc3dd3a6715a43e7e1c6d73 Mon Sep 17 00:00:00 2001 From: Geon Kim Date: Wed, 20 Sep 2023 13:21:10 +0900 Subject: [PATCH 197/261] Fix: detail in contracts/2023fall.js --- src/lottery/modules/contracts/2023fall.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 295ae4eb..3b7a9c22 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -83,7 +83,7 @@ const quests = buildQuests({ eventSharingOnInstagram: { name: "나만 알기에는 아까운 이벤트", description: - "추석에 맞춰 쏟아지는 혜택들. 나만 알 순 없죠. 인스타그램 친구들에게 스토리로 공유해보아요. 이벤트 안내 페이지에서 인스타그램 스토리로 이벤트 공유하기 버튼을 눌러보세요.", + "추석에 맞춰 쏟아지는 혜택들. 나만 알 순 없죠. 인스타그램 친구들에게 스토리로 공유해보아요. 이벤트 안내 페이지에서 인스타그램 스토리에 공유하기을 눌러보세요.", imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_eventSharingOnInstagram.png", reward: 100, @@ -91,7 +91,7 @@ const quests = buildQuests({ purchaseSharingOnInstagram: { name: "상품 획득을 축하합니다", description: - "이벤트를 열심히 즐긴 당신. 그 상품 획득을 축하 받을 자격이 충분합니다. 달토끼 상점에서 상품 구매 후 뜨는 버튼을 눌러 상품 획득을 공유하세요.", + "이벤트를 열심히 즐긴 당신. 그 상품 획득을 축하 받을 자격이 충분합니다. 달토끼 상점에서 상품 구매 후 뜨는 인스타그램 스토리에 공유하기 버튼을 눌러 상품 획득을 공유하세요.", imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_purchaseSharingOnInstagram.png", reward: 100, From 9d47eb764ab80a0fd6fe7bf596041b2e5843851a Mon Sep 17 00:00:00 2001 From: static Date: Wed, 20 Sep 2023 21:58:27 +0900 Subject: [PATCH 198/261] Refactor: loadenv.js --- loadenv.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/loadenv.js b/loadenv.js index 1f9528b6..3dfc5be6 100644 --- a/loadenv.js +++ b/loadenv.js @@ -38,5 +38,10 @@ module.exports = { slackWebhookUrl: { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, - eventConfig: process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG), // optional + eventConfig: (process.env.EVENT_CONFIG && + JSON.parse(process.env.EVENT_CONFIG)) || { + mode: "2023fall", + startAt: "2023-09-25T00:00:00+09:00", + endAt: "2023-10-10T00:00:00+09:00", + }, }; From 535fbbbe27b5f064a3cf110e1d79c593b5c65ddd Mon Sep 17 00:00:00 2001 From: static Date: Wed, 20 Sep 2023 22:09:00 +0900 Subject: [PATCH 199/261] Fix: the condition of payingAndSendingQuest --- src/lottery/modules/contracts/2023fall.js | 22 ++++++---------------- src/services/rooms.js | 12 ++++++++++-- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 3b7a9c22..1677c26e 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -33,7 +33,7 @@ const quests = buildQuests({ roomSharing: { name: "Taxi로 모여라", description: - "방을 공유해 친구들을 택시에 초대해보세요. 채팅창 상단의 햄버거 버튼을 누르면 공유하기 버튼을 찾을 수 있어요.", + "방을 공유해 친구들을 택시에 초대해보세요. 채팅창 상단의 햄버거(☰) 버튼을 누르면 공유하기 버튼을 찾을 수 있어요.", imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_roomSharing.png", reward: 50, @@ -59,7 +59,7 @@ const quests = buildQuests({ nicknameChanging: { name: "닉네임 변신", description: - "닉네임을 변경하여 자신을 표현하세요. 마이페이지의 수정하기 버튼을 눌러 닉네임을 수정할 수 있어요.", + "닉네임을 변경하여 자신을 표현하세요. 마이페이지수정하기 버튼을 눌러 닉네임을 수정할 수 있어요.", imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_nicknameChanging.png", reward: 50, @@ -110,29 +110,19 @@ const completeFirstLoginQuest = async (userId, timestamp) => { }; /** - * payingAndSending 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이거나, 모든 참가자가 정산 또는 송금을 완료하지 않았다면 요청하지 않습니다. + * payingAndSending 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. - * @param {number} roomObject.settlementTotal - 정산 또는 송금이 완료된 참여자 수입니다. * @returns {Promise} * @description 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. * @usage rooms - commitPaymentHandler, rooms - settlementHandler */ -const completePayingAndSendingQuest = async (timestamp, roomObject) => { +const completePayingAndSendingQuest = async (userId, timestamp, roomObject) => { if (roomObject.part.length < 2) return null; - if (roomObject.part.length > roomObject.settlementTotal) return null; - return await Promise.all( - roomObject.part.map( - async (participant) => - await completeQuest( - participant.user._id, - timestamp, - quests.payingAndSending - ) - ) - ); + return await completeQuest(userId, timestamp, quests.payingAndSending); }; /** diff --git a/src/services/rooms.js b/src/services/rooms.js index 4ef35731..d4b7557e 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -497,7 +497,11 @@ const commitPaymentHandler = async (req, res) => { req.timestamp, roomObject ); - await contracts?.completePayingAndSendingQuest(req.timestamp, roomObject); + await contracts?.completePayingAndSendingQuest( + req.userOid, + req.timestamp, + roomObject + ); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); @@ -571,7 +575,11 @@ const settlementHandler = async (req, res) => { req.timestamp, roomObject ); - await contracts?.completePayingAndSendingQuest(req.timestamp, roomObject); + await contracts?.completePayingAndSendingQuest( + req.userOid, + req.timestamp, + roomObject + ); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); From 0a15da94337586fbeb6766cfbddaede5935b336d Mon Sep 17 00:00:00 2001 From: static Date: Wed, 20 Sep 2023 22:43:27 +0900 Subject: [PATCH 200/261] Add: check event period in some endpoints --- src/lottery/routes/docs/quests.js | 4 ++-- src/lottery/services/globalState.js | 14 +++++++++++++- src/lottery/services/quests.js | 22 ++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/lottery/routes/docs/quests.js b/src/lottery/routes/docs/quests.js index e85f934c..1d6a89c5 100644 --- a/src/lottery/routes/docs/quests.js +++ b/src/lottery/routes/docs/quests.js @@ -18,7 +18,7 @@ eventsDocs[`${apiPrefix}/instagram/share-event`] = { properties: { result: { type: "boolean", - description: "성공 여부. 항상 true입니다.", + description: "성공 여부", example: true, }, }, @@ -45,7 +45,7 @@ eventsDocs[`${apiPrefix}/instagram/share-purchase`] = { required: ["result"], properties: { result: { - description: "성공 여부. 항상 true입니다.", + description: "성공 여부", type: "boolean", example: true, }, diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 32ff76cb..4e96f610 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -1,9 +1,13 @@ const { eventStatusModel } = require("../modules/stores/mongo"); +const { userModel } = require("../../modules/stores/mongo"); const logger = require("../../modules/logger"); const { isLogin, getLoginInfo } = require("../../modules/auths/login"); const { eventConfig } = require("../../../loadenv"); -const { userModel } = require("../../modules/stores/mongo"); +const eventPeriod = eventConfig && { + startAt: new Date(eventConfig.startAt), + endAt: new Date(eventConfig.endAt), +}; const contracts = eventConfig && require(`../modules/contracts/${eventConfig.mode}`); const quests = contracts ? Object.values(contracts.quests) : undefined; @@ -37,6 +41,14 @@ const getUserGlobalStateHandler = async (req, res) => { const createUserGlobalStateHandler = async (req, res) => { try { + if ( + req.timestamp >= eventPeriod.endAt || + req.timestamp < eventPeriod.startAt + ) + return res + .status(400) + .json({ error: "GlobalState/Create : out of date" }); + let eventStatus = await eventStatusModel .findOne({ userId: req.userOid }) .lean(); diff --git a/src/lottery/services/quests.js b/src/lottery/services/quests.js index 7212893f..bdfa89ac 100644 --- a/src/lottery/services/quests.js +++ b/src/lottery/services/quests.js @@ -1,11 +1,25 @@ const logger = require("../../modules/logger"); const contracts = require("../modules/contracts/2023fall"); +const { eventConfig } = require("../../../loadenv"); +const eventPeriod = eventConfig && { + startAt: new Date(eventConfig.startAt), + endAt: new Date(eventConfig.endAt), +}; + /** * 인스타그램 스토리에 이벤트를 공유했을 때. */ const instagramEventShareHandler = async (req, res) => { try { + if ( + req.timestamp >= eventPeriod.endAt || + req.timestamp < eventPeriod.startAt + ) + return res + .status(400) + .json({ error: "Quests/Instagram/ShareEvent : out of date" }); + const { userOid } = req; const contractResult = await contracts.completeEventSharingOnInstagramQuest( userOid, @@ -25,6 +39,14 @@ const instagramEventShareHandler = async (req, res) => { */ const instagramPurchaseShareHandler = async (req, res) => { try { + if ( + req.timestamp >= eventPeriod.endAt || + req.timestamp < eventPeriod.startAt + ) + return res + .status(400) + .json({ error: "Quests/Instagram/SharePurchase : out of date" }); + const { userOid } = req; const contractResult = await contracts.completePurchaseSharingOnInstagramQuest( From 1fef3b18161b2178cadea9f7270b9955b0ab3096 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 20 Sep 2023 22:56:52 +0900 Subject: [PATCH 201/261] Add: timestampValidator middleware --- src/lottery/middlewares/timestampValidator.js | 18 +++++++++++++++ src/lottery/routes/globalState.js | 3 ++- src/lottery/routes/items.js | 3 ++- src/lottery/routes/quests.js | 1 + src/lottery/services/globalState.js | 12 ---------- src/lottery/services/items.js | 12 ---------- src/lottery/services/quests.js | 22 ------------------- 7 files changed, 23 insertions(+), 48 deletions(-) create mode 100644 src/lottery/middlewares/timestampValidator.js diff --git a/src/lottery/middlewares/timestampValidator.js b/src/lottery/middlewares/timestampValidator.js new file mode 100644 index 00000000..781ec265 --- /dev/null +++ b/src/lottery/middlewares/timestampValidator.js @@ -0,0 +1,18 @@ +const { eventConfig } = require("../../../loadenv"); +const eventPeriod = eventConfig && { + startAt: new Date(eventConfig.startAt), + endAt: new Date(eventConfig.endAt), +}; + +const timestampValidator = (req, res, next) => { + if ( + req.timestamp >= eventPeriod.endAt || + req.timestamp < eventPeriod.startAt + ) { + return res.status(400).json({ error: "out of date" }); + } else { + next(); + } +}; + +module.exports = timestampValidator; diff --git a/src/lottery/routes/globalState.js b/src/lottery/routes/globalState.js index be63bb3f..cb69d782 100644 --- a/src/lottery/routes/globalState.js +++ b/src/lottery/routes/globalState.js @@ -7,8 +7,9 @@ const globalStateSchema = require("./docs/globalStateSchema"); router.get("/", globalStateHandlers.getUserGlobalStateHandler); -// 아래의 Endpoint 접근 시 로그인 필요 +// 아래의 Endpoint 접근 시 로그인 및 시각 체크 필요 router.use(require("../../middlewares/auth")); +router.use(require("../middlewares/timestampValidator")); router.post( "/create", diff --git a/src/lottery/routes/items.js b/src/lottery/routes/items.js index 95136a5f..21dc47ae 100644 --- a/src/lottery/routes/items.js +++ b/src/lottery/routes/items.js @@ -8,8 +8,9 @@ const itemsSchema = require("./docs/itemsSchema"); router.get("/list", itemsHandlers.listHandler); -// 아래의 Endpoint 접근 시 로그인 필요 +// 아래의 Endpoint 접근 시 로그인 및 시각 체크 필요 router.use(require("../../middlewares/auth")); +router.use(require("../middlewares/timestampValidator")); router.post( "/purchase/:itemId", diff --git a/src/lottery/routes/quests.js b/src/lottery/routes/quests.js index f8cf8177..032c96c7 100644 --- a/src/lottery/routes/quests.js +++ b/src/lottery/routes/quests.js @@ -3,6 +3,7 @@ const router = express.Router(); const quests = require("../services/quests"); router.use(require("../../middlewares/auth")); +router.use(require("../middlewares/timestampValidator")); router.post("/instagram/share-event", quests.instagramEventShareHandler); router.post("/instagram/share-purchase", quests.instagramPurchaseShareHandler); diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 4e96f610..c36ac9f1 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -4,10 +4,6 @@ const logger = require("../../modules/logger"); const { isLogin, getLoginInfo } = require("../../modules/auths/login"); const { eventConfig } = require("../../../loadenv"); -const eventPeriod = eventConfig && { - startAt: new Date(eventConfig.startAt), - endAt: new Date(eventConfig.endAt), -}; const contracts = eventConfig && require(`../modules/contracts/${eventConfig.mode}`); const quests = contracts ? Object.values(contracts.quests) : undefined; @@ -41,14 +37,6 @@ const getUserGlobalStateHandler = async (req, res) => { const createUserGlobalStateHandler = async (req, res) => { try { - if ( - req.timestamp >= eventPeriod.endAt || - req.timestamp < eventPeriod.startAt - ) - return res - .status(400) - .json({ error: "GlobalState/Create : out of date" }); - let eventStatus = await eventStatusModel .findOne({ userId: req.userOid }) .lean(); diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index fa860077..ece89fec 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -20,12 +20,6 @@ const updateEventStatus = async ( } ); -const { eventConfig } = require("../../../loadenv"); -const eventPeriod = eventConfig && { - startAt: new Date(eventConfig.startAt), - endAt: new Date(eventConfig.endAt), -}; - const getRandomItem = async (req, depth) => { if (depth >= 10) { logger.error(`User ${req.userOid} failed to open random box`); @@ -130,12 +124,6 @@ const purchaseHandler = async (req, res) => { .status(400) .json({ error: "Items/Purchase : nonexistent eventStatus" }); - if ( - req.timestamp >= eventPeriod.endAt || - req.timestamp < eventPeriod.startAt - ) - return res.status(400).json({ error: "Items/Purchase : out of date" }); - const { itemId } = req.params; const item = await itemModel.findOne({ _id: itemId }).lean(); if (!item) diff --git a/src/lottery/services/quests.js b/src/lottery/services/quests.js index bdfa89ac..7212893f 100644 --- a/src/lottery/services/quests.js +++ b/src/lottery/services/quests.js @@ -1,25 +1,11 @@ const logger = require("../../modules/logger"); const contracts = require("../modules/contracts/2023fall"); -const { eventConfig } = require("../../../loadenv"); -const eventPeriod = eventConfig && { - startAt: new Date(eventConfig.startAt), - endAt: new Date(eventConfig.endAt), -}; - /** * 인스타그램 스토리에 이벤트를 공유했을 때. */ const instagramEventShareHandler = async (req, res) => { try { - if ( - req.timestamp >= eventPeriod.endAt || - req.timestamp < eventPeriod.startAt - ) - return res - .status(400) - .json({ error: "Quests/Instagram/ShareEvent : out of date" }); - const { userOid } = req; const contractResult = await contracts.completeEventSharingOnInstagramQuest( userOid, @@ -39,14 +25,6 @@ const instagramEventShareHandler = async (req, res) => { */ const instagramPurchaseShareHandler = async (req, res) => { try { - if ( - req.timestamp >= eventPeriod.endAt || - req.timestamp < eventPeriod.startAt - ) - return res - .status(400) - .json({ error: "Quests/Instagram/SharePurchase : out of date" }); - const { userOid } = req; const contractResult = await contracts.completePurchaseSharingOnInstagramQuest( From 719aad6b2a660878d31a412a82ba1b8c8f47ed4b Mon Sep 17 00:00:00 2001 From: static Date: Wed, 20 Sep 2023 22:57:56 +0900 Subject: [PATCH 202/261] Remove: hideNickname function --- src/lottery/services/publicNotice.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 2ce717af..e6b287fa 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -7,10 +7,6 @@ const { publicNoticePopulateOption, } = require("../modules/populates/transactions"); -const hideNickname = (nickname) => { - return `${nickname.toString().slice(0, 2)}${"*".repeat(nickname.length - 2)}`; -}; - const getRecentPurchaceItemListHandler = async (req, res) => { try { const transactions = ( @@ -22,7 +18,7 @@ const getRecentPurchaceItemListHandler = async (req, res) => { .lean() ).map( ({ userId, item, comment }) => - `${hideNickname(userId.nickname)}님께서 ${item.name}을(를) ${ + `${userId.nickname}님께서 ${item.name}을(를) ${ comment.startsWith("송편") ? "을(를) 구입하셨습니다." : comment.startsWith("랜덤 박스") @@ -73,7 +69,7 @@ const getTicketLeaderboardHandler = async (req, res) => { return null; } return { - nickname: hideNickname(userInfo.nickname), + nickname: userInfo.nickname, profileImageUrl: userInfo.profileImageUrl, ticket1Amount: user.ticket1Amount, ticket2Amount: user.ticket2Amount, From 7d54a704b9fee3f3a4b04cd40a62788631c97102 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 20 Sep 2023 23:03:11 +0900 Subject: [PATCH 203/261] Fix: check if eventPeriod is defined in timestampValidator --- src/lottery/middlewares/timestampValidator.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lottery/middlewares/timestampValidator.js b/src/lottery/middlewares/timestampValidator.js index 781ec265..22511536 100644 --- a/src/lottery/middlewares/timestampValidator.js +++ b/src/lottery/middlewares/timestampValidator.js @@ -6,6 +6,7 @@ const eventPeriod = eventConfig && { const timestampValidator = (req, res, next) => { if ( + !eventPeriod || req.timestamp >= eventPeriod.endAt || req.timestamp < eventPeriod.startAt ) { From 4ce23a0c450dd2431dbe590f17239dd104dcb86a Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Thu, 21 Sep 2023 00:41:56 +0900 Subject: [PATCH 204/261] Add: getValueRank evaluate the rank of public notice --- src/lottery/services/publicNotice.js | 45 ++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index e6b287fa..ce0f5633 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -7,25 +7,46 @@ const { publicNoticePopulateOption, } = require("../modules/populates/transactions"); +const hideNickname = (nickname) => { + return `${nickname.toString().slice(0, 2)}${"*".repeat(nickname.length - 2)}`; +}; + +const getValueRank = (item, createAt, timestamp) => { + return ( + (parseInt( + (new Date(createAt).getTime() / timestamp).toString().slice(7, 10) + ) * + item.price) / + 1000 + ); +}; + const getRecentPurchaceItemListHandler = async (req, res) => { try { const transactions = ( await transactionModel .find({ type: "use", itemType: 0 }) .sort({ createAt: -1 }) - .limit(5) + .limit(1000) .populate(publicNoticePopulateOption) .lean() - ).map( - ({ userId, item, comment }) => - `${userId.nickname}님께서 ${item.name}을(를) ${ - comment.startsWith("송편") - ? "을(를) 구입하셨습니다." - : comment.startsWith("랜덤 박스") - ? "을(를) 뽑았습니다." - : "을(를) 획득하셨습니다." - }` - ); + ) + .sort( + (x, y) => + getValueRank(y.item, y.createAt, req.timestamp) - + getValueRank(x.item, x.createAt, req.timestamp) + ) + .slice(0, 5) + .map( + ({ userId, item, comment }) => + `${hideNickname(userId.nickname)}님께서 ${item.name}을(를) ${ + comment.startsWith("송편") + ? " 을(를) 구입하셨습니다." + : comment.startsWith("랜덤 박스") + ? "을(를) 뽑았습니다." + : "을(를) 획득하셨습니다." + }` + ); res.json({ transactions }); } catch (err) { logger.error(err); @@ -69,7 +90,7 @@ const getTicketLeaderboardHandler = async (req, res) => { return null; } return { - nickname: userInfo.nickname, + nickname: hideNickname(userInfo.nickname), profileImageUrl: userInfo.profileImageUrl, ticket1Amount: user.ticket1Amount, ticket2Amount: user.ticket2Amount, From 91c442566f35366f24d26b0710f7648f8b8424e9 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 21 Sep 2023 11:18:36 +0900 Subject: [PATCH 205/261] Add: hideItemStock function --- src/lottery/routes/docs/itemsSchema.js | 2 +- src/lottery/services/items.js | 11 ++++++++--- src/lottery/services/publicNotice.js | 2 +- src/lottery/services/transactions.js | 11 ++++++++++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/lottery/routes/docs/itemsSchema.js b/src/lottery/routes/docs/itemsSchema.js index 1601f237..5f592053 100644 --- a/src/lottery/routes/docs/itemsSchema.js +++ b/src/lottery/routes/docs/itemsSchema.js @@ -43,7 +43,7 @@ const itemBase = { }, stock: { type: "number", - description: "남은 상품 재고. 0 이상입니다.", + description: "남은 상품 재고. 재고가 있는 경우 1, 없는 경우 0입니다.", example: 10, }, }, diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index ece89fec..760c2e90 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -20,6 +20,11 @@ const updateEventStatus = async ( } ); +const hideItemStock = (item) => { + item.stock = item.stock > 0 ? 1 : 0; + return item; +}; + const getRandomItem = async (req, depth) => { if (depth >= 10) { logger.error(`User ${req.userOid} failed to open random box`); @@ -89,7 +94,7 @@ const getRandomItem = async (req, depth) => { userId: req.userOid, item: randomItem._id, itemType: randomItem.itemType, - comment: `랜덤 박스에서 "${randomItem.name}" 1개를 획득했습니다.`, + comment: `랜덤박스에서 "${randomItem.name}" 1개를 획득했습니다.`, }); await transaction.save(); @@ -109,7 +114,7 @@ const listHandler = async (_, res) => { const items = await itemModel .find({}, "name imageUrl price description isDisabled stock itemType") .lean(); - res.json({ items }); + res.json({ items: items.map((item) => hideItemStock(item)) }); } catch (err) { logger.error(err); res.status(500).json({ error: "Items/List : internal server error" }); @@ -181,7 +186,7 @@ const purchaseHandler = async (req, res) => { res.json({ result: true, - reward: randomItem, + reward: hideItemStock(randomItem), }); } catch (err) { logger.error(err); diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index e6b287fa..62421bbd 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -21,7 +21,7 @@ const getRecentPurchaceItemListHandler = async (req, res) => { `${userId.nickname}님께서 ${item.name}을(를) ${ comment.startsWith("송편") ? "을(를) 구입하셨습니다." - : comment.startsWith("랜덤 박스") + : comment.startsWith("랜덤박스") ? "을(를) 뽑았습니다." : "을(를) 획득하셨습니다." }` diff --git a/src/lottery/services/transactions.js b/src/lottery/services/transactions.js index c151d81f..22b8c4a1 100644 --- a/src/lottery/services/transactions.js +++ b/src/lottery/services/transactions.js @@ -4,6 +4,13 @@ const { transactionPopulateOption, } = require("../modules/populates/transactions"); +const hideItemStock = (transaction) => { + if (transaction.item) { + transaction.item.stock = transaction.item.stock > 0 ? 1 : 0; + } + return transaction; +}; + const getUserTransactionsHandler = async (req, res) => { try { // userId는 이미 Frontend에서 알고 있고, 중복되는 값이므로 제외합니다. @@ -13,7 +20,9 @@ const getUserTransactionsHandler = async (req, res) => { .lean(); if (transactions) res.json({ - transactions, + transactions: transactions.map((transaction) => + hideItemStock(transaction) + ), }); else res.status(500).json({ error: "Transactions/ : internal server error" }); From f3f2ff2930a72bfade19ac602de589d4552aaf80 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 21 Sep 2023 11:26:59 +0900 Subject: [PATCH 206/261] Refactor: send 400 code when item is out of stock --- src/lottery/modules/quests.js | 3 ++- src/lottery/services/items.js | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index 26bf2e24..d93ec66d 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -98,7 +98,8 @@ const completeQuest = async (userId, timestamp, quest) => { // 5단계: 완료 보상 중 티켓이 있는 경우, 티켓 정보를 가져옵니다. const ticket1 = quest.reward.ticket1 && (await itemModel.findOne({ itemType: 1 }).lean()); - if (quest.reward.ticket1 && !ticket1) throw "Fail to find ticket1"; + if (quest.reward.ticket1 && !ticket1) + throw new Error("Fail to find ticket1"); // 6단계: 유저의 EventStatus를 업데이트합니다. await eventStatusModel.updateOne( diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 760c2e90..bf84b04a 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -78,7 +78,7 @@ const getRandomItem = async (req, depth) => { ) .lean(); if (!newRandomItem) { - throw new Error("The item was already sold out"); + throw new Error(`Item ${randomItem._id.toString()} was already sold out`); } // 2단계: 유저 정보를 업데이트합니다. @@ -155,7 +155,10 @@ const purchaseHandler = async (req, res) => { }, } ); - if (modifiedCount === 0) throw new Error("The item was already sold out"); + if (modifiedCount === 0) + return res + .status(400) + .json({ error: "Items/Purchase : item out of stock" }); // 2단계: 유저 정보를 업데이트합니다. await updateEventStatus(req.userOid, { From 2f0b3f8807f88e26dd151cedbcbc8822fdf495c0 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 21 Sep 2023 11:59:32 +0900 Subject: [PATCH 207/261] Add: status restore when random box failing --- src/lottery/services/items.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index bf84b04a..160bb644 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -39,9 +39,7 @@ const getRandomItem = async (req, depth) => { }) .lean(); const randomItems = items - .map((item) => { - return Array(item.randomWeight).fill(item); - }) + .map((item) => Array(item.randomWeight).fill(item)) .reduce((a, b) => a.concat(b), []); const dumpRandomItems = randomItems .map((item) => item._id.toString()) @@ -182,10 +180,30 @@ const purchaseHandler = async (req, res) => { if (item.itemType !== 3) return res.json({ result: true }); const randomItem = await getRandomItem(req, 0); - if (!randomItem) + if (!randomItem) { + // 랜덤박스가 실패한 경우, 상태를 구매 이전으로 되돌립니다. + // TODO: Transactions 도입 후 이 코드는 삭제합니다. + logger.info(`User ${req.userOid}'s status will be restored`); + + await transactionModel.deleteOne({ _id: transaction._id }); + await updateEventStatus(req.userOid, { + creditDelta: item.price, + }); + await itemModel.updateOne( + { _id: item._id }, + { + $inc: { + stock: 1, + }, + } + ); + + logger.info(`User ${req.userOid}'s status was successfully restored`); + return res .status(500) .json({ error: "Items/Purchase : random box error" }); + } res.json({ result: true, From 0c1fe65b9c8f9112128b7c0d07b1ecc0a9563f22 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 21 Sep 2023 13:54:30 +0900 Subject: [PATCH 208/261] Docs: update example of stock field --- src/lottery/routes/docs/itemsSchema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lottery/routes/docs/itemsSchema.js b/src/lottery/routes/docs/itemsSchema.js index 5f592053..2a4eca64 100644 --- a/src/lottery/routes/docs/itemsSchema.js +++ b/src/lottery/routes/docs/itemsSchema.js @@ -44,7 +44,7 @@ const itemBase = { stock: { type: "number", description: "남은 상품 재고. 재고가 있는 경우 1, 없는 경우 0입니다.", - example: 10, + example: 1, }, }, }; From 5d4f9002764ca8eb1bd610d26555449eea80aad3 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 21 Sep 2023 18:28:16 +0900 Subject: [PATCH 209/261] Refactor: resolve comment by @14KGun --- src/lottery/services/items.js | 2 +- src/lottery/services/transactions.js | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 160bb644..f4eeccdb 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -112,7 +112,7 @@ const listHandler = async (_, res) => { const items = await itemModel .find({}, "name imageUrl price description isDisabled stock itemType") .lean(); - res.json({ items: items.map((item) => hideItemStock(item)) }); + res.json({ items: items.map(hideItemStock) }); } catch (err) { logger.error(err); res.status(500).json({ error: "Items/List : internal server error" }); diff --git a/src/lottery/services/transactions.js b/src/lottery/services/transactions.js index 22b8c4a1..695cf3bf 100644 --- a/src/lottery/services/transactions.js +++ b/src/lottery/services/transactions.js @@ -20,9 +20,7 @@ const getUserTransactionsHandler = async (req, res) => { .lean(); if (transactions) res.json({ - transactions: transactions.map((transaction) => - hideItemStock(transaction) - ), + transactions: transactions.map(hideItemStock), }); else res.status(500).json({ error: "Transactions/ : internal server error" }); From d2fd93d24aeacb23384dcab74cce87f89ecbbfc5 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 22 Sep 2023 05:00:26 +0900 Subject: [PATCH 210/261] Add: addOneItemStockAction and addFiveItemStockAction --- src/lottery/index.js | 22 +++++++----- src/lottery/modules/items.js | 66 ++++++++++++++++++++++++++++++++++++ src/modules/adminResource.js | 4 +-- 3 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 src/lottery/modules/items.js diff --git a/src/lottery/index.js b/src/lottery/index.js index a24af419..9fc6f2c4 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -6,8 +6,13 @@ const { transactionModel, } = require("./modules/stores/mongo"); -const { eventConfig } = require("../../loadenv"); const { buildResource } = require("../modules/adminResource"); +const { + addOneItemStockAction, + addFiveItemStockAction, +} = require("./modules/items"); + +const { eventConfig } = require("../../loadenv"); // [Routes] 기존 docs 라우터의 docs extend eventConfig && require("./routes/docs")(); @@ -24,18 +29,19 @@ lotteryRouter.use("/items", require("./routes/items")); lotteryRouter.use("/public-notice", require("./routes/publicNotice")); lotteryRouter.use("/quests", require("./routes/quests")); -const resources = [ - eventStatusModel, - questModel, - itemModel, - transactionModel, -].map(buildResource()); +const itemResource = buildResource([ + addOneItemStockAction, + addFiveItemStockAction, +])(itemModel); +const otherResources = [eventStatusModel, questModel, transactionModel].map( + buildResource() +); const contracts = eventConfig && require(`./modules/contracts/${eventConfig.mode}`); module.exports = { lotteryRouter, - resources, + resources: [itemResource, ...otherResources], contracts, }; diff --git a/src/lottery/modules/items.js b/src/lottery/modules/items.js new file mode 100644 index 00000000..dae8e906 --- /dev/null +++ b/src/lottery/modules/items.js @@ -0,0 +1,66 @@ +const { itemModel } = require("./stores/mongo"); +const { buildRecordAction } = require("../../modules/adminResource"); +const logger = require("../../modules/logger"); + +const addItemStockActionHandler = (count) => async (req, res, context) => { + const itemId = context.record.params._id; + const oldStock = context.record.params.stock; + + try { + const item = await itemModel + .findOneAndUpdate( + { _id: itemId }, + { + $inc: { + stock: count, + }, + }, + { + new: true, + } + ) + .lean(); + if (!item) throw new Error("Fail to update stock"); + + let record = context.record.toJSON(context.currentAdmin); + record.params = item; + + return { + record, + notice: { + message: `성공적으로 재고 ${count}개를 추가했습니다. (${oldStock} → ${item.stock})`, + }, + response: {}, + }; + } catch (err) { + logger.error(err); + logger.error( + `Fail to process addItemStockActionHandler(${count}) for Item ${itemId}` + ); + + return { + record: context.record.toJSON(context.currentAdmin), + notice: { + message: `재고를 추가하지 못했습니다. 오류 메세지: ${err}`, + type: "error", + }, + }; + } +}; +const addItemStockActionLogs = ["update"]; + +const addOneItemStockAction = buildRecordAction( + "addOneItemStock", + addItemStockActionHandler(1), + addItemStockActionLogs +); +const addFiveItemStockAction = buildRecordAction( + "addFiveItemStock", + addItemStockActionHandler(5), + addItemStockActionLogs +); + +module.exports = { + addOneItemStockAction, + addFiveItemStockAction, +}; diff --git a/src/modules/adminResource.js b/src/modules/adminResource.js index b75013b3..f5ba3eb8 100644 --- a/src/modules/adminResource.js +++ b/src/modules/adminResource.js @@ -72,7 +72,7 @@ const recordActionAfterHandler = (actions) => async (res, req, context) => { return res; }; -const recordAction = (actionName, handler, logActions) => ({ +const buildRecordAction = (actionName, handler, logActions) => ({ actionName, actionType: "record", component: false, @@ -100,6 +100,6 @@ const buildResource = module.exports = { generateTarget, - recordAction, + buildRecordAction, buildResource, }; From a106e8587bf75231c2a7ad5269531aee7643c01c Mon Sep 17 00:00:00 2001 From: static Date: Fri, 22 Sep 2023 22:28:00 +0900 Subject: [PATCH 211/261] Add: /events/2023fall/quests/share-room endpoint --- src/lottery/modules/contracts/2023fall.js | 12 ++++++++++-- src/lottery/routes/quests.js | 1 + src/lottery/services/quests.js | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 1677c26e..10f562c1 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -137,8 +137,16 @@ const completeFirstRoomCreationQuest = async (userId, timestamp) => { return await completeQuest(userId, timestamp, quests.firstRoomCreation); }; -const completeRoomSharingQuest = async () => { - // TODO +/** + * roomSharing 퀘스트의 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. + * @returns {Promise} + * @description 인스타그램 스토리에 추석 이벤트를 공유할 때마다 호출해 주세요. + * @usage quests - instagramEventShareHandler + */ +const completeRoomSharingQuest = async (userId, timestamp) => { + return await completeQuest(userId, timestamp, quests.roomSharing); }; /** diff --git a/src/lottery/routes/quests.js b/src/lottery/routes/quests.js index 032c96c7..b1ec591b 100644 --- a/src/lottery/routes/quests.js +++ b/src/lottery/routes/quests.js @@ -5,6 +5,7 @@ const quests = require("../services/quests"); router.use(require("../../middlewares/auth")); router.use(require("../middlewares/timestampValidator")); +router.post("/share-room", quests.roomShareHandler); router.post("/instagram/share-event", quests.instagramEventShareHandler); router.post("/instagram/share-purchase", quests.instagramPurchaseShareHandler); diff --git a/src/lottery/services/quests.js b/src/lottery/services/quests.js index 7212893f..1ba5df08 100644 --- a/src/lottery/services/quests.js +++ b/src/lottery/services/quests.js @@ -1,6 +1,23 @@ const logger = require("../../modules/logger"); const contracts = require("../modules/contracts/2023fall"); +/** + * 방을 공유했을 때. + */ +const roomShareHandler = async (req, res) => { + try { + const { userOid } = req; + const contractResult = await contracts.completeRoomSharingQuest( + userOid, + req.timestamp + ); + res.json({ result: !!contractResult }); + } catch (err) { + logger.error(err); + res.status(500).json({ error: "Quests/ShareRoom: internal server error" }); + } +}; + /** * 인스타그램 스토리에 이벤트를 공유했을 때. */ @@ -41,6 +58,7 @@ const instagramPurchaseShareHandler = async (req, res) => { }; module.exports = { + roomShareHandler, instagramEventShareHandler, instagramPurchaseShareHandler, }; From 58602dd85e9b2503c523f3e7d8943dc487c678c4 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 23 Sep 2023 00:12:29 +0900 Subject: [PATCH 212/261] Add: /event/2023fall/quests/complete endpoint --- src/lottery/modules/contracts/2023fall.js | 46 ++-------------- src/lottery/modules/quests.js | 3 ++ src/lottery/routes/docs/globalState.js | 10 +++- src/lottery/routes/docs/items.js | 10 ++++ src/lottery/routes/docs/quests.js | 38 ++++--------- src/lottery/routes/docs/questsSchema.js | 13 +++++ src/lottery/routes/docs/swaggerDocs.js | 24 +++++---- src/lottery/routes/quests.js | 14 +++-- src/lottery/services/quests.js | 65 +++++------------------ 9 files changed, 84 insertions(+), 139 deletions(-) create mode 100644 src/lottery/routes/docs/questsSchema.js diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 10f562c1..a896a6ce 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -37,6 +37,7 @@ const quests = buildQuests({ imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_roomSharing.png", reward: 50, + isApiRequired: true, }, paying: { name: "정산해요 택시의 숲", @@ -87,6 +88,7 @@ const quests = buildQuests({ imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_eventSharingOnInstagram.png", reward: 100, + isApiRequired: true, }, purchaseSharingOnInstagram: { name: "상품 획득을 축하합니다", @@ -95,6 +97,7 @@ const quests = buildQuests({ imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/quest_purchaseSharingOnInstagram.png", reward: 100, + isApiRequired: true, }, }); @@ -137,18 +140,6 @@ const completeFirstRoomCreationQuest = async (userId, timestamp) => { return await completeQuest(userId, timestamp, quests.firstRoomCreation); }; -/** - * roomSharing 퀘스트의 완료를 요청합니다. - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. - * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. - * @returns {Promise} - * @description 인스타그램 스토리에 추석 이벤트를 공유할 때마다 호출해 주세요. - * @usage quests - instagramEventShareHandler - */ -const completeRoomSharingQuest = async (userId, timestamp) => { - return await completeQuest(userId, timestamp, quests.roomSharing); -}; - /** * paying 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. @@ -227,45 +218,14 @@ const completeAdPushAgreementQuest = async ( return await completeQuest(userId, timestamp, quests.adPushAgreement); }; -/** - * eventSharingOnInstagram 퀘스트의 완료를 요청합니다. - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. - * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. - * @returns {Promise} - * @description 인스타그램 스토리에 추석 이벤트를 공유할 때마다 호출해 주세요. - * @usage quests - instagramEventShareHandler - */ -const completeEventSharingOnInstagramQuest = async (userId, timestamp) => { - return await completeQuest(userId, timestamp, quests.eventSharingOnInstagram); -}; - -/** - * purchaseSharingOnInstagram 퀘스트의 완료를 요청합니다. - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. - * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. - * @returns {Promise} - * @description 인스타그램 스토리에 구매한 아이템을 공유할 때마다 호출해 주세요. - * @usage quests - instagramPurchaseShareHandler - */ -const completePurchaseSharingOnInstagramQuest = async (userId, timestamp) => { - return await completeQuest( - userId, - timestamp, - quests.purchaseSharingOnInstagram - ); -}; - module.exports = { quests, completeFirstLoginQuest, completePayingAndSendingQuest, completeFirstRoomCreationQuest, - completeRoomSharingQuest, completePayingQuest, completeSendingQuest, completeNicknameChangingQuest, completeAccountChangingQuest, completeAdPushAgreementQuest, - completeEventSharingOnInstagramQuest, - completePurchaseSharingOnInstagramQuest, }; diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index d93ec66d..b61b3b77 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -42,6 +42,9 @@ const buildQuests = (quests) => { // quest.maxCount가 없는 경우, 기본값(1)으로 설정합니다. quest.maxCount = quest.maxCount || 1; + + // quest.isApiRequired가 없는 경우, 기본값(false)으로 설정합니다. + quest.isApiRequired = quest.isApiRequired || false; } return quests; diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 4cf9630b..077f7628 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -66,6 +66,7 @@ globalStateDocs[`${apiPrefix}/`] = { "imageUrl", "reward", "maxCount", + "isApiRequired", ], properties: { id: { @@ -111,6 +112,11 @@ globalStateDocs[`${apiPrefix}/`] = { description: "최대 완료 가능 횟수", example: 1, }, + isApiRequired: { + type: "boolean", + description: `/events/${eventConfig.mode}/quests/complete/:questId API를 통해 퀘스트 완료를 요청할 수 있는지 여부`, + example: false, + }, }, }, }, @@ -123,13 +129,13 @@ globalStateDocs[`${apiPrefix}/`] = { }, }; globalStateDocs[`${apiPrefix}/create`] = { - get: { + post: { tags: [`${apiPrefix}`], summary: "Frontend에서 Global state로 관리하는 정보 생성", description: "유저의 재화 개수, 퀘스트 완료 상태 등 Frontend에서 Global state로 관리할 정보를 생성합니다.", requestBody: { - description: "Update an existent user in the store", + description: "", content: { "application/json": { schema: { diff --git a/src/lottery/routes/docs/items.js b/src/lottery/routes/docs/items.js index 4079795a..39bc9111 100644 --- a/src/lottery/routes/docs/items.js +++ b/src/lottery/routes/docs/items.js @@ -37,6 +37,16 @@ itemsDocs[`${apiPrefix}/purchase/:itemId`] = { tags: [`${apiPrefix}`], summary: "상품 구매", description: "상품을 구매합니다.", + requestBody: { + description: "", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/purchaseHandler", + }, + }, + }, + }, responses: { 200: { description: "", diff --git a/src/lottery/routes/docs/quests.js b/src/lottery/routes/docs/quests.js index 1d6a89c5..7ba5a42c 100644 --- a/src/lottery/routes/docs/quests.js +++ b/src/lottery/routes/docs/quests.js @@ -2,39 +2,21 @@ const { eventConfig } = require("../../../../loadenv"); const apiPrefix = `/events/${eventConfig.mode}/quests`; const eventsDocs = {}; -eventsDocs[`${apiPrefix}/instagram/share-event`] = { +eventsDocs[`${apiPrefix}/complete/:questId`] = { post: { tags: [`${apiPrefix}`], - summary: "eventSharingOnInstagram 퀘스트 완료 요청", - description: "eventSharingOnInstagram 퀘스트의 완료를 요청합니다.", - responses: { - 200: { - description: "", - content: { - "application/json": { - schema: { - type: "object", - required: ["result"], - properties: { - result: { - type: "boolean", - description: "성공 여부", - example: true, - }, - }, - }, + summary: "퀘스트 완료 요청", + description: "퀘스트의 완료를 요청합니다.", + requestBody: { + description: "", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/completeHandler", }, }, }, }, - }, -}; - -eventsDocs[`${apiPrefix}/instagram/share-purchase`] = { - post: { - tags: [`${apiPrefix}`], - summary: "purchaseSharingOnInstagram 퀘스트 완료 요청", - description: "purchaseSharingOnInstagram 퀘스트의 완료를 요청합니다.", responses: { 200: { description: "", @@ -45,8 +27,8 @@ eventsDocs[`${apiPrefix}/instagram/share-purchase`] = { required: ["result"], properties: { result: { - description: "성공 여부", type: "boolean", + description: "성공 여부", example: true, }, }, diff --git a/src/lottery/routes/docs/questsSchema.js b/src/lottery/routes/docs/questsSchema.js new file mode 100644 index 00000000..f1a14a4e --- /dev/null +++ b/src/lottery/routes/docs/questsSchema.js @@ -0,0 +1,13 @@ +const questsSchema = { + completeHandler: { + type: "object", + required: ["questId"], + properties: { + questId: { + type: "string", + }, + }, + }, +}; + +module.exports = questsSchema; diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index 77a85f61..73fa7b66 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -1,11 +1,14 @@ -const { eventConfig } = require("../../../../loadenv"); const globalStateDocs = require("./globalState"); const itemsDocs = require("./items"); -const transactionsDocs = require("./transactions"); +const publicNoticeDocs = require("./publicNotice"); const questsDocs = require("./quests"); +const transactionsDocs = require("./transactions"); + const itemsSchema = require("./itemsSchema"); -const publicNoticeDocs = require("./publicNotice"); const globalStateSchema = require("./globalStateSchema"); +const questsSchema = require("./questsSchema"); + +const { eventConfig } = require("../../../../loadenv"); const apiPrefix = `/events/${eventConfig.mode}`; const eventSwaggerDocs = { @@ -19,29 +22,30 @@ const eventSwaggerDocs = { description: "이벤트 - 아이템 관련 API", }, { - name: `${apiPrefix}/transactions`, - description: "이벤트 - 입출금 내역 관련 API", + name: `${apiPrefix}/public-notice`, + description: "이벤트 - 아이템 구매, 뽑기, 획득 공지 관련 API", }, { name: `${apiPrefix}/quests`, description: "이벤트 - 퀘스트 관련 API", }, { - name: `${apiPrefix}/public-notice`, - description: "이벤트 - 아이템 구매, 뽑기, 획득 공지 관련 API", + name: `${apiPrefix}/transactions`, + description: "이벤트 - 입출금 내역 관련 API", }, ], paths: { ...globalStateDocs, ...itemsDocs, - ...transactionsDocs, - ...questsDocs, ...publicNoticeDocs, + ...questsDocs, + ...transactionsDocs, }, components: { schemas: { - ...itemsSchema, ...globalStateSchema, + ...itemsSchema, + ...questsSchema, }, }, }; diff --git a/src/lottery/routes/quests.js b/src/lottery/routes/quests.js index b1ec591b..6ff1246b 100644 --- a/src/lottery/routes/quests.js +++ b/src/lottery/routes/quests.js @@ -1,12 +1,18 @@ const express = require("express"); + const router = express.Router(); -const quests = require("../services/quests"); +const questsHandlers = require("../services/quests"); + +const { validateParams } = require("../../middlewares/ajv"); +const questsSchema = require("./docs/questsSchema"); router.use(require("../../middlewares/auth")); router.use(require("../middlewares/timestampValidator")); -router.post("/share-room", quests.roomShareHandler); -router.post("/instagram/share-event", quests.instagramEventShareHandler); -router.post("/instagram/share-purchase", quests.instagramPurchaseShareHandler); +router.post( + "/complete/:questId", + validateParams(questsSchema.completeHandler), + questsHandlers.completeHandler +); module.exports = router; diff --git a/src/lottery/services/quests.js b/src/lottery/services/quests.js index 1ba5df08..4f6c3271 100644 --- a/src/lottery/services/quests.js +++ b/src/lottery/services/quests.js @@ -1,64 +1,25 @@ +const { completeQuest } = require("../modules/quests"); const logger = require("../../modules/logger"); -const contracts = require("../modules/contracts/2023fall"); -/** - * 방을 공유했을 때. - */ -const roomShareHandler = async (req, res) => { - try { - const { userOid } = req; - const contractResult = await contracts.completeRoomSharingQuest( - userOid, - req.timestamp - ); - res.json({ result: !!contractResult }); - } catch (err) { - logger.error(err); - res.status(500).json({ error: "Quests/ShareRoom: internal server error" }); - } -}; +const { eventConfig } = require("../../../loadenv"); +const quests = eventConfig + ? require(`../modules/contracts/${eventConfig.mode}`).quests + : undefined; -/** - * 인스타그램 스토리에 이벤트를 공유했을 때. - */ -const instagramEventShareHandler = async (req, res) => { +const completeHandler = async (req, res) => { try { - const { userOid } = req; - const contractResult = await contracts.completeEventSharingOnInstagramQuest( - userOid, - req.timestamp - ); - res.json({ result: !!contractResult }); - } catch (err) { - logger.error(err); - res - .status(500) - .json({ error: "Quests/Instagram/ShareEvent: internal server error" }); - } -}; + const quest = quests[req.params.questId]; + if (!quest?.isApiRequired) + return res.status(400).json({ error: "Quests/Complete: invalid Quest" }); -/** - * 인스타그램 스토리에 아이템 구매 내역을 공유했을 때. - */ -const instagramPurchaseShareHandler = async (req, res) => { - try { - const { userOid } = req; - const contractResult = - await contracts.completePurchaseSharingOnInstagramQuest( - userOid, - req.timestamp - ); - res.json({ result: !!contractResult }); + const result = await completeQuest(req.userOid, req.timestamp, quest); + res.json({ result: !!result }); // boolean으로 변환하기 위해 !!를 사용합니다. } catch (err) { logger.error(err); - res - .status(500) - .json({ error: "Quests/Instagram/SharePurchase: internal server error" }); + res.status(500).json({ error: "Quests/Complete: internal server error" }); } }; module.exports = { - roomShareHandler, - instagramEventShareHandler, - instagramPurchaseShareHandler, + completeHandler, }; From 34e2bcb9cfeb6c8d44ecdf10972d8b24293e8761 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 23 Sep 2023 00:22:01 +0900 Subject: [PATCH 213/261] Add: instagramStoryStickerImageUrl field in itemSchema --- src/lottery/modules/populates/transactions.js | 2 +- src/lottery/modules/stores/mongo.js | 3 +++ src/lottery/routes/docs/itemsSchema.js | 5 +++++ src/lottery/services/items.js | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lottery/modules/populates/transactions.js b/src/lottery/modules/populates/transactions.js index bcf84863..4dfc11e9 100644 --- a/src/lottery/modules/populates/transactions.js +++ b/src/lottery/modules/populates/transactions.js @@ -1,7 +1,7 @@ const transactionPopulateOption = [ { path: "item", - select: "name imageUrl price description isDisabled stock itemType", + select: "-isRandomItem -randomWeight", }, ]; diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 2e32978d..9db6905d 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -57,6 +57,9 @@ const itemSchema = Schema({ type: String, required: true, }, + instagramStoryStickerImageUrl: { + type: String, + }, price: { type: Number, required: true, diff --git a/src/lottery/routes/docs/itemsSchema.js b/src/lottery/routes/docs/itemsSchema.js index 2a4eca64..2549540b 100644 --- a/src/lottery/routes/docs/itemsSchema.js +++ b/src/lottery/routes/docs/itemsSchema.js @@ -26,6 +26,11 @@ const itemBase = { description: "이미지 썸네일 URL", example: "THUMBNAIL URL", }, + instagramStoryStickerImageUrl: { + type: "string", + description: "인스타그램 스토리 스티커 이미지 URL", + example: "STICKER URL", + }, price: { type: "number", description: "상품의 가격. 0 이상입니다.", diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index f4eeccdb..eba71923 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -110,7 +110,7 @@ const getRandomItem = async (req, depth) => { const listHandler = async (_, res) => { try { const items = await itemModel - .find({}, "name imageUrl price description isDisabled stock itemType") + .find({}, "-isRandomItem -randomWeight") .lean(); res.json({ items: items.map(hideItemStock) }); } catch (err) { From 08451e9db353f802fe1ff20a57bd11f05dec78de Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Sat, 23 Sep 2023 00:26:50 +0900 Subject: [PATCH 214/261] Add: Advanced getValueRank calculating function --- src/lottery/services/publicNotice.js | 54 ++++++++++++++++------------ 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index ce0f5633..2359462b 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -7,18 +7,27 @@ const { publicNoticePopulateOption, } = require("../modules/populates/transactions"); -const hideNickname = (nickname) => { - return `${nickname.toString().slice(0, 2)}${"*".repeat(nickname.length - 2)}`; -}; - +/** + * getValueRank 사용자의 상품 구매 내역 또는 경품 추첨 내역의 순위 결정을 위한 가치를 평가하는 함수 + * 상품 가격이 높을수록, 상품 구매 일시가 최근일 수록 가치가 높습니다. + * 요청이 들어온 시간과 트랜젝션이 있었던 시간의 차를 로그스케일로 변환후 이를 가격에 곱하여 가치를 구합니다. + * 시간의 단위는 millisecond입니다. + * t_1/2(반감기, half-life)는 4일입니다 . + * (2일 = 2 * 24 * 60 * 60 * 1000 = 172800000ms) + * Tau는 반감기를 결정하는 상수입니다. + * Tau = t_1/2 / ln(2) 로 구할 수 있습니다. + * Tau = 249297703 + * N_0(초기값)는 item.price를 사용합니다. + * @param {Object} item + * @param {number|Date} createAt + * @param {number|Date} timestamp + * @returns {Promise} + * @description 가치를 기준으로 정렬하기 위해 사용됨 + */ const getValueRank = (item, createAt, timestamp) => { - return ( - (parseInt( - (new Date(createAt).getTime() / timestamp).toString().slice(7, 10) - ) * - item.price) / - 1000 - ); + const t = timestamp - new Date(createAt).getTime(); // millisecond + const Tau = 249297703; + return item.price * Math.exp(-t / Tau); }; const getRecentPurchaceItemListHandler = async (req, res) => { @@ -37,16 +46,17 @@ const getRecentPurchaceItemListHandler = async (req, res) => { getValueRank(x.item, x.createAt, req.timestamp) ) .slice(0, 5) - .map( - ({ userId, item, comment }) => - `${hideNickname(userId.nickname)}님께서 ${item.name}을(를) ${ - comment.startsWith("송편") - ? " 을(를) 구입하셨습니다." - : comment.startsWith("랜덤 박스") - ? "을(를) 뽑았습니다." - : "을(를) 획득하셨습니다." - }` - ); + .map(({ userId, item, comment, createAt }) => ({ + text: `${userId.nickname}님께서 ${item.name}${ + comment.startsWith("송편") + ? "을(를) 구입하셨습니다." + : comment.startsWith("랜덤 박스") + ? "을(를) 뽑았습니다." + : "을(를) 획득하셨습니다." + }`, + createAt, + })); + console.log(transactions); res.json({ transactions }); } catch (err) { logger.error(err); @@ -90,7 +100,7 @@ const getTicketLeaderboardHandler = async (req, res) => { return null; } return { - nickname: hideNickname(userInfo.nickname), + nickname: userInfo.nickname, profileImageUrl: userInfo.profileImageUrl, ticket1Amount: user.ticket1Amount, ticket2Amount: user.ticket2Amount, From 96d0f9480370fd57d51a0bce8d1a1a926b1ebe14 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Sat, 23 Sep 2023 00:33:29 +0900 Subject: [PATCH 215/261] Remove: console log deleted --- src/lottery/services/publicNotice.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index fe5d853d..475f1d2a 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -56,7 +56,6 @@ const getRecentPurchaceItemListHandler = async (req, res) => { }`, createAt, })); - console.log(transactions); res.json({ transactions }); } catch (err) { logger.error(err); From e6c5a0a957406ca452f59881a95e8d1272d37b68 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 23 Sep 2023 01:01:02 +0900 Subject: [PATCH 216/261] Refactor: use nullish coalescing operator --- src/lottery/modules/quests.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index b61b3b77..539bfc41 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -37,14 +37,14 @@ const buildQuests = (quests) => { } // quest.reward에 누락된 필드가 있는 경우, 기본값(0)으로 설정합니다. - quest.reward.credit = quest.reward.credit || 0; - quest.reward.ticket1 = quest.reward.ticket1 || 0; + quest.reward.credit = quest.reward.credit ?? 0; + quest.reward.ticket1 = quest.reward.ticket1 ?? 0; // quest.maxCount가 없는 경우, 기본값(1)으로 설정합니다. - quest.maxCount = quest.maxCount || 1; + quest.maxCount = quest.maxCount ?? 1; // quest.isApiRequired가 없는 경우, 기본값(false)으로 설정합니다. - quest.isApiRequired = quest.isApiRequired || false; + quest.isApiRequired = quest.isApiRequired ?? false; } return quests; From 276495ed9bb5daa5f8a4353abfd35eab04854045 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 23 Sep 2023 14:59:48 +0900 Subject: [PATCH 217/261] Refactor: resolve comments --- src/lottery/modules/populates/transactions.js | 3 ++- src/lottery/routes/docs/questsSchema.js | 5 +++++ src/lottery/services/items.js | 5 ++++- src/lottery/services/quests.js | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/lottery/modules/populates/transactions.js b/src/lottery/modules/populates/transactions.js index 4dfc11e9..6d965258 100644 --- a/src/lottery/modules/populates/transactions.js +++ b/src/lottery/modules/populates/transactions.js @@ -1,7 +1,8 @@ const transactionPopulateOption = [ { path: "item", - select: "-isRandomItem -randomWeight", + select: + "name imageUrl instagramStoryStickerImageUrl price description isDisabled stock itemType", }, ]; diff --git a/src/lottery/routes/docs/questsSchema.js b/src/lottery/routes/docs/questsSchema.js index f1a14a4e..3432cc8c 100644 --- a/src/lottery/routes/docs/questsSchema.js +++ b/src/lottery/routes/docs/questsSchema.js @@ -5,6 +5,11 @@ const questsSchema = { properties: { questId: { type: "string", + enum: [ + "roomSharing", + "eventSharingOnInstagram", + "purchaseSharingOnInstagram", + ], }, }, }, diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index eba71923..f122481a 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -110,7 +110,10 @@ const getRandomItem = async (req, depth) => { const listHandler = async (_, res) => { try { const items = await itemModel - .find({}, "-isRandomItem -randomWeight") + .find( + {}, + "name imageUrl instagramStoryStickerImageUrl price description isDisabled stock itemType" + ) .lean(); res.json({ items: items.map(hideItemStock) }); } catch (err) { diff --git a/src/lottery/services/quests.js b/src/lottery/services/quests.js index 4f6c3271..5dd4f0e5 100644 --- a/src/lottery/services/quests.js +++ b/src/lottery/services/quests.js @@ -9,7 +9,7 @@ const quests = eventConfig const completeHandler = async (req, res) => { try { const quest = quests[req.params.questId]; - if (!quest?.isApiRequired) + if (!quest || !quest.isApiRequired) return res.status(400).json({ error: "Quests/Complete: invalid Quest" }); const result = await completeQuest(req.userOid, req.timestamp, quest); From 1dea168ada4c4d3b36ef6564316643e80e8bf8f0 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 23 Sep 2023 20:48:39 +0900 Subject: [PATCH 218/261] Fix: initial creditAmount --- src/lottery/services/globalState.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index c36ac9f1..13ce9bb7 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -47,6 +47,7 @@ const createUserGlobalStateHandler = async (req, res) => { eventStatus = new eventStatusModel({ userId: req.userOid, + creditAmount: 100, // 초기 송편 개수는 0개가 아닌 100개입니다. }); await eventStatus.save(); From ae6f35f906ab4b195f7e26cf68ad96f175798b0e Mon Sep 17 00:00:00 2001 From: static Date: Sun, 24 Sep 2023 18:32:33 +0900 Subject: [PATCH 219/261] Add: new leaderboard probability model --- src/lottery/services/publicNotice.js | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 62421bbd..e6e1e6f9 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -35,6 +35,25 @@ const getRecentPurchaceItemListHandler = async (req, res) => { } }; +const calculateProbabilityV2 = (users, weightSum, base, weight) => { + // 유저 수가 상품 수보다 적거나 같으면 무조건 상품을 받게된다. + if (users.length <= 15) return 1; + + /** + * 경험적으로 발견한 사실 + * + * p를 에어팟 당첨 확률이라고 하자. + * 모든 유저의 p값이 1/15 미만일 경우, 실제 당첨 확률은 15p이다. + * 그렇지 않은 경우, 실제 당첨 확률은 1-a^p꼴의 지수함수를 따른다. + * + * 계산 과정 + * a는 유저 수, 전체 티켓 수, 티켓 분포에 의해 결정되는 값으로, 현실적으로 계산하기 어렵다. + * 따라서, 모든 유저가 같은 수의 티켓을 가지고 있다고 가정하고 a를 계산한 뒤, 이를 확률 계산에 사용한다. + */ + if (base !== null) return 1 - Math.pow(base, weight); + else return (weight / weightSum) * 15; +}; + const getTicketLeaderboardHandler = async (req, res) => { try { const users = await eventStatusModel @@ -60,6 +79,11 @@ const getTicketLeaderboardHandler = async (req, res) => { } return before + user.weight; }, 0); + const isExponential = + sortedUsers.find((user) => user.weight >= weightSum / 15) != undefined; + const base = isExponential + ? Math.pow(1 - 15 / users.length, users.length / weightSum) + : null; const leaderboard = await Promise.all( sortedUsers.slice(0, 20).map(async (user) => { @@ -74,6 +98,12 @@ const getTicketLeaderboardHandler = async (req, res) => { ticket1Amount: user.ticket1Amount, ticket2Amount: user.ticket2Amount, probability: user.weight / weightSum, + probabilityV2: calculateProbabilityV2( + users, + weightSum, + base, + user.weight + ), }; }) ); @@ -87,6 +117,12 @@ const getTicketLeaderboardHandler = async (req, res) => { leaderboard, rank: rank + 1, probability: sortedUsers[rank].weight / weightSum, + probabilityV2: calculateProbabilityV2( + users, + weightSum, + base, + sortedUsers[rank].weight + ), }); else res.json({ leaderboard }); } catch (err) { From 86ffd0729ee8abb7c794ca167658d0d6f6dcc70b Mon Sep 17 00:00:00 2001 From: static Date: Sun, 24 Sep 2023 21:04:00 +0900 Subject: [PATCH 220/261] Docs: describe probabilityV2 field --- src/lottery/routes/docs/publicNotice.js | 11 +++++++++++ src/lottery/services/publicNotice.js | 11 +++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js index ca30cfb9..71e9e3a3 100644 --- a/src/lottery/routes/docs/publicNotice.js +++ b/src/lottery/routes/docs/publicNotice.js @@ -59,6 +59,7 @@ publicNoticeDocs[`${apiPrefix}/leaderboard`] = { "ticket1Amount", "ticket2Amount", "probability", + "probabilityV2", ], properties: { nickname: { @@ -86,6 +87,11 @@ publicNoticeDocs[`${apiPrefix}/leaderboard`] = { description: "1등 당첨 확률", example: 0.001, }, + probabilityV2: { + type: "number", + description: "근사적인 상품 당첨 확률", + example: 0.015, + }, }, }, }, @@ -99,6 +105,11 @@ publicNoticeDocs[`${apiPrefix}/leaderboard`] = { description: "1등 당첨 확률", example: 0.00003, }, + probabilityV2: { + type: "number", + description: "근사적인 상품 당첨 확률", + example: 0.00045, + }, }, }, }, diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index e6e1e6f9..b58e41be 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -42,13 +42,20 @@ const calculateProbabilityV2 = (users, weightSum, base, weight) => { /** * 경험적으로 발견한 사실 * - * p를 에어팟 당첨 확률이라고 하자. + * p를 에어팟 당첨 확률, M을 전체 티켓 수라고 하자. * 모든 유저의 p값이 1/15 미만일 경우, 실제 당첨 확률은 15p이다. - * 그렇지 않은 경우, 실제 당첨 확률은 1-a^p꼴의 지수함수를 따른다. + * 그렇지 않은 경우, 실제 당첨 확률은 1-a^Mp꼴의 지수함수를 따른다. (Note: Mp는 티켓 수이다.) * * 계산 과정 + * * a는 유저 수, 전체 티켓 수, 티켓 분포에 의해 결정되는 값으로, 현실적으로 계산하기 어렵다. * 따라서, 모든 유저가 같은 수의 티켓을 가지고 있다고 가정하고 a를 계산한 뒤, 이를 확률 계산에 사용한다. + * + * a값의 계산 과정 + * + * N을 유저 수라고 하자. 모든 유저가 같은 수의 티켓 M/N개를 가지고 있다고 하자. + * 이때 기대되는 당첨 확률은 직관적으로 15/N임을 알 수 있다. 즉, 1-a^(M/N) = 15/N이다. + * a에 대해 정리하면, a = (1-15/N)^(N/M)임을 알 수 있다. */ if (base !== null) return 1 - Math.pow(base, weight); else return (weight / weightSum) * 15; From c4a69c3fa5f745129862af08716a60e4b55400e9 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 24 Sep 2023 21:41:29 +0900 Subject: [PATCH 221/261] Add: totalTicketAmount and totalUserAmount field --- src/lottery/routes/docs/publicNotice.js | 12 +++++++++++- src/lottery/services/publicNotice.js | 9 ++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js index 71e9e3a3..eed3a652 100644 --- a/src/lottery/routes/docs/publicNotice.js +++ b/src/lottery/routes/docs/publicNotice.js @@ -46,7 +46,7 @@ publicNoticeDocs[`${apiPrefix}/leaderboard`] = { "application/json": { schema: { type: "object", - required: ["leaderboard"], + required: ["leaderboard", "totalTicketAmount", "totalUserAmount"], properties: { leaderboard: { type: "array", @@ -95,6 +95,16 @@ publicNoticeDocs[`${apiPrefix}/leaderboard`] = { }, }, }, + totalTicketAmount: { + type: "number", + description: "전체 티켓의 수", + example: 300, + }, + totalUserAmount: { + type: "number", + description: "리더보드에 포함된 유저의 수", + example: 100, + }, rank: { type: "number", description: "유저의 리더보드 순위. 1부터 시작합니다.", diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index b58e41be..ccd66aa0 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -122,6 +122,8 @@ const getTicketLeaderboardHandler = async (req, res) => { if (rank >= 0) res.json({ leaderboard, + totalTicketAmount: weightSum, + totalUserAmount: users.length, rank: rank + 1, probability: sortedUsers[rank].weight / weightSum, probabilityV2: calculateProbabilityV2( @@ -131,7 +133,12 @@ const getTicketLeaderboardHandler = async (req, res) => { sortedUsers[rank].weight ), }); - else res.json({ leaderboard }); + else + res.json({ + leaderboard, + totalTicketAmount: weightSum, + totalUserAmount: users.length, + }); } catch (err) { logger.error(err); res From a9ee2367d1d2ed50e45d4e5cb0157390f8188158 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 24 Sep 2023 23:12:32 +0900 Subject: [PATCH 222/261] Add: totalTicket1Amount and totalTicket2Amount field --- src/lottery/routes/docs/publicNotice.js | 16 +++++++++++++--- src/lottery/services/publicNotice.js | 19 ++++++++++++++----- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js index eed3a652..e5ce2f55 100644 --- a/src/lottery/routes/docs/publicNotice.js +++ b/src/lottery/routes/docs/publicNotice.js @@ -46,7 +46,12 @@ publicNoticeDocs[`${apiPrefix}/leaderboard`] = { "application/json": { schema: { type: "object", - required: ["leaderboard", "totalTicketAmount", "totalUserAmount"], + required: [ + "leaderboard", + "totalTicket1Amount", + "totalTicket2Amount", + "totalUserAmount", + ], properties: { leaderboard: { type: "array", @@ -95,11 +100,16 @@ publicNoticeDocs[`${apiPrefix}/leaderboard`] = { }, }, }, - totalTicketAmount: { + totalTicket1Amount: { type: "number", - description: "전체 티켓의 수", + description: "전체 일반 티켓의 수", example: 300, }, + totalTicket2Amount: { + type: "number", + description: "전체 고급 티켓의 수", + example: 100, + }, totalUserAmount: { type: "number", description: "리더보드에 포함된 유저의 수", diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 94d25e8c..87b77428 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -110,12 +110,19 @@ const getTicketLeaderboardHandler = async (req, res) => { const userId = isLogin(req) ? getLoginInfo(req).oid : null; let rank = -1; - const weightSum = sortedUsers.reduce((before, user, index) => { + let weightSum = 0; + let totalTicket1Amount = 0; + let totalTicket2Amount = 0; + for (const user of sortedUsers) { + weightSum += user.weight; + totalTicket1Amount += user.ticket1Amount; + totalTicket2Amount += user.ticket2Amount; + if (rank < 0 && user.userId === userId) { rank = index; } - return before + user.weight; - }, 0); + } + const isExponential = sortedUsers.find((user) => user.weight >= weightSum / 15) != undefined; const base = isExponential @@ -152,7 +159,8 @@ const getTicketLeaderboardHandler = async (req, res) => { if (rank >= 0) res.json({ leaderboard, - totalTicketAmount: weightSum, + totalTicket1Amount, + totalTicket2Amount, totalUserAmount: users.length, rank: rank + 1, probability: sortedUsers[rank].weight / weightSum, @@ -166,7 +174,8 @@ const getTicketLeaderboardHandler = async (req, res) => { else res.json({ leaderboard, - totalTicketAmount: weightSum, + totalTicket1Amount, + totalTicket2Amount, totalUserAmount: users.length, }); } catch (err) { From fbf7cc945bf2c05f58feb7af0b12ed55ab28d517 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 24 Sep 2023 23:22:59 +0900 Subject: [PATCH 223/261] Refactor: use reduce function instead of for loop --- src/lottery/services/publicNotice.js | 30 +++++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 87b77428..cd78b84d 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -110,18 +110,24 @@ const getTicketLeaderboardHandler = async (req, res) => { const userId = isLogin(req) ? getLoginInfo(req).oid : null; let rank = -1; - let weightSum = 0; - let totalTicket1Amount = 0; - let totalTicket2Amount = 0; - for (const user of sortedUsers) { - weightSum += user.weight; - totalTicket1Amount += user.ticket1Amount; - totalTicket2Amount += user.ticket2Amount; - - if (rank < 0 && user.userId === userId) { - rank = index; - } - } + const [weightSum, totalTicket1Amount, totalTicket2Amount] = + sortedUsers.reduce( + ( + [_weightSum, _totalTicket1Amount, _totalTicket2Amount], + user, + index + ) => { + if (rank < 0 && user.userId === userId) { + rank = index; + } + return [ + _weightSum + user.weight, + _totalTicket1Amount + user.ticket1Amount, + _totalTicket2Amount + user.ticket2Amount, + ]; + }, + [0, 0, 0] + ); const isExponential = sortedUsers.find((user) => user.weight >= weightSum / 15) != undefined; From 560b1743fb9aaef488aeae4432893f0000444ede Mon Sep 17 00:00:00 2001 From: static Date: Sun, 24 Sep 2023 23:33:45 +0900 Subject: [PATCH 224/261] Refactor: resolve comment --- src/lottery/services/publicNotice.js | 42 +++++++++++++--------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index cd78b84d..379e08da 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -130,7 +130,7 @@ const getTicketLeaderboardHandler = async (req, res) => { ); const isExponential = - sortedUsers.find((user) => user.weight >= weightSum / 15) != undefined; + sortedUsers.find((user) => user.weight >= weightSum / 15) !== undefined; const base = isExponential ? Math.pow(1 - 15 / users.length, users.length / weightSum) : null; @@ -162,28 +162,24 @@ const getTicketLeaderboardHandler = async (req, res) => { .status(500) .json({ error: "PublicNotice/Leaderboard : internal server error" }); - if (rank >= 0) - res.json({ - leaderboard, - totalTicket1Amount, - totalTicket2Amount, - totalUserAmount: users.length, - rank: rank + 1, - probability: sortedUsers[rank].weight / weightSum, - probabilityV2: calculateProbabilityV2( - users, - weightSum, - base, - sortedUsers[rank].weight - ), - }); - else - res.json({ - leaderboard, - totalTicket1Amount, - totalTicket2Amount, - totalUserAmount: users.length, - }); + res.json({ + leaderboard, + totalTicket1Amount, + totalTicket2Amount, + totalUserAmount: users.length, + ...(rank >= 0 + ? { + rank: rank + 1, + probability: sortedUsers[rank].weight / weightSum, + probabilityV2: calculateProbabilityV2( + users, + weightSum, + base, + sortedUsers[rank].weight + ), + } + : {}), + }); } catch (err) { logger.error(err); res From 9e0fce447deae456b76b0c937f392664c5c24387 Mon Sep 17 00:00:00 2001 From: withsang Date: Mon, 25 Sep 2023 00:30:53 +0900 Subject: [PATCH 225/261] Fix: set express to trust proxy --- app.js | 3 +++ src/middlewares/limitRate.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app.js b/app.js index 689de7a9..3394dec0 100644 --- a/app.js +++ b/app.js @@ -19,6 +19,9 @@ connectDatabase(); app.use(express.urlencoded({ extended: false })); app.use(express.json()); +// reverse proxy가 설정한 헤더를 신뢰합니다. +app.set("trust proxy", true); + // [Middleware] CORS 설정 app.use(require("./src/middlewares/cors")); diff --git a/src/middlewares/limitRate.js b/src/middlewares/limitRate.js index b218b0ba..4cba6af3 100644 --- a/src/middlewares/limitRate.js +++ b/src/middlewares/limitRate.js @@ -2,7 +2,7 @@ const rateLimit = require("express-rate-limit"); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes - max: 1500, // Limit each IP to 100 requests per `window` (here, per 15 minutes) + max: 1500, // Limit each IP to 1500 requests per `window` (here, per 15 minutes) standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); From 9fbd0893da0de2f893650a1b43a159567e790e83 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Sun, 24 Sep 2023 15:43:31 +0000 Subject: [PATCH 226/261] Fix: remove redundatn populate option --- src/services/chats.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/services/chats.js b/src/services/chats.js index 34c32fc0..abcbd073 100644 --- a/src/services/chats.js +++ b/src/services/chats.js @@ -207,8 +207,7 @@ const readChatHandler = async (req, res) => { arrayFilters: [{ "updater.user": { $eq: user._id } }], } ) - .lean() - .populate(roomPopulateOption); + .lean(); if (!roomObject) { return res.status(404).send("Chat/read : cannot find room info"); From 75550df0ebf1b8ef0508972cb5643e8e069f675d Mon Sep 17 00:00:00 2001 From: static Date: Mon, 25 Sep 2023 14:33:47 +0900 Subject: [PATCH 227/261] Add: check if roomObject.time is included in eventPeriod --- src/lottery/modules/contracts/2023fall.js | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index a896a6ce..624a9bd9 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,5 +1,12 @@ const { buildQuests, completeQuest } = require("../quests"); const mongoose = require("mongoose"); +const logger = require("../../../modules/logger"); + +const { eventConfig } = require("../../../../loadenv"); +const eventPeriod = eventConfig && { + startAt: new Date(eventConfig.startAt), + endAt: new Date(eventConfig.endAt), +}; /** 전체 퀘스트 목록입니다. */ const quests = buildQuests({ @@ -117,13 +124,24 @@ const completeFirstLoginQuest = async (userId, timestamp) => { * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {Object} roomObject - 방의 정보입니다. + * @param {mongoose.Types.ObjectId} roomObject._id - 방의 ObjectId입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. + * @param {Date} roomObject.time - 출발 시각입니다. * @returns {Promise} * @description 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. * @usage rooms - commitPaymentHandler, rooms - settlementHandler */ const completePayingAndSendingQuest = async (userId, timestamp, roomObject) => { if (roomObject.part.length < 2) return null; + if ( + roomObject.time >= eventPeriod.endAt || + roomObject.time < eventPeriod.startAt + ) + return null; + + logger.info( + `User ${userId} requested to complete payingAndSendingQuest in Room ${roomObject._id}` + ); return await completeQuest(userId, timestamp, quests.payingAndSending); }; @@ -145,13 +163,24 @@ const completeFirstRoomCreationQuest = async (userId, timestamp) => { * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {Object} roomObject - 방의 정보입니다. + * @param {mongoose.Types.ObjectId} roomObject._id - 방의 ObjectId입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. + * @param {Date} roomObject.time - 출발 시각입니다. * @returns {Promise} * @description 정산 요청이 이루어질 때마다 호출해 주세요. * @usage rooms - commitPaymentHandler */ const completePayingQuest = async (userId, timestamp, roomObject) => { if (roomObject.part.length < 2) return null; + if ( + roomObject.time >= eventPeriod.endAt || + roomObject.time < eventPeriod.startAt + ) + return null; + + logger.info( + `User ${userId} requested to complete payingQuest in Room ${roomObject._id}` + ); return await completeQuest(userId, timestamp, quests.paying); }; @@ -161,13 +190,24 @@ const completePayingQuest = async (userId, timestamp, roomObject) => { * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {Object} roomObject - 방의 정보입니다. + * @param {mongoose.Types.ObjectId} roomObject._id - 방의 ObjectId입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. + * @param {Date} roomObject.time - 출발 시각입니다. * @returns {Promise} * @description 송금이 이루어질 때마다 호출해 주세요. * @usage rooms - settlementHandler */ const completeSendingQuest = async (userId, timestamp, roomObject) => { if (roomObject.part.length < 2) return null; + if ( + roomObject.time >= eventPeriod.endAt || + roomObject.time < eventPeriod.startAt + ) + return null; + + logger.info( + `User ${userId} requested to complete sendingQuest in Room ${roomObject._id}` + ); return await completeQuest(userId, timestamp, quests.sending); }; From 83bf23023cf731832c8df7ee680dfc5b9f94d074 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 25 Sep 2023 15:44:52 +0900 Subject: [PATCH 228/261] Refactor: resolve comment --- src/lottery/modules/contracts/2023fall.js | 30 +++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 624a9bd9..e318483c 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -132,16 +132,16 @@ const completeFirstLoginQuest = async (userId, timestamp) => { * @usage rooms - commitPaymentHandler, rooms - settlementHandler */ const completePayingAndSendingQuest = async (userId, timestamp, roomObject) => { + logger.info( + `User ${userId} requested to complete payingAndSendingQuest in Room ${roomObject._id}` + ); + if (roomObject.part.length < 2) return null; if ( roomObject.time >= eventPeriod.endAt || roomObject.time < eventPeriod.startAt ) - return null; - - logger.info( - `User ${userId} requested to complete payingAndSendingQuest in Room ${roomObject._id}` - ); + return null; // 택시 출발 시각이 이벤트 기간 내에 포함되지 않는 경우 퀘스트 완료 요청을 하지 않습니다. return await completeQuest(userId, timestamp, quests.payingAndSending); }; @@ -171,16 +171,16 @@ const completeFirstRoomCreationQuest = async (userId, timestamp) => { * @usage rooms - commitPaymentHandler */ const completePayingQuest = async (userId, timestamp, roomObject) => { + logger.info( + `User ${userId} requested to complete payingQuest in Room ${roomObject._id}` + ); + if (roomObject.part.length < 2) return null; if ( roomObject.time >= eventPeriod.endAt || roomObject.time < eventPeriod.startAt ) - return null; - - logger.info( - `User ${userId} requested to complete payingQuest in Room ${roomObject._id}` - ); + return null; // 택시 출발 시각이 이벤트 기간 내에 포함되지 않는 경우 퀘스트 완료 요청을 하지 않습니다. return await completeQuest(userId, timestamp, quests.paying); }; @@ -198,16 +198,16 @@ const completePayingQuest = async (userId, timestamp, roomObject) => { * @usage rooms - settlementHandler */ const completeSendingQuest = async (userId, timestamp, roomObject) => { + logger.info( + `User ${userId} requested to complete sendingQuest in Room ${roomObject._id}` + ); + if (roomObject.part.length < 2) return null; if ( roomObject.time >= eventPeriod.endAt || roomObject.time < eventPeriod.startAt ) - return null; - - logger.info( - `User ${userId} requested to complete sendingQuest in Room ${roomObject._id}` - ); + return null; // 택시 출발 시각이 이벤트 기간 내에 포함되지 않는 경우 퀘스트 완료 요청을 하지 않습니다. return await completeQuest(userId, timestamp, quests.sending); }; From 8697b053bbcd77594234cdc601452c89797195f7 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 26 Sep 2023 00:59:00 +0900 Subject: [PATCH 229/261] Add: blockedlist --- src/lottery/middlewares/blockedlist.js | 20 ++++++++++++++++++++ src/lottery/modules/quests.js | 5 +++-- src/lottery/modules/stores/mongo.js | 3 +++ src/lottery/routes/items.js | 3 ++- src/lottery/routes/quests.js | 1 + 5 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 src/lottery/middlewares/blockedlist.js diff --git a/src/lottery/middlewares/blockedlist.js b/src/lottery/middlewares/blockedlist.js new file mode 100644 index 00000000..47115c42 --- /dev/null +++ b/src/lottery/middlewares/blockedlist.js @@ -0,0 +1,20 @@ +const { eventStatusModel } = require("../modules/stores/mongo"); +const logger = require("../../modules/logger"); + +const isBanned = async (req, res, next) => { + try { + const eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); + if (eventStatus.isBanned) { + return res.status(400).json({ error: "Blockedlist : banned user" }); + } else { + next(); + } + } catch (err) { + logger.error(err); + res.error(500).json({ + error: "Blockedlist error : internal server error", + }); + } +}; + +module.exports = isBanned; diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index 539bfc41..2454caf3 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -8,6 +8,7 @@ const logger = require("../../modules/logger"); const mongoose = require("mongoose"); const { eventConfig } = require("../../../loadenv"); +const { isBanned } = require("../middlewares/blockedlist"); const eventPeriod = eventConfig && { startAt: new Date(eventConfig.startAt), endAt: new Date(eventConfig.endAt), @@ -65,9 +66,9 @@ const buildQuests = (quests) => { */ const completeQuest = async (userId, timestamp, quest) => { try { - // 1단계: 유저의 EventStatus를 가져옵니다. + // 1단계: 유저의 EventStatus를 가져옵니다. 블록드리스트인지도 확인합니다. const eventStatus = await eventStatusModel.findOne({ userId }).lean(); - if (!eventStatus) return null; + if (!eventStatus || eventStatus.isBanned) return null; // 2단계: 이벤트 기간인지 확인합니다. if (timestamp >= eventPeriod.endAt || timestamp < eventPeriod.startAt) { diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 9db6905d..0f495f67 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -34,6 +34,9 @@ const eventStatusSchema = Schema({ min: 0, validate: integerValidator, }, + isBanned: { + type: Boolean, + }, }); const questSchema = Schema({ diff --git a/src/lottery/routes/items.js b/src/lottery/routes/items.js index 21dc47ae..42117669 100644 --- a/src/lottery/routes/items.js +++ b/src/lottery/routes/items.js @@ -8,8 +8,9 @@ const itemsSchema = require("./docs/itemsSchema"); router.get("/list", itemsHandlers.listHandler); -// 아래의 Endpoint 접근 시 로그인 및 시각 체크 필요 +// 아래의 Endpoint 접근 시 로그인, 블록드리스트 및 시각 체크 필요 router.use(require("../../middlewares/auth")); +router.use(require("../middlewares/blockedlist")); router.use(require("../middlewares/timestampValidator")); router.post( diff --git a/src/lottery/routes/quests.js b/src/lottery/routes/quests.js index 6ff1246b..0aa56d17 100644 --- a/src/lottery/routes/quests.js +++ b/src/lottery/routes/quests.js @@ -7,6 +7,7 @@ const { validateParams } = require("../../middlewares/ajv"); const questsSchema = require("./docs/questsSchema"); router.use(require("../../middlewares/auth")); +router.use(require("../middlewares/blockedlist")); router.use(require("../middlewares/timestampValidator")); router.post( From 33680dec50bd54eb591686bea6ab215b8407da75 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 26 Sep 2023 01:29:37 +0900 Subject: [PATCH 230/261] Add: resolve complete --- src/lottery/middlewares/blockedlist.js | 12 +++++++----- src/lottery/modules/populates/publicNotice.js | 11 ----------- src/lottery/modules/quests.js | 1 - src/lottery/routes/items.js | 2 +- src/lottery/routes/quests.js | 2 +- 5 files changed, 9 insertions(+), 19 deletions(-) delete mode 100644 src/lottery/modules/populates/publicNotice.js diff --git a/src/lottery/middlewares/blockedlist.js b/src/lottery/middlewares/blockedlist.js index 47115c42..99b23ef0 100644 --- a/src/lottery/middlewares/blockedlist.js +++ b/src/lottery/middlewares/blockedlist.js @@ -1,20 +1,22 @@ const { eventStatusModel } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); -const isBanned = async (req, res, next) => { +const checkBanned = async (req, res, next) => { try { - const eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); + const eventStatus = await eventStatusModel + .findOne({ userId: req.userOid }) + .lean(); if (eventStatus.isBanned) { - return res.status(400).json({ error: "Blockedlist : banned user" }); + return res.status(400).json({ error: "BlockedList: banned user" }); } else { next(); } } catch (err) { logger.error(err); res.error(500).json({ - error: "Blockedlist error : internal server error", + error: "BlockedList: internal server error", }); } }; -module.exports = isBanned; +module.exports = checkBanned; diff --git a/src/lottery/modules/populates/publicNotice.js b/src/lottery/modules/populates/publicNotice.js deleted file mode 100644 index 5709ed7d..00000000 --- a/src/lottery/modules/populates/publicNotice.js +++ /dev/null @@ -1,11 +0,0 @@ -const publicNoticePopulateOption = [ - { path: "event" }, - { - path: "item", - select: "name price description itemType", - }, -]; - -module.exports = { - publicNoticePopulateOption, -}; diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index 2454caf3..c994a141 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -8,7 +8,6 @@ const logger = require("../../modules/logger"); const mongoose = require("mongoose"); const { eventConfig } = require("../../../loadenv"); -const { isBanned } = require("../middlewares/blockedlist"); const eventPeriod = eventConfig && { startAt: new Date(eventConfig.startAt), endAt: new Date(eventConfig.endAt), diff --git a/src/lottery/routes/items.js b/src/lottery/routes/items.js index 42117669..c8551346 100644 --- a/src/lottery/routes/items.js +++ b/src/lottery/routes/items.js @@ -10,7 +10,7 @@ router.get("/list", itemsHandlers.listHandler); // 아래의 Endpoint 접근 시 로그인, 블록드리스트 및 시각 체크 필요 router.use(require("../../middlewares/auth")); -router.use(require("../middlewares/blockedlist")); +router.use(require("../middlewares/blockedList")); router.use(require("../middlewares/timestampValidator")); router.post( diff --git a/src/lottery/routes/quests.js b/src/lottery/routes/quests.js index 0aa56d17..fc087dd8 100644 --- a/src/lottery/routes/quests.js +++ b/src/lottery/routes/quests.js @@ -7,7 +7,7 @@ const { validateParams } = require("../../middlewares/ajv"); const questsSchema = require("./docs/questsSchema"); router.use(require("../../middlewares/auth")); -router.use(require("../middlewares/blockedlist")); +router.use(require("../middlewares/blockedList")); router.use(require("../middlewares/timestampValidator")); router.post( From 2a99922e36b836b7b97fbcf754cb99ccfbb479f5 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 26 Sep 2023 01:50:58 +0900 Subject: [PATCH 231/261] Add: resolve complete --- src/lottery/middlewares/blockedlist.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lottery/middlewares/blockedlist.js b/src/lottery/middlewares/blockedlist.js index 99b23ef0..06141958 100644 --- a/src/lottery/middlewares/blockedlist.js +++ b/src/lottery/middlewares/blockedlist.js @@ -9,6 +9,7 @@ const checkBanned = async (req, res, next) => { if (eventStatus.isBanned) { return res.status(400).json({ error: "BlockedList: banned user" }); } else { + req.eventStatus = eventStatus; next(); } } catch (err) { From d916cbaa624e5b8cba91dbb2fd44543be6ae5a88 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 26 Sep 2023 01:52:31 +0900 Subject: [PATCH 232/261] Add: resolve comments --- src/lottery/routes/items.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lottery/routes/items.js b/src/lottery/routes/items.js index c8551346..f05298eb 100644 --- a/src/lottery/routes/items.js +++ b/src/lottery/routes/items.js @@ -8,7 +8,7 @@ const itemsSchema = require("./docs/itemsSchema"); router.get("/list", itemsHandlers.listHandler); -// 아래의 Endpoint 접근 시 로그인, 블록드리스트 및 시각 체크 필요 +// 아래의 Endpoint 접근 시 로그인, 블록드리스트 및 시각 체크 요구 router.use(require("../../middlewares/auth")); router.use(require("../middlewares/blockedList")); router.use(require("../middlewares/timestampValidator")); From be0be8930c58fa55ee9551a7968a9e5661b56d08 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 26 Sep 2023 02:00:23 +0900 Subject: [PATCH 233/261] Add: blockedList -> checkBanned --- src/lottery/middlewares/{blockedlist.js => checkBanned.js} | 4 ++-- src/lottery/routes/items.js | 4 ++-- src/lottery/routes/quests.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/lottery/middlewares/{blockedlist.js => checkBanned.js} (81%) diff --git a/src/lottery/middlewares/blockedlist.js b/src/lottery/middlewares/checkBanned.js similarity index 81% rename from src/lottery/middlewares/blockedlist.js rename to src/lottery/middlewares/checkBanned.js index 06141958..485d66a8 100644 --- a/src/lottery/middlewares/blockedlist.js +++ b/src/lottery/middlewares/checkBanned.js @@ -7,7 +7,7 @@ const checkBanned = async (req, res, next) => { .findOne({ userId: req.userOid }) .lean(); if (eventStatus.isBanned) { - return res.status(400).json({ error: "BlockedList: banned user" }); + return res.status(400).json({ error: "checkBanned: banned user" }); } else { req.eventStatus = eventStatus; next(); @@ -15,7 +15,7 @@ const checkBanned = async (req, res, next) => { } catch (err) { logger.error(err); res.error(500).json({ - error: "BlockedList: internal server error", + error: "checkBanned: internal server error", }); } }; diff --git a/src/lottery/routes/items.js b/src/lottery/routes/items.js index f05298eb..7d1513f5 100644 --- a/src/lottery/routes/items.js +++ b/src/lottery/routes/items.js @@ -8,9 +8,9 @@ const itemsSchema = require("./docs/itemsSchema"); router.get("/list", itemsHandlers.listHandler); -// 아래의 Endpoint 접근 시 로그인, 블록드리스트 및 시각 체크 요구 +// 아래의 Endpoint 접근 시 로그인, 블록드리스트 및 시각 체크 필요 router.use(require("../../middlewares/auth")); -router.use(require("../middlewares/blockedList")); +router.use(require("../middlewares/checkBanned")); router.use(require("../middlewares/timestampValidator")); router.post( diff --git a/src/lottery/routes/quests.js b/src/lottery/routes/quests.js index fc087dd8..2aa55624 100644 --- a/src/lottery/routes/quests.js +++ b/src/lottery/routes/quests.js @@ -7,7 +7,7 @@ const { validateParams } = require("../../middlewares/ajv"); const questsSchema = require("./docs/questsSchema"); router.use(require("../../middlewares/auth")); -router.use(require("../middlewares/blockedList")); +router.use(require("../middlewares/checkBanned")); router.use(require("../middlewares/timestampValidator")); router.post( From 2ccf016e58a0e843d2016aecc9a13937b32120a0 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 26 Sep 2023 02:12:45 +0900 Subject: [PATCH 234/261] Add: resolve comments --- src/lottery/middlewares/checkBanned.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lottery/middlewares/checkBanned.js b/src/lottery/middlewares/checkBanned.js index 485d66a8..4f168301 100644 --- a/src/lottery/middlewares/checkBanned.js +++ b/src/lottery/middlewares/checkBanned.js @@ -8,10 +8,9 @@ const checkBanned = async (req, res, next) => { .lean(); if (eventStatus.isBanned) { return res.status(400).json({ error: "checkBanned: banned user" }); - } else { - req.eventStatus = eventStatus; - next(); } + req.eventStatus = eventStatus; + next(); } catch (err) { logger.error(err); res.error(500).json({ From ca629de626d4c5914487b6753ad72f32b1b8a523 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 26 Sep 2023 17:21:53 +0900 Subject: [PATCH 235/261] Add: reduction in eventStatus query --- src/lottery/services/items.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index f122481a..9ed2dd0f 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -124,8 +124,7 @@ const listHandler = async (_, res) => { const purchaseHandler = async (req, res) => { try { - const eventStatus = await eventStatusModel.findOne({ userId: req.userOid }); - if (!eventStatus) + if (!req.eventStatus) return res .status(400) .json({ error: "Items/Purchase : nonexistent eventStatus" }); @@ -138,7 +137,7 @@ const purchaseHandler = async (req, res) => { // 구매 가능 조건: 크레딧이 충분하며, 재고가 남아있으며, 판매 중인 아이템이어야 합니다. if (item.isDisabled) return res.status(400).json({ error: "Items/Purchase : disabled item" }); - if (eventStatus.creditAmount < item.price) + if (req.eventStatus.creditAmount < item.price) return res .status(400) .json({ error: "Items/Purchase : not enough credit" }); From 0ca451c6b73aae28cacd90eefbe513df73ed4210 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 26 Sep 2023 22:57:32 +0900 Subject: [PATCH 236/261] Add: swaggerDocs and error handling --- src/lottery/middlewares/checkBanned.js | 14 ++++++++++ src/lottery/routes/docs/globalState.js | 5 ++++ src/lottery/routes/docs/items.js | 36 ++++++++++++++++++++++++++ src/lottery/routes/docs/quests.js | 18 +++++++++++++ src/lottery/services/items.js | 5 ---- 5 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/lottery/middlewares/checkBanned.js b/src/lottery/middlewares/checkBanned.js index 4f168301..830f5986 100644 --- a/src/lottery/middlewares/checkBanned.js +++ b/src/lottery/middlewares/checkBanned.js @@ -1,11 +1,25 @@ const { eventStatusModel } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); +/** + * + * @param {*} req eventStatus가 성공적일 경우 req.eventStatus = eventStatus로 들어갑니다. + * @param {*} res + * @param {*} next + * @returns + * 사용자가 차단 되었는지 여부를 판단합니다. + * 차단된 사용자는 이벤트에 한하여 서비스 이용에 제재를 받습니다. + */ const checkBanned = async (req, res, next) => { try { const eventStatus = await eventStatusModel .findOne({ userId: req.userOid }) .lean(); + if (!eventStatus) { + return res + .status(400) + .json({ error: "checkBanned: nonexistent eventStatus" }); + } if (eventStatus.isBanned) { return res.status(400).json({ error: "checkBanned: banned user" }); } diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 077f7628..d1dbff44 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -120,6 +120,11 @@ globalStateDocs[`${apiPrefix}/`] = { }, }, }, + isBanned: { + type: "boolean", + description: "해당 유저 제재 대상 여부", + example: false, + }, }, }, }, diff --git a/src/lottery/routes/docs/items.js b/src/lottery/routes/docs/items.js index 39bc9111..0d3cda40 100644 --- a/src/lottery/routes/docs/items.js +++ b/src/lottery/routes/docs/items.js @@ -29,6 +29,24 @@ itemsDocs[`${apiPrefix}/list`] = { }, }, }, + 400: { + description: + "해당 유저 제재 대상 여부checkBanned에서 이벤트에 동의하지 않은 사람과 제재 대상을 선별합니다.", + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + description: "", + example: "checkBanned: banned user", + }, + }, + }, + }, + }, + }, }, }, }; @@ -69,6 +87,24 @@ itemsDocs[`${apiPrefix}/purchase/:itemId`] = { }, }, }, + 400: { + description: + "checkBanned에서 이벤트에 동의하지 않은 사람과 제재 대상을 선별합니다.", + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + description: "", + example: "checkBanned: banned user", + }, + }, + }, + }, + }, + }, }, }, }; diff --git a/src/lottery/routes/docs/quests.js b/src/lottery/routes/docs/quests.js index 7ba5a42c..5842c210 100644 --- a/src/lottery/routes/docs/quests.js +++ b/src/lottery/routes/docs/quests.js @@ -36,6 +36,24 @@ eventsDocs[`${apiPrefix}/complete/:questId`] = { }, }, }, + 400: { + description: + "checkBanned에서 이벤트에 동의하지 않은 사람과 제재 대상을 선별합니다.", + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + description: "", + example: "checkBanned: banned user", + }, + }, + }, + }, + }, + }, }, }, }; diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 9ed2dd0f..21c4310c 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -124,11 +124,6 @@ const listHandler = async (_, res) => { const purchaseHandler = async (req, res) => { try { - if (!req.eventStatus) - return res - .status(400) - .json({ error: "Items/Purchase : nonexistent eventStatus" }); - const { itemId } = req.params; const item = await itemModel.findOne({ _id: itemId }).lean(); if (!item) From 5c25dabe6b86b40cbcd0365f73aedf5c96ee6a82 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 27 Sep 2023 00:26:51 +0900 Subject: [PATCH 237/261] Add: resolve comments --- src/lottery/middlewares/checkBanned.js | 5 ++--- src/lottery/routes/docs/items.js | 19 +------------------ src/lottery/routes/docs/quests.js | 1 + 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/src/lottery/middlewares/checkBanned.js b/src/lottery/middlewares/checkBanned.js index 830f5986..dca4c310 100644 --- a/src/lottery/middlewares/checkBanned.js +++ b/src/lottery/middlewares/checkBanned.js @@ -2,13 +2,12 @@ const { eventStatusModel } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); /** - * + * 사용자가 차단 되었는지 여부를 판단합니다. + * 차단된 사용자는 이벤트에 한하여 서비스 이용에 제재를 받습니다. * @param {*} req eventStatus가 성공적일 경우 req.eventStatus = eventStatus로 들어갑니다. * @param {*} res * @param {*} next * @returns - * 사용자가 차단 되었는지 여부를 판단합니다. - * 차단된 사용자는 이벤트에 한하여 서비스 이용에 제재를 받습니다. */ const checkBanned = async (req, res, next) => { try { diff --git a/src/lottery/routes/docs/items.js b/src/lottery/routes/docs/items.js index 0d3cda40..1137dc2b 100644 --- a/src/lottery/routes/docs/items.js +++ b/src/lottery/routes/docs/items.js @@ -29,24 +29,6 @@ itemsDocs[`${apiPrefix}/list`] = { }, }, }, - 400: { - description: - "해당 유저 제재 대상 여부checkBanned에서 이벤트에 동의하지 않은 사람과 제재 대상을 선별합니다.", - content: { - "application/json": { - schema: { - type: "object", - properties: { - error: { - type: "string", - description: "", - example: "checkBanned: banned user", - }, - }, - }, - }, - }, - }, }, }, }; @@ -94,6 +76,7 @@ itemsDocs[`${apiPrefix}/purchase/:itemId`] = { "application/json": { schema: { type: "object", + required: ["error"], properties: { error: { type: "string", diff --git a/src/lottery/routes/docs/quests.js b/src/lottery/routes/docs/quests.js index 5842c210..cd624688 100644 --- a/src/lottery/routes/docs/quests.js +++ b/src/lottery/routes/docs/quests.js @@ -43,6 +43,7 @@ eventsDocs[`${apiPrefix}/complete/:questId`] = { "application/json": { schema: { type: "object", + required: ["error"], properties: { error: { type: "string", From 893cff05805a2170bd31aba6e55cd1defe7f64cd Mon Sep 17 00:00:00 2001 From: static Date: Wed, 27 Sep 2023 02:56:58 +0900 Subject: [PATCH 238/261] Remove: linear model in leaderboard probability model --- src/lottery/services/publicNotice.js | 42 +++++++++------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/src/lottery/services/publicNotice.js b/src/lottery/services/publicNotice.js index 379e08da..f81aaf87 100644 --- a/src/lottery/services/publicNotice.js +++ b/src/lottery/services/publicNotice.js @@ -65,30 +65,27 @@ const getRecentPurchaceItemListHandler = async (req, res) => { } }; -const calculateProbabilityV2 = (users, weightSum, base, weight) => { +const calculateProbabilityV2 = (users, weightSum, weight) => { // 유저 수가 상품 수보다 적거나 같으면 무조건 상품을 받게된다. if (users.length <= 15) return 1; /** - * 경험적으로 발견한 사실 + * 실험적으로 발견한 사실 * - * p를 에어팟 당첨 확률, M을 전체 티켓 수라고 하자. - * 모든 유저의 p값이 1/15 미만일 경우, 실제 당첨 확률은 15p이다. - * 그렇지 않은 경우, 실제 당첨 확률은 1-a^Mp꼴의 지수함수를 따른다. (Note: Mp는 티켓 수이다.) - * - * 계산 과정 - * - * a는 유저 수, 전체 티켓 수, 티켓 분포에 의해 결정되는 값으로, 현실적으로 계산하기 어렵다. - * 따라서, 모든 유저가 같은 수의 티켓을 가지고 있다고 가정하고 a를 계산한 뒤, 이를 확률 계산에 사용한다. + * x를 티켓 수라고 하면, 실제 당첨 확률은 1-a^x꼴의 지수함수를 따르는 것을 시뮬레이션을 통해 발견하였다. + * 이때 a는 전체 유저 수, 전체 티켓 수, 각 유저의 티켓 수에 의해 결정되는 값이다. * * a값의 계산 과정 * - * N을 유저 수라고 하자. 모든 유저가 같은 수의 티켓 M/N개를 가지고 있다고 하자. - * 이때 기대되는 당첨 확률은 직관적으로 15/N임을 알 수 있다. 즉, 1-a^(M/N) = 15/N이다. - * a에 대해 정리하면, a = (1-15/N)^(N/M)임을 알 수 있다. + * 매번 a값을 정확하게 계산하는 것은 현실적으로 어렵다. + * 따라서, 모든 유저가 같은 수의 티켓을 가지고 있다고 가정하고 a를 계산한 뒤, 이를 확률 계산에 사용한다. + * M을 전체 티켓 수, N을 전체 유저 수라고 하자. + * 모든 유저가 같은 수의 티켓 M/N개를 가지고 있다면, 한 유저가 상품에 당첨될 확률은 15/N임을 직관적으로 알 수 있다. + * 실제 당첨 확률은 1-a^x꼴의 지수함수를 따르므로, 1-a^(M/N) = 15/N이라는 식을 세울 수 있다. + * a에 대해 정리하면, a = (1-15/N)^(N/M)을 얻는다. */ - if (base !== null) return 1 - Math.pow(base, weight); - else return (weight / weightSum) * 15; + const base = Math.pow(1 - 15 / users.length, users.length / weightSum); + return 1 - Math.pow(base, weight); }; const getTicketLeaderboardHandler = async (req, res) => { @@ -128,13 +125,6 @@ const getTicketLeaderboardHandler = async (req, res) => { }, [0, 0, 0] ); - - const isExponential = - sortedUsers.find((user) => user.weight >= weightSum / 15) !== undefined; - const base = isExponential - ? Math.pow(1 - 15 / users.length, users.length / weightSum) - : null; - const leaderboard = await Promise.all( sortedUsers.slice(0, 20).map(async (user) => { const userInfo = await userModel.findOne({ _id: user.userId }).lean(); @@ -148,12 +138,7 @@ const getTicketLeaderboardHandler = async (req, res) => { ticket1Amount: user.ticket1Amount, ticket2Amount: user.ticket2Amount, probability: user.weight / weightSum, - probabilityV2: calculateProbabilityV2( - users, - weightSum, - base, - user.weight - ), + probabilityV2: calculateProbabilityV2(users, weightSum, user.weight), }; }) ); @@ -174,7 +159,6 @@ const getTicketLeaderboardHandler = async (req, res) => { probabilityV2: calculateProbabilityV2( users, weightSum, - base, sortedUsers[rank].weight ), } From 8f4fbbb37c7c996945915314cc770ba83a3a2d54 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Wed, 27 Sep 2023 18:09:57 +0900 Subject: [PATCH 239/261] Fix: logininfo API --- src/modules/stores/mongo.js | 2 +- src/routes/docs/logininfo.js | 4 ++++ src/services/logininfo.js | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 119e0c0d..7cd59b87 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -12,7 +12,7 @@ const userSchema = Schema({ ongoingRoom: [{ type: Schema.Types.ObjectId, ref: "Room" }], // 참여중인 진행중인 방 배열 doneRoom: [{ type: Schema.Types.ObjectId, ref: "Room" }], // 참여중인 완료된 방 배열 withdraw: { type: Boolean, default: false }, - phoneNumber: { type: String }, // 전화번호 (2023FALL 이벤트부터 추가) + phoneNumber: { type: String, default: "" }, // 전화번호 (2023FALL 이벤트부터 추가) ban: { type: Boolean, default: false }, //계정 정지 여부 joinat: { type: Date, required: true }, //가입 시각 agreeOnTermsOfService: { type: Boolean, default: false }, //이용약관 동의 여부 diff --git a/src/routes/docs/logininfo.js b/src/routes/docs/logininfo.js index b24c5fe6..f572f942 100644 --- a/src/routes/docs/logininfo.js +++ b/src/routes/docs/logininfo.js @@ -30,6 +30,10 @@ const logininfoDocs = { withdraw: { type: "boolean", }, + phoneNumber: { + type: "string", + description: "사용자 전화번호", + }, ban: { type: "boolean", }, diff --git a/src/services/logininfo.js b/src/services/logininfo.js index 32a6b96d..eebb09ff 100644 --- a/src/services/logininfo.js +++ b/src/services/logininfo.js @@ -9,7 +9,7 @@ const logininfoHandler = async (req, res) => { const userDetail = await userModel.findOne( { id: user.id }, - "_id name nickname id withdraw ban joinat agreeOnTermsOfService subinfo email profileImageUrl account" + "_id name nickname id withdraw phoneNumber ban joinat agreeOnTermsOfService subinfo email profileImageUrl account" ); res.json({ @@ -18,6 +18,7 @@ const logininfoHandler = async (req, res) => { name: userDetail.name, nickname: userDetail.nickname, withdraw: userDetail.withdraw, + phoneNumber: userDetail.phoneNumber, ban: userDetail.ban, joinat: userDetail.joinat, agreeOnTermsOfService: userDetail.agreeOnTermsOfService, From 18f65b9819d40647e95a3ca5b734aab7d6be4660 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Wed, 27 Sep 2023 20:13:39 +0900 Subject: [PATCH 240/261] Fix: schema of phoneNumber --- src/modules/stores/mongo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 7cd59b87..119e0c0d 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -12,7 +12,7 @@ const userSchema = Schema({ ongoingRoom: [{ type: Schema.Types.ObjectId, ref: "Room" }], // 참여중인 진행중인 방 배열 doneRoom: [{ type: Schema.Types.ObjectId, ref: "Room" }], // 참여중인 완료된 방 배열 withdraw: { type: Boolean, default: false }, - phoneNumber: { type: String, default: "" }, // 전화번호 (2023FALL 이벤트부터 추가) + phoneNumber: { type: String }, // 전화번호 (2023FALL 이벤트부터 추가) ban: { type: Boolean, default: false }, //계정 정지 여부 joinat: { type: Date, required: true }, //가입 시각 agreeOnTermsOfService: { type: Boolean, default: false }, //이용약관 동의 여부 From 77123ed6fcf41169036f63e9d3ac85bde6d8b0dc Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 7 Oct 2023 18:57:58 +0900 Subject: [PATCH 241/261] Fix: skip validation for trust proxy setting in rate-limit middleware --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- src/middlewares/limitRate.js | 4 ++++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index cdb57f4f..20ec12eb 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "eslint-config-prettier": "^8.3.0", "express": "^4.17.1", "express-formidable": "^1.2.0", - "express-rate-limit": "^6.6.0", + "express-rate-limit": "^7.1.0", "express-session": "^1.17.3", "express-validator": "^6.14.0", "firebase-admin": "^11.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2249f8ac..8e1b598e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,8 +60,8 @@ dependencies: specifier: ^1.2.0 version: 1.2.0 express-rate-limit: - specifier: ^6.6.0 - version: 6.8.1(express@4.18.2) + specifier: ^7.1.0 + version: 7.1.0(express@4.18.2) express-session: specifier: ^1.17.3 version: 1.17.3 @@ -5088,9 +5088,9 @@ packages: formidable: 1.2.6 dev: false - /express-rate-limit@6.8.1(express@4.18.2): - resolution: {integrity: sha512-xJyudsE60CsDShK74Ni1MxsldYaIoivmG3ieK2tAckMsYCBewEuGalss6p/jHmFFnqM9xd5ojE0W2VlanxcOKg==} - engines: {node: '>= 14.0.0'} + /express-rate-limit@7.1.0(express@4.18.2): + resolution: {integrity: sha512-pwKOMedrpJJeINON/9jhAa18udV2qwxPZSoklPZK8pmXxUyE5uXaptiwjGw8bZILbxqfUZ/p8pQA99ODjSgA5Q==} + engines: {node: '>= 16'} peerDependencies: express: ^4 || ^5 dependencies: diff --git a/src/middlewares/limitRate.js b/src/middlewares/limitRate.js index 4cba6af3..c5069c8f 100644 --- a/src/middlewares/limitRate.js +++ b/src/middlewares/limitRate.js @@ -5,6 +5,10 @@ const limiter = rateLimit({ max: 1500, // Limit each IP to 1500 requests per `window` (here, per 15 minutes) standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers + validate: { + default: true, + trustProxy: false, // Disable the validation error caused by 'trust proxy' set to true + }, }); module.exports = limiter; From 73e3c0005b15169bf2895a080e6bbe287ca61ffe Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 7 Oct 2023 19:00:12 +0900 Subject: [PATCH 242/261] Refactor: force node.js >= 18 --- .npmrc | 2 ++ .nvmrc | 1 + package.json | 32 ++++++++++++++++++-------------- pnpm-lock.yaml | 4 ++-- 4 files changed, 23 insertions(+), 16 deletions(-) create mode 100644 .npmrc create mode 100644 .nvmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..be7e0b34 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +# Force Node.js and pnpm versions according to package.json +engine-strict=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..7950a445 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v18.17.0 diff --git a/package.json b/package.json index 20ec12eb..1882cbbf 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,22 @@ "name": "taxi-back", "version": "1.0.0", "description": "KAIST Taxi Party Matching Web Service", + "author": "sparcs/taxi", + "license": "MIT", "main": "app.js", + "scripts": { + "preinstall": "npx only-allow pnpm", + "start": "cross-env TZ='Asia/Seoul' npx nodemon app.js", + "test": "npm run sample && cross-env TZ='Asia/Seoul' npm run mocha", + "mocha": "cross-env TZ='Asia/Seoul' NODE_ENV=test mocha --recursive --reporter spec --exit", + "serve": "cross-env TZ='Asia/Seoul' NODE_ENV=production node app.js", + "lint": "npx eslint --fix .", + "sample": "cd sampleGenerator && npm start && cd .." + }, + "engines": { + "node": ">=18.0.0", + "pnpm": ">=8.0.0" + }, "dependencies": { "@adminjs/express": "^5.1.0", "@adminjs/mongoose": "^3.0.3", @@ -40,22 +55,11 @@ "winston-daily-rotate-file": "^4.7.1" }, "devDependencies": { - "chai": "*", + "chai": "^4.3.7", "eslint": "^8.22.0", "eslint-plugin-mocha": "^10.1.0", - "mocha": "*", + "mocha": "^10.2.0", "nodemon": "^2.0.14", "supertest": "^6.2.4" - }, - "scripts": { - "preinstall": "npx only-allow pnpm", - "start": "cross-env TZ='Asia/Seoul' npx nodemon app.js", - "test": "npm run sample && cross-env TZ='Asia/Seoul' npm run mocha", - "mocha": "cross-env TZ='Asia/Seoul' NODE_ENV=test mocha --recursive --reporter spec --exit", - "serve": "cross-env TZ='Asia/Seoul' NODE_ENV=production node app.js", - "lint": "npx eslint --fix .", - "sample": "cd sampleGenerator && npm start && cd .." - }, - "author": "sparcs/taxi", - "license": "MIT" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e1b598e..77d40850 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,7 +110,7 @@ dependencies: devDependencies: chai: - specifier: '*' + specifier: ^4.3.7 version: 4.3.7 eslint: specifier: ^8.22.0 @@ -119,7 +119,7 @@ devDependencies: specifier: ^10.1.0 version: 10.1.0(eslint@8.22.0) mocha: - specifier: '*' + specifier: ^10.2.0 version: 10.2.0 nodemon: specifier: ^2.0.14 From 02d8127d08c36cdc10cebc42e4ca102f201002de Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 7 Oct 2023 19:00:48 +0900 Subject: [PATCH 243/261] Fix: specify strictQuery to Mongoose --- src/modules/stores/mongo.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 119e0c0d..f05f266a 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -173,6 +173,8 @@ const adminLogSchema = Schema({ }, // 수행 업무 }); +mongoose.set("strictQuery", true); + const database = mongoose.connection; database.on("error", console.error.bind(console, "mongoose connection error.")); database.on("open", () => { From c5058248b39b1a0dfc6fa4999c89dcfd937d3654 Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 7 Oct 2023 19:14:49 +0900 Subject: [PATCH 244/261] Refactor: update Node.js version of Dockerfile & Actions --- .github/workflows/test_ci.yml | 7 ++++--- Dockerfile | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_ci.yml b/.github/workflows/test_ci.yml index 3c8885b2..f3aed1ef 100644 --- a/.github/workflows/test_ci.yml +++ b/.github/workflows/test_ci.yml @@ -12,7 +12,8 @@ jobs: strategy: matrix: # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ - node-version: [16.x] + node-version: ['18.x'] + pnpm-version: ['8'] mongodb-version: ['5.0'] steps: - name: Start MongoDB @@ -24,12 +25,12 @@ jobs: - name: Install Node.js uses: actions/setup-node@v3 with: - node-version: 16 + node-version: ${{ matrix.node-version }} - uses: pnpm/action-setup@v2 name: Install pnpm with: - version: 8 + version: ${{ matrix.pnpm-version }} - id: submodule-local name: Save local version of submodule run: echo "ver=`cd sampleGenerator && git log --pretty="%h" -1 && cd ..`" >> $GITHUB_OUTPUT diff --git a/Dockerfile b/Dockerfile index 78ab302f..12ee05fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16-alpine +FROM node:18-alpine # Copy repository WORKDIR /usr/src/app @@ -6,7 +6,7 @@ COPY . . # Install curl (for taxi-docker) RUN apk update && apk add curl -RUN npm install --global pnpm@8.6.6 serve@14.1.2 +RUN npm install --global pnpm@8.8.0 # Install requirements RUN pnpm i --force --frozen-lockfile From 2b558807b65c63a0600c6cd581b08e10f9f12422 Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 7 Oct 2023 19:42:03 +0900 Subject: [PATCH 245/261] Add: add pnpm cache for CI workflow --- .github/workflows/test_ci.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_ci.yml b/.github/workflows/test_ci.yml index f3aed1ef..dab9f5a0 100644 --- a/.github/workflows/test_ci.yml +++ b/.github/workflows/test_ci.yml @@ -13,7 +13,6 @@ jobs: matrix: # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ node-version: ['18.x'] - pnpm-version: ['8'] mongodb-version: ['5.0'] steps: - name: Start MongoDB @@ -22,15 +21,15 @@ jobs: uses: actions/checkout@v3 with: submodules: true + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 - name: Install Node.js uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - - - uses: pnpm/action-setup@v2 - name: Install pnpm - with: - version: ${{ matrix.pnpm-version }} + cache: 'pnpm' - id: submodule-local name: Save local version of submodule run: echo "ver=`cd sampleGenerator && git log --pretty="%h" -1 && cd ..`" >> $GITHUB_OUTPUT From bd26b54c8b3e870b1bb0abaa15553133ef602a21 Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 7 Oct 2023 20:07:35 +0900 Subject: [PATCH 246/261] Refactor: use pnpm fetch for better caching --- Dockerfile | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 12ee05fd..0873829c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,21 @@ FROM node:18-alpine -# Copy repository WORKDIR /usr/src/app -COPY . . -# Install curl (for taxi-docker) -RUN apk update && apk add curl -RUN npm install --global pnpm@8.8.0 +# Install curl(for taxi-watchtower) and pnpm +RUN apk update && apk add curl && npm install --global pnpm@8.8.0 -# Install requirements -RUN pnpm i --force --frozen-lockfile +# pnpm fetch does require only lockfile +COPY pnpm-lock.yaml . + +# Note: devDependencies are not fetched +RUN pnpm fetch --prod + +# Copy repository and install dependencies +ADD . ./ +RUN pnpm install --offline --prod # Run container EXPOSE 80 ENV PORT 80 CMD ["pnpm", "run", "serve"] - From 68165897be34d9584bb27d2520457dc5e176ff82 Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 7 Oct 2023 20:32:32 +0900 Subject: [PATCH 247/261] Refactor: cache github actions to build image --- .github/workflows/push_image_ecr.yml | 37 +++++++++++++++++++++--- .github/workflows/push_image_ecr_dev.yml | 36 +++++++++++++++++++---- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/.github/workflows/push_image_ecr.yml b/.github/workflows/push_image_ecr.yml index 17e172f7..232535a1 100644 --- a/.github/workflows/push_image_ecr.yml +++ b/.github/workflows/push_image_ecr.yml @@ -22,6 +22,17 @@ jobs: with: fetch-depth: 0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Cache Docker layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + - name: Get previous tag-version id: previous_tag uses: WyriHaximus/github-action-get-previous-tag@v1 @@ -47,15 +58,33 @@ jobs: id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - - name: Build and Push to AWS ECR + - name: Build Image id: build_image + uses: docker/build-push-action@v5 + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ steps.tag.outputs.tag }} + ECR_REPOSITORY: taxi-back + with: + tags: | + "${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}" + "${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:latest" + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new + + - name: Push to AWS ECR + id: push_image env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ steps.tag.outputs.tag }} ECR_REPOSITORY: taxi-back run: | - docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . - docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:latest . docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest - echo "Push iamge : $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG and latest" + echo "Push image : $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG and latest" + + - name: Remove old cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache + \ No newline at end of file diff --git a/.github/workflows/push_image_ecr_dev.yml b/.github/workflows/push_image_ecr_dev.yml index ef917f5b..701bfa7d 100644 --- a/.github/workflows/push_image_ecr_dev.yml +++ b/.github/workflows/push_image_ecr_dev.yml @@ -20,7 +20,18 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Cache Docker layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: @@ -31,13 +42,28 @@ jobs: - name: Login to AWS ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - - - name: Build and Push to AWS ECR + + - name: Build Image id: build_image + uses: docker/build-push-action@v5 + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: taxi-back + with: + tags: "${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:dev" + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new + + - name: Push to AWS ECR + id: push_image env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY: taxi-back run: | - docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:dev . docker push $ECR_REGISTRY/$ECR_REPOSITORY:dev - echo "Push iamge : $ECR_REGISTRY/$ECR_REPOSITORY:dev" + echo "Push image : $ECR_REGISTRY/$ECR_REPOSITORY:dev" + + - name: Remove old cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache \ No newline at end of file From a839177632aa80093155afcf0ee0f8b345860136 Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 7 Oct 2023 20:55:41 +0900 Subject: [PATCH 248/261] Fix: bump dependencies --- package.json | 6 +- pnpm-lock.yaml | 1017 ++++++++++++++++++++++++++---------------------- 2 files changed, 559 insertions(+), 464 deletions(-) diff --git a/package.json b/package.json index 1882cbbf..10e08d1f 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "express-validator": "^6.14.0", "firebase-admin": "^11.4.1", "jsonwebtoken": "^8.5.1", - "mongoose": "^6.11.3", + "mongoose": "^6.12.0", "node-cron": "3.0.2", "node-mocks-http": "^1.12.1", "querystring": "^0.2.1", @@ -55,11 +55,11 @@ "winston-daily-rotate-file": "^4.7.1" }, "devDependencies": { - "chai": "^4.3.7", + "chai": "^4.3.10", "eslint": "^8.22.0", "eslint-plugin-mocha": "^10.1.0", "mocha": "^10.2.0", - "nodemon": "^2.0.14", + "nodemon": "^3.0.1", "supertest": "^6.2.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77d40850..44ff861f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,10 +7,10 @@ settings: dependencies: '@adminjs/express': specifier: ^5.1.0 - version: 5.1.0(adminjs@6.8.7)(express-formidable@1.2.0)(express-session@1.17.3)(express@4.18.2)(tslib@2.6.1) + version: 5.1.0(adminjs@6.8.7)(express-formidable@1.2.0)(express-session@1.17.3)(express@4.18.2)(tslib@2.6.2) '@adminjs/mongoose': specifier: ^3.0.3 - version: 3.0.3(adminjs@6.8.7)(mongoose@6.11.5) + version: 3.0.3(adminjs@6.8.7)(mongoose@6.12.0) adminjs: specifier: ^6.8.7 version: 6.8.7 @@ -34,7 +34,7 @@ dependencies: version: 2.2.0 connect-mongo: specifier: ^4.6.0 - version: 4.6.0(express-session@1.17.3)(mongodb@4.16.0) + version: 4.6.0(express-session@1.17.3)(mongodb@4.17.1) connect-redis: specifier: ^6.1.3 version: 6.1.3 @@ -75,8 +75,8 @@ dependencies: specifier: ^8.5.1 version: 8.5.1 mongoose: - specifier: ^6.11.3 - version: 6.11.5 + specifier: ^6.12.0 + version: 6.12.0 node-cron: specifier: 3.0.2 version: 3.0.2 @@ -110,8 +110,8 @@ dependencies: devDependencies: chai: - specifier: ^4.3.7 - version: 4.3.7 + specifier: ^4.3.10 + version: 4.3.10 eslint: specifier: ^8.22.0 version: 8.22.0 @@ -122,8 +122,8 @@ devDependencies: specifier: ^10.2.0 version: 10.2.0 nodemon: - specifier: ^2.0.14 - version: 2.0.22 + specifier: ^3.0.1 + version: 3.0.1 supertest: specifier: ^6.2.4 version: 6.3.3 @@ -181,7 +181,7 @@ packages: - prop-types dev: false - /@adminjs/express@5.1.0(adminjs@6.8.7)(express-formidable@1.2.0)(express-session@1.17.3)(express@4.18.2)(tslib@2.6.1): + /@adminjs/express@5.1.0(adminjs@6.8.7)(express-formidable@1.2.0)(express-session@1.17.3)(express@4.18.2)(tslib@2.6.2): resolution: {integrity: sha512-+mrtDmoAYA9R+/FTYWOLL48g005yrgcAWC2phdwqGzznIxGKSp2YERcfzdTI7Svtnlaal72/QW8Q3OhzJjVLzQ==} peerDependencies: adminjs: '>=6.0.0' @@ -195,10 +195,10 @@ packages: express-formidable: 1.2.0 express-session: 1.17.3 path-to-regexp: 6.2.1 - tslib: 2.6.1 + tslib: 2.6.2 dev: false - /@adminjs/mongoose@3.0.3(adminjs@6.8.7)(mongoose@6.11.5): + /@adminjs/mongoose@3.0.3(adminjs@6.8.7)(mongoose@6.12.0): resolution: {integrity: sha512-J/Ogz3oJ2ytOsbeqBpjgIFtiAmGk3MVVfJq2cUidXJ1phrvNHhb7AjiaKd+pcdFcT84COUHaoo6uPYvrLhZEQg==} peerDependencies: adminjs: '>=6.0.0' @@ -207,7 +207,7 @@ packages: adminjs: 6.8.7 escape-regexp: 0.0.1 lodash: 4.17.21 - mongoose: 6.11.5 + mongoose: 6.12.0 dev: false /@ampproject/remapping@2.2.1: @@ -223,7 +223,7 @@ packages: requiresBuild: true dependencies: '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.378.0 + '@aws-sdk/types': 3.425.0 tslib: 1.14.1 dev: false optional: true @@ -244,7 +244,7 @@ packages: '@aws-crypto/sha256-js': 3.0.0 '@aws-crypto/supports-web-crypto': 3.0.0 '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.378.0 + '@aws-sdk/types': 3.425.0 '@aws-sdk/util-locate-window': 3.310.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 @@ -256,7 +256,7 @@ packages: requiresBuild: true dependencies: '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.378.0 + '@aws-sdk/types': 3.425.0 tslib: 1.14.1 dev: false optional: true @@ -273,388 +273,453 @@ packages: resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} requiresBuild: true dependencies: - '@aws-sdk/types': 3.378.0 + '@aws-sdk/types': 3.425.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 dev: false optional: true - /@aws-sdk/client-cognito-identity@3.385.0: - resolution: {integrity: sha512-fRXZhxvBBeK/Jxb+sLPhyQmcduNSugSKJDz474A/wLK5UIuDOnKhDTjsa0OXMpY5DkqwdYLwDcGZtxUbEZ8DCQ==} + /@aws-sdk/client-cognito-identity@3.427.0: + resolution: {integrity: sha512-9brRaNnl6haE7R3R43A5CSNw0k1YtB3xjuArbMg/p6NDUpvRSRgOVNWu2R02Yjh/j2ZuaLOCPLuCipb+PHQPKQ==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.385.0 - '@aws-sdk/credential-provider-node': 3.385.0 - '@aws-sdk/middleware-host-header': 3.379.1 - '@aws-sdk/middleware-logger': 3.378.0 - '@aws-sdk/middleware-recursion-detection': 3.378.0 - '@aws-sdk/middleware-signing': 3.379.1 - '@aws-sdk/middleware-user-agent': 3.382.0 - '@aws-sdk/types': 3.378.0 - '@aws-sdk/util-endpoints': 3.382.0 - '@aws-sdk/util-user-agent-browser': 3.378.0 - '@aws-sdk/util-user-agent-node': 3.378.0 - '@smithy/config-resolver': 2.0.1 - '@smithy/fetch-http-handler': 2.0.1 - '@smithy/hash-node': 2.0.1 - '@smithy/invalid-dependency': 2.0.1 - '@smithy/middleware-content-length': 2.0.1 - '@smithy/middleware-endpoint': 2.0.1 - '@smithy/middleware-retry': 2.0.1 - '@smithy/middleware-serde': 2.0.1 - '@smithy/middleware-stack': 2.0.0 - '@smithy/node-config-provider': 2.0.1 - '@smithy/node-http-handler': 2.0.1 - '@smithy/protocol-http': 2.0.1 - '@smithy/smithy-client': 2.0.1 - '@smithy/types': 2.0.2 - '@smithy/url-parser': 2.0.1 + '@aws-sdk/client-sts': 3.427.0 + '@aws-sdk/credential-provider-node': 3.427.0 + '@aws-sdk/middleware-host-header': 3.425.0 + '@aws-sdk/middleware-logger': 3.425.0 + '@aws-sdk/middleware-recursion-detection': 3.425.0 + '@aws-sdk/middleware-signing': 3.425.0 + '@aws-sdk/middleware-user-agent': 3.427.0 + '@aws-sdk/region-config-resolver': 3.425.0 + '@aws-sdk/types': 3.425.0 + '@aws-sdk/util-endpoints': 3.427.0 + '@aws-sdk/util-user-agent-browser': 3.425.0 + '@aws-sdk/util-user-agent-node': 3.425.0 + '@smithy/config-resolver': 2.0.14 + '@smithy/fetch-http-handler': 2.2.2 + '@smithy/hash-node': 2.0.11 + '@smithy/invalid-dependency': 2.0.11 + '@smithy/middleware-content-length': 2.0.13 + '@smithy/middleware-endpoint': 2.0.11 + '@smithy/middleware-retry': 2.0.16 + '@smithy/middleware-serde': 2.0.11 + '@smithy/middleware-stack': 2.0.5 + '@smithy/node-config-provider': 2.1.1 + '@smithy/node-http-handler': 2.1.7 + '@smithy/protocol-http': 3.0.7 + '@smithy/smithy-client': 2.1.10 + '@smithy/types': 2.3.5 + '@smithy/url-parser': 2.0.11 '@smithy/util-base64': 2.0.0 '@smithy/util-body-length-browser': 2.0.0 - '@smithy/util-body-length-node': 2.0.0 - '@smithy/util-defaults-mode-browser': 2.0.1 - '@smithy/util-defaults-mode-node': 2.0.1 - '@smithy/util-retry': 2.0.0 + '@smithy/util-body-length-node': 2.1.0 + '@smithy/util-defaults-mode-browser': 2.0.14 + '@smithy/util-defaults-mode-node': 2.0.18 + '@smithy/util-retry': 2.0.4 '@smithy/util-utf8': 2.0.0 - tslib: 2.6.1 + tslib: 2.6.2 transitivePeerDependencies: - aws-crt dev: false optional: true - /@aws-sdk/client-sso@3.382.0: - resolution: {integrity: sha512-ge11t4hJllOF8pBNF0p1X52lLqUsLGAoey24fvk3fyvvczeLpegGYh2kdLG0iwFTDgRxaUqK+kboH5Wy9ux/pw==} + /@aws-sdk/client-sso@3.427.0: + resolution: {integrity: sha512-sFVFEmsQ1rmgYO1SgrOTxE/MTKpeE4hpOkm1WqhLQK7Ij136vXpjCxjH1JYZiHiUzO1wr9t4ex4dlB5J3VS/Xg==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/middleware-host-header': 3.379.1 - '@aws-sdk/middleware-logger': 3.378.0 - '@aws-sdk/middleware-recursion-detection': 3.378.0 - '@aws-sdk/middleware-user-agent': 3.382.0 - '@aws-sdk/types': 3.378.0 - '@aws-sdk/util-endpoints': 3.382.0 - '@aws-sdk/util-user-agent-browser': 3.378.0 - '@aws-sdk/util-user-agent-node': 3.378.0 - '@smithy/config-resolver': 2.0.1 - '@smithy/fetch-http-handler': 2.0.1 - '@smithy/hash-node': 2.0.1 - '@smithy/invalid-dependency': 2.0.1 - '@smithy/middleware-content-length': 2.0.1 - '@smithy/middleware-endpoint': 2.0.1 - '@smithy/middleware-retry': 2.0.1 - '@smithy/middleware-serde': 2.0.1 - '@smithy/middleware-stack': 2.0.0 - '@smithy/node-config-provider': 2.0.1 - '@smithy/node-http-handler': 2.0.1 - '@smithy/protocol-http': 2.0.1 - '@smithy/smithy-client': 2.0.1 - '@smithy/types': 2.0.2 - '@smithy/url-parser': 2.0.1 + '@aws-sdk/middleware-host-header': 3.425.0 + '@aws-sdk/middleware-logger': 3.425.0 + '@aws-sdk/middleware-recursion-detection': 3.425.0 + '@aws-sdk/middleware-user-agent': 3.427.0 + '@aws-sdk/region-config-resolver': 3.425.0 + '@aws-sdk/types': 3.425.0 + '@aws-sdk/util-endpoints': 3.427.0 + '@aws-sdk/util-user-agent-browser': 3.425.0 + '@aws-sdk/util-user-agent-node': 3.425.0 + '@smithy/config-resolver': 2.0.14 + '@smithy/fetch-http-handler': 2.2.2 + '@smithy/hash-node': 2.0.11 + '@smithy/invalid-dependency': 2.0.11 + '@smithy/middleware-content-length': 2.0.13 + '@smithy/middleware-endpoint': 2.0.11 + '@smithy/middleware-retry': 2.0.16 + '@smithy/middleware-serde': 2.0.11 + '@smithy/middleware-stack': 2.0.5 + '@smithy/node-config-provider': 2.1.1 + '@smithy/node-http-handler': 2.1.7 + '@smithy/protocol-http': 3.0.7 + '@smithy/smithy-client': 2.1.10 + '@smithy/types': 2.3.5 + '@smithy/url-parser': 2.0.11 '@smithy/util-base64': 2.0.0 '@smithy/util-body-length-browser': 2.0.0 - '@smithy/util-body-length-node': 2.0.0 - '@smithy/util-defaults-mode-browser': 2.0.1 - '@smithy/util-defaults-mode-node': 2.0.1 - '@smithy/util-retry': 2.0.0 + '@smithy/util-body-length-node': 2.1.0 + '@smithy/util-defaults-mode-browser': 2.0.14 + '@smithy/util-defaults-mode-node': 2.0.18 + '@smithy/util-retry': 2.0.4 '@smithy/util-utf8': 2.0.0 - tslib: 2.6.1 + tslib: 2.6.2 transitivePeerDependencies: - aws-crt dev: false optional: true - /@aws-sdk/client-sts@3.385.0: - resolution: {integrity: sha512-VdSDwICW2cBttbdj1izu6VYflJbZZKu3/FSaJGuGu8SgTvRsa56g6E5xfbUfR/SCstuETObKLusSfQZ6yxUnzA==} + /@aws-sdk/client-sts@3.427.0: + resolution: {integrity: sha512-le2wLJKILyWuRfPz2HbyaNtu5kEki+ojUkTqCU6FPDRrqUvEkaaCBH9Awo/2AtrCfRkiobop8RuTTj6cAnpiJg==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/credential-provider-node': 3.385.0 - '@aws-sdk/middleware-host-header': 3.379.1 - '@aws-sdk/middleware-logger': 3.378.0 - '@aws-sdk/middleware-recursion-detection': 3.378.0 - '@aws-sdk/middleware-sdk-sts': 3.379.1 - '@aws-sdk/middleware-signing': 3.379.1 - '@aws-sdk/middleware-user-agent': 3.382.0 - '@aws-sdk/types': 3.378.0 - '@aws-sdk/util-endpoints': 3.382.0 - '@aws-sdk/util-user-agent-browser': 3.378.0 - '@aws-sdk/util-user-agent-node': 3.378.0 - '@smithy/config-resolver': 2.0.1 - '@smithy/fetch-http-handler': 2.0.1 - '@smithy/hash-node': 2.0.1 - '@smithy/invalid-dependency': 2.0.1 - '@smithy/middleware-content-length': 2.0.1 - '@smithy/middleware-endpoint': 2.0.1 - '@smithy/middleware-retry': 2.0.1 - '@smithy/middleware-serde': 2.0.1 - '@smithy/middleware-stack': 2.0.0 - '@smithy/node-config-provider': 2.0.1 - '@smithy/node-http-handler': 2.0.1 - '@smithy/protocol-http': 2.0.1 - '@smithy/smithy-client': 2.0.1 - '@smithy/types': 2.0.2 - '@smithy/url-parser': 2.0.1 + '@aws-sdk/credential-provider-node': 3.427.0 + '@aws-sdk/middleware-host-header': 3.425.0 + '@aws-sdk/middleware-logger': 3.425.0 + '@aws-sdk/middleware-recursion-detection': 3.425.0 + '@aws-sdk/middleware-sdk-sts': 3.425.0 + '@aws-sdk/middleware-signing': 3.425.0 + '@aws-sdk/middleware-user-agent': 3.427.0 + '@aws-sdk/region-config-resolver': 3.425.0 + '@aws-sdk/types': 3.425.0 + '@aws-sdk/util-endpoints': 3.427.0 + '@aws-sdk/util-user-agent-browser': 3.425.0 + '@aws-sdk/util-user-agent-node': 3.425.0 + '@smithy/config-resolver': 2.0.14 + '@smithy/fetch-http-handler': 2.2.2 + '@smithy/hash-node': 2.0.11 + '@smithy/invalid-dependency': 2.0.11 + '@smithy/middleware-content-length': 2.0.13 + '@smithy/middleware-endpoint': 2.0.11 + '@smithy/middleware-retry': 2.0.16 + '@smithy/middleware-serde': 2.0.11 + '@smithy/middleware-stack': 2.0.5 + '@smithy/node-config-provider': 2.1.1 + '@smithy/node-http-handler': 2.1.7 + '@smithy/protocol-http': 3.0.7 + '@smithy/smithy-client': 2.1.10 + '@smithy/types': 2.3.5 + '@smithy/url-parser': 2.0.11 '@smithy/util-base64': 2.0.0 '@smithy/util-body-length-browser': 2.0.0 - '@smithy/util-body-length-node': 2.0.0 - '@smithy/util-defaults-mode-browser': 2.0.1 - '@smithy/util-defaults-mode-node': 2.0.1 - '@smithy/util-retry': 2.0.0 + '@smithy/util-body-length-node': 2.1.0 + '@smithy/util-defaults-mode-browser': 2.0.14 + '@smithy/util-defaults-mode-node': 2.0.18 + '@smithy/util-retry': 2.0.4 '@smithy/util-utf8': 2.0.0 fast-xml-parser: 4.2.5 - tslib: 2.6.1 + tslib: 2.6.2 transitivePeerDependencies: - aws-crt dev: false optional: true - /@aws-sdk/credential-provider-cognito-identity@3.385.0: - resolution: {integrity: sha512-NeWJgI2XdfO0ZM25KsfNx9CDmLByY3ymVc0ae4Os+bd8pJsFeo1rX3NSkyw8XGryEbOlVJ3Jz5W5huhjo4LvqQ==} + /@aws-sdk/credential-provider-cognito-identity@3.427.0: + resolution: {integrity: sha512-BQNzNrMJlBAfXhYNdAUqaVASpT9Aho5swj7glZKxx4Uds1w5Pih2e14JWgnl8XgUWAZ36pchTrV1aA4JT7N8vw==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/client-cognito-identity': 3.385.0 - '@aws-sdk/types': 3.378.0 - '@smithy/property-provider': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-sdk/client-cognito-identity': 3.427.0 + '@aws-sdk/types': 3.425.0 + '@smithy/property-provider': 2.0.12 + '@smithy/types': 2.3.5 + tslib: 2.6.2 transitivePeerDependencies: - aws-crt dev: false optional: true - /@aws-sdk/credential-provider-env@3.378.0: - resolution: {integrity: sha512-B2OVdO9kBClDwGgWTBLAQwFV8qYTYGyVujg++1FZFSFMt8ORFdZ5fNpErvJtiSjYiOOQMzyBeSNhKyYNXCiJjQ==} + /@aws-sdk/credential-provider-env@3.425.0: + resolution: {integrity: sha512-J20etnLvMKXRVi5FK4F8yOCNm2RTaQn5psQTGdDEPWJNGxohcSpzzls8U2KcMyUJ+vItlrThr4qwgpHG3i/N0w==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.378.0 - '@smithy/property-provider': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-sdk/types': 3.425.0 + '@smithy/property-provider': 2.0.12 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@aws-sdk/credential-provider-ini@3.385.0: - resolution: {integrity: sha512-WBIR5GdfUzCGzynQYX/TuCXw3KJCkHBk6bVAsO1YmfR68XKVAxWmJPKovlK/rR6LIuV+iwUMNludO+SkmG0efg==} + /@aws-sdk/credential-provider-http@3.425.0: + resolution: {integrity: sha512-aP9nkoVWf+OlNMecrUqe4+RuQrX13nucVbty0HTvuwfwJJj0T6ByWZzle+fo1D+5OxvJmtzTflBWt6jUERdHWA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/credential-provider-env': 3.378.0 - '@aws-sdk/credential-provider-process': 3.378.0 - '@aws-sdk/credential-provider-sso': 3.385.0 - '@aws-sdk/credential-provider-web-identity': 3.378.0 - '@aws-sdk/types': 3.378.0 - '@smithy/credential-provider-imds': 2.0.1 - '@smithy/property-provider': 2.0.1 - '@smithy/shared-ini-file-loader': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-sdk/types': 3.425.0 + '@smithy/fetch-http-handler': 2.2.2 + '@smithy/node-http-handler': 2.1.7 + '@smithy/property-provider': 2.0.12 + '@smithy/protocol-http': 3.0.7 + '@smithy/types': 2.3.5 + tslib: 2.6.2 + dev: false + optional: true + + /@aws-sdk/credential-provider-ini@3.427.0: + resolution: {integrity: sha512-NmH1cO/w98CKMltYec3IrJIIco19wRjATFNiw83c+FGXZ+InJwReqBnruxIOmKTx2KDzd6fwU1HOewS7UjaaaQ==} + engines: {node: '>=14.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/credential-provider-env': 3.425.0 + '@aws-sdk/credential-provider-process': 3.425.0 + '@aws-sdk/credential-provider-sso': 3.427.0 + '@aws-sdk/credential-provider-web-identity': 3.425.0 + '@aws-sdk/types': 3.425.0 + '@smithy/credential-provider-imds': 2.0.16 + '@smithy/property-provider': 2.0.12 + '@smithy/shared-ini-file-loader': 2.2.0 + '@smithy/types': 2.3.5 + tslib: 2.6.2 transitivePeerDependencies: - aws-crt dev: false optional: true - /@aws-sdk/credential-provider-node@3.385.0: - resolution: {integrity: sha512-Lk8uu6jm/8OkbLX4Qnss8o5bnt0yQa0Tb7Azbh5/5otju5kStVAD2E+zMGrMP++NriGyZV87crduh0J8l4JUTA==} + /@aws-sdk/credential-provider-node@3.427.0: + resolution: {integrity: sha512-wYYbQ57nKL8OfgRbl8k6uXcdnYml+p3LSSfDUAuUEp1HKlQ8lOXFJ3BdLr5qrk7LhpyppSRnWBmh2c3kWa7ANQ==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/credential-provider-env': 3.378.0 - '@aws-sdk/credential-provider-ini': 3.385.0 - '@aws-sdk/credential-provider-process': 3.378.0 - '@aws-sdk/credential-provider-sso': 3.385.0 - '@aws-sdk/credential-provider-web-identity': 3.378.0 - '@aws-sdk/types': 3.378.0 - '@smithy/credential-provider-imds': 2.0.1 - '@smithy/property-provider': 2.0.1 - '@smithy/shared-ini-file-loader': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-sdk/credential-provider-env': 3.425.0 + '@aws-sdk/credential-provider-ini': 3.427.0 + '@aws-sdk/credential-provider-process': 3.425.0 + '@aws-sdk/credential-provider-sso': 3.427.0 + '@aws-sdk/credential-provider-web-identity': 3.425.0 + '@aws-sdk/types': 3.425.0 + '@smithy/credential-provider-imds': 2.0.16 + '@smithy/property-provider': 2.0.12 + '@smithy/shared-ini-file-loader': 2.2.0 + '@smithy/types': 2.3.5 + tslib: 2.6.2 transitivePeerDependencies: - aws-crt dev: false optional: true - /@aws-sdk/credential-provider-process@3.378.0: - resolution: {integrity: sha512-KFTIy7u+wXj3eDua4rgS0tODzMnXtXhAm1RxzCW9FL5JLBBrd82ymCj1Dp72217Sw5Do6NjCnDTTNkCHZMA77w==} + /@aws-sdk/credential-provider-process@3.425.0: + resolution: {integrity: sha512-YY6tkLdvtb1Fgofp3b1UWO+5vwS14LJ/smGmuGpSba0V7gFJRdcrJ9bcb9vVgAGuMdjzRJ+bUKlLLtqXkaykEw==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.378.0 - '@smithy/property-provider': 2.0.1 - '@smithy/shared-ini-file-loader': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-sdk/types': 3.425.0 + '@smithy/property-provider': 2.0.12 + '@smithy/shared-ini-file-loader': 2.2.0 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@aws-sdk/credential-provider-sso@3.385.0: - resolution: {integrity: sha512-ETFnS+4ZKTAgT8boVpIpRuXA9wWGpNqOcI1RXtjsaIgQ9s8uNn2JPa8l71gZh861mzBC8Hadp1EpNu+43w4lkg==} + /@aws-sdk/credential-provider-sso@3.427.0: + resolution: {integrity: sha512-c+tXyS/i49erHs4bAp6vKNYeYlyQ0VNMBgoco0LCn1rL0REtHbfhWMnqDLF6c2n3yIWDOTrQu0D73Idnpy16eA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/client-sso': 3.382.0 - '@aws-sdk/token-providers': 3.385.0 - '@aws-sdk/types': 3.378.0 - '@smithy/property-provider': 2.0.1 - '@smithy/shared-ini-file-loader': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-sdk/client-sso': 3.427.0 + '@aws-sdk/token-providers': 3.427.0 + '@aws-sdk/types': 3.425.0 + '@smithy/property-provider': 2.0.12 + '@smithy/shared-ini-file-loader': 2.2.0 + '@smithy/types': 2.3.5 + tslib: 2.6.2 transitivePeerDependencies: - aws-crt dev: false optional: true - /@aws-sdk/credential-provider-web-identity@3.378.0: - resolution: {integrity: sha512-GWjydOszhc4xDF8xuPtBvboglXQr0gwCW1oHAvmLcOT38+Hd6qnKywnMSeoXYRPgoKfF9TkWQgW1jxplzCG0UA==} + /@aws-sdk/credential-provider-web-identity@3.425.0: + resolution: {integrity: sha512-/0R65TgRzL01JU3SzloivWNwdkbIhr06uY/F5pBHf/DynQqaspKNfdHn6AiozgSVDfwRHFjKBTUy6wvf3QFkuA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.378.0 - '@smithy/property-provider': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-sdk/types': 3.425.0 + '@smithy/property-provider': 2.0.12 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@aws-sdk/credential-providers@3.385.0: - resolution: {integrity: sha512-II4WAFMk061Ud6n1Pux+5T3FQe6gLIwmpF+QgMH97TxJZWFiKyhmJ1Z0VArjo1wwcEPMyIN21Ij91ayop8agwQ==} + /@aws-sdk/credential-providers@3.427.0: + resolution: {integrity: sha512-rKKohSHju462vo+uQnPjcEZPBAfAMgGH6K1XyyCNpuOC0yYLkG87PYpvAQeb8riTrkHPX0dYUHuTHZ6zQgMGjA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/client-cognito-identity': 3.385.0 - '@aws-sdk/client-sso': 3.382.0 - '@aws-sdk/client-sts': 3.385.0 - '@aws-sdk/credential-provider-cognito-identity': 3.385.0 - '@aws-sdk/credential-provider-env': 3.378.0 - '@aws-sdk/credential-provider-ini': 3.385.0 - '@aws-sdk/credential-provider-node': 3.385.0 - '@aws-sdk/credential-provider-process': 3.378.0 - '@aws-sdk/credential-provider-sso': 3.385.0 - '@aws-sdk/credential-provider-web-identity': 3.378.0 - '@aws-sdk/types': 3.378.0 - '@smithy/credential-provider-imds': 2.0.1 - '@smithy/property-provider': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-sdk/client-cognito-identity': 3.427.0 + '@aws-sdk/client-sso': 3.427.0 + '@aws-sdk/client-sts': 3.427.0 + '@aws-sdk/credential-provider-cognito-identity': 3.427.0 + '@aws-sdk/credential-provider-env': 3.425.0 + '@aws-sdk/credential-provider-http': 3.425.0 + '@aws-sdk/credential-provider-ini': 3.427.0 + '@aws-sdk/credential-provider-node': 3.427.0 + '@aws-sdk/credential-provider-process': 3.425.0 + '@aws-sdk/credential-provider-sso': 3.427.0 + '@aws-sdk/credential-provider-web-identity': 3.425.0 + '@aws-sdk/types': 3.425.0 + '@smithy/credential-provider-imds': 2.0.16 + '@smithy/property-provider': 2.0.12 + '@smithy/types': 2.3.5 + tslib: 2.6.2 transitivePeerDependencies: - aws-crt dev: false optional: true - /@aws-sdk/middleware-host-header@3.379.1: - resolution: {integrity: sha512-LI4KpAFWNWVr2aH2vRVblr0Y8tvDz23lj8LOmbDmCrzd5M21nxuocI/8nEAQj55LiTIf9Zs+dHCdsyegnFXdrA==} + /@aws-sdk/middleware-host-header@3.425.0: + resolution: {integrity: sha512-E5Gt41LObQ+cr8QnLthwsH3MtVSNXy1AKJMowDr85h0vzqA/FHUkgHyOGntgozzjXT5M0MaSRYxS0xwTR5D4Ew==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.378.0 - '@smithy/protocol-http': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-sdk/types': 3.425.0 + '@smithy/protocol-http': 3.0.7 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@aws-sdk/middleware-logger@3.378.0: - resolution: {integrity: sha512-l1DyaDLm3KeBMNMuANI3scWh8Xvu248x+vw6Z7ExWOhGXFmQ1MW7YvASg/SdxWkhlF9HmkkTif1LdMB22x6QDA==} + /@aws-sdk/middleware-logger@3.425.0: + resolution: {integrity: sha512-INE9XWRXx2f4a/r2vOU0tAmgctVp7nEaEasemNtVBYhqbKLZvr9ndLBSgKGgJ8LIcXAoISipaMuFiqIGkFsm7A==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.378.0 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-sdk/types': 3.425.0 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@aws-sdk/middleware-recursion-detection@3.378.0: - resolution: {integrity: sha512-mUMfHAz0oGNIWiTZHTVJb+I515Hqs2zx1j36Le4MMiiaMkPW1SRUF1FIwGuc1wh6E8jB5q+XfEMriDjRi4TZRA==} + /@aws-sdk/middleware-recursion-detection@3.425.0: + resolution: {integrity: sha512-77gnzJ5b91bgD75L/ugpOyerx6lR3oyS4080X1YI58EzdyBMkDrHM4FbMcY2RynETi3lwXCFzLRyZjWXY1mRlw==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.378.0 - '@smithy/protocol-http': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-sdk/types': 3.425.0 + '@smithy/protocol-http': 3.0.7 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@aws-sdk/middleware-sdk-sts@3.379.1: - resolution: {integrity: sha512-SK3gSyT0XbLiY12+AjLFYL9YngxOXHnZF3Z33Cdd4a+AUYrVBV7JBEEGD1Nlwrcmko+3XgaKlmgUaR5s91MYvg==} + /@aws-sdk/middleware-sdk-sts@3.425.0: + resolution: {integrity: sha512-JFojrg76oKAoBknnr9EL5N2aJ1mRCtBqXoZYST58GSx8uYdFQ89qS65VNQ8JviBXzsrCNAn4vDhZ5Ch5E6TxGQ==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/middleware-signing': 3.379.1 - '@aws-sdk/types': 3.378.0 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-sdk/middleware-signing': 3.425.0 + '@aws-sdk/types': 3.425.0 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@aws-sdk/middleware-signing@3.379.1: - resolution: {integrity: sha512-kBk2ZUvR84EM4fICjr8K+Ykpf8SI1UzzPp2/UVYZ0X+4H/ZCjfSqohGRwHykMqeplne9qHSL7/rGJs1H3l3gPg==} + /@aws-sdk/middleware-signing@3.425.0: + resolution: {integrity: sha512-ZpOfgJHk7ovQ0sSwg3tU4NxFOnz53lJlkJRf7S+wxQALHM0P2MJ6LYBrZaFMVsKiJxNIdZBXD6jclgHg72ZW6Q==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.378.0 - '@smithy/property-provider': 2.0.1 - '@smithy/protocol-http': 2.0.1 - '@smithy/signature-v4': 2.0.1 - '@smithy/types': 2.0.2 - '@smithy/util-middleware': 2.0.0 - tslib: 2.6.1 + '@aws-sdk/types': 3.425.0 + '@smithy/property-provider': 2.0.12 + '@smithy/protocol-http': 3.0.7 + '@smithy/signature-v4': 2.0.11 + '@smithy/types': 2.3.5 + '@smithy/util-middleware': 2.0.4 + tslib: 2.6.2 dev: false optional: true - /@aws-sdk/middleware-user-agent@3.382.0: - resolution: {integrity: sha512-LFRW1jmXOrOAd3911ktn6oaYmuurNnulbdRMOUdwz99GGdLVFipQhOi9idKswb8IOhPa4jEVQt25Kcv7ctvu0A==} + /@aws-sdk/middleware-user-agent@3.427.0: + resolution: {integrity: sha512-y9HxYsNvnA3KqDl8w1jHeCwz4P9CuBEtu/G+KYffLeAMBsMZmh4SIkFFCO9wE/dyYg6+yo07rYcnnIfy7WA0bw==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.378.0 - '@aws-sdk/util-endpoints': 3.382.0 - '@smithy/protocol-http': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-sdk/types': 3.425.0 + '@aws-sdk/util-endpoints': 3.427.0 + '@smithy/protocol-http': 3.0.7 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@aws-sdk/token-providers@3.385.0: - resolution: {integrity: sha512-2A2Y7/bU5EaxQwLwLy7ojs+Wy5VOBkIlGPH7ZcpPaoQ1Hscwn3Wvx/DZmOvbyYfZ1CbIFutoHJlVxh6KZldUDw==} + /@aws-sdk/region-config-resolver@3.425.0: + resolution: {integrity: sha512-u7uv/iUOapIJdRgRkO3wnpYsUgV6ponsZJQgVg/8L+n+Vo5PQL5gAcIuAOwcYSKQPFaeK+KbmByI4SyOK203Vw==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.378.0 - '@smithy/property-provider': 2.0.1 - '@smithy/shared-ini-file-loader': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@smithy/node-config-provider': 2.1.1 + '@smithy/types': 2.3.5 + '@smithy/util-config-provider': 2.0.0 + '@smithy/util-middleware': 2.0.4 + tslib: 2.6.2 dev: false optional: true - /@aws-sdk/types@3.378.0: - resolution: {integrity: sha512-qP0CvR/ItgktmN8YXpGQglzzR/6s0nrsQ4zIfx3HMwpsBTwuouYahcCtF1Vr82P4NFcoDA412EJahJ2pIqEd+w==} + /@aws-sdk/token-providers@3.427.0: + resolution: {integrity: sha512-4E5E+4p8lJ69PBY400dJXF06LUHYx5lkKzBEsYqWWhoZcoftrvi24ltIhUDoGVLkrLcTHZIWSdFAWSos4hXqeg==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/middleware-host-header': 3.425.0 + '@aws-sdk/middleware-logger': 3.425.0 + '@aws-sdk/middleware-recursion-detection': 3.425.0 + '@aws-sdk/middleware-user-agent': 3.427.0 + '@aws-sdk/types': 3.425.0 + '@aws-sdk/util-endpoints': 3.427.0 + '@aws-sdk/util-user-agent-browser': 3.425.0 + '@aws-sdk/util-user-agent-node': 3.425.0 + '@smithy/config-resolver': 2.0.14 + '@smithy/fetch-http-handler': 2.2.2 + '@smithy/hash-node': 2.0.11 + '@smithy/invalid-dependency': 2.0.11 + '@smithy/middleware-content-length': 2.0.13 + '@smithy/middleware-endpoint': 2.0.11 + '@smithy/middleware-retry': 2.0.16 + '@smithy/middleware-serde': 2.0.11 + '@smithy/middleware-stack': 2.0.5 + '@smithy/node-config-provider': 2.1.1 + '@smithy/node-http-handler': 2.1.7 + '@smithy/property-provider': 2.0.12 + '@smithy/protocol-http': 3.0.7 + '@smithy/shared-ini-file-loader': 2.2.0 + '@smithy/smithy-client': 2.1.10 + '@smithy/types': 2.3.5 + '@smithy/url-parser': 2.0.11 + '@smithy/util-base64': 2.0.0 + '@smithy/util-body-length-browser': 2.0.0 + '@smithy/util-body-length-node': 2.1.0 + '@smithy/util-defaults-mode-browser': 2.0.14 + '@smithy/util-defaults-mode-node': 2.0.18 + '@smithy/util-retry': 2.0.4 + '@smithy/util-utf8': 2.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt dev: false optional: true - /@aws-sdk/util-endpoints@3.382.0: - resolution: {integrity: sha512-flajPyjmjNG67fXk7l4GoTB/7J11VBqtFZXuuAZKhKU07Ia3IQupsFqNf5lV8D44ZgjnKH0fTGnv3dUALjW7Wg==} + /@aws-sdk/types@3.425.0: + resolution: {integrity: sha512-6lqbmorwerN4v+J5dqbHPAsjynI0mkEF+blf+69QTaKKGaxBBVaXgqoqul9RXYcK5MMrrYRbQIMd0zYOoy90kA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.378.0 - tslib: 2.6.1 + '@smithy/types': 2.3.5 + tslib: 2.6.2 + dev: false + optional: true + + /@aws-sdk/util-endpoints@3.427.0: + resolution: {integrity: sha512-rSyiAIFF/EVvity/+LWUqoTMJ0a25RAc9iqx0WZ4tf1UjuEXRRXxZEb+jEZg1bk+pY84gdLdx9z5E+MSJCZxNQ==} + engines: {node: '>=14.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/types': 3.425.0 + '@smithy/node-config-provider': 2.1.1 + tslib: 2.6.2 dev: false optional: true @@ -663,23 +728,23 @@ packages: engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true - /@aws-sdk/util-user-agent-browser@3.378.0: - resolution: {integrity: sha512-FSCpagzftK1W+m7Ar6lpX7/Gr9y5P56nhFYz8U4EYQ4PkufS6czWX9YW+/FA5OYV0vlQ/SvPqMnzoHIPUNhZrQ==} + /@aws-sdk/util-user-agent-browser@3.425.0: + resolution: {integrity: sha512-22Y9iMtjGcFjGILR6/xdp1qRezlHVLyXtnpEsbuPTiernRCPk6zfAnK/ATH77r02MUjU057tdxVkd5umUBTn9Q==} requiresBuild: true dependencies: - '@aws-sdk/types': 3.378.0 - '@smithy/types': 2.0.2 + '@aws-sdk/types': 3.425.0 + '@smithy/types': 2.3.5 bowser: 2.11.0 - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true - /@aws-sdk/util-user-agent-node@3.378.0: - resolution: {integrity: sha512-IdwVJV0E96MkJeFte4dlWqvB+oiqCiZ5lOlheY3W9NynTuuX0GGYNC8Y9yIsV8Oava1+ujpJq0ww6qXdYxmO4A==} + /@aws-sdk/util-user-agent-node@3.425.0: + resolution: {integrity: sha512-SIR4F5uQeeVAi8lv4OgRirtdtNi5zeyogTuQgGi9su8F/WP1N6JqxofcwpUY5f8/oJ2UlXr/tx1f09UHfJJzvA==} engines: {node: '>=14.0.0'} requiresBuild: true peerDependencies: @@ -688,10 +753,10 @@ packages: aws-crt: optional: true dependencies: - '@aws-sdk/types': 3.378.0 - '@smithy/node-config-provider': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@aws-sdk/types': 3.425.0 + '@smithy/node-config-provider': 2.1.1 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true @@ -699,7 +764,7 @@ packages: resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} requiresBuild: true dependencies: - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true @@ -2269,15 +2334,15 @@ packages: resolution: {integrity: sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==} dev: false - /@google-cloud/firestore@6.7.0: - resolution: {integrity: sha512-bkH2jb5KkQSUa+NAvpip9HQ+rpYhi77IaqHovWuN07adVmvNXX08gPpvPWEzoXYa/wDjEVI7LiAtCWkJJEYTNg==} + /@google-cloud/firestore@6.8.0: + resolution: {integrity: sha512-JRpk06SmZXLGz0pNx1x7yU3YhkUXheKgH5hbDZ4kMsdhtfV5qPLJLRI4wv69K0cZorIk+zTMOwptue7hizo0eA==} engines: {node: '>=12.0.0'} requiresBuild: true dependencies: fast-deep-equal: 3.1.3 functional-red-black-tree: 1.0.1 google-gax: 3.6.1 - protobufjs: 7.2.4 + protobufjs: 7.2.5 transitivePeerDependencies: - encoding - supports-color @@ -2356,7 +2421,7 @@ packages: '@types/long': 4.0.2 lodash.camelcase: 4.3.0 long: 4.0.0 - protobufjs: 7.2.4 + protobufjs: 7.2.5 yargs: 17.7.2 dev: false optional: true @@ -2460,6 +2525,14 @@ packages: dev: false optional: true + /@mongodb-js/saslprep@1.1.0: + resolution: {integrity: sha512-Xfijy7HvfzzqiOAhAepF4SGN5e9leLkMvg/OPOF97XemjfVCYN/oWa75wnkc6mltMSTwY+XlbhWgUOJmkFspSw==} + requiresBuild: true + dependencies: + sparse-bitfield: 3.0.3 + dev: false + optional: true + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2706,82 +2779,83 @@ packages: rollup: 2.79.1 dev: false - /@smithy/abort-controller@2.0.1: - resolution: {integrity: sha512-0s7XjIbsTwZyUW9OwXQ8J6x1UiA1TNCh60Vaw56nHahL7kUZsLhmTlWiaxfLkFtO2Utkj8YewcpHTYpxaTzO+w==} + /@smithy/abort-controller@2.0.11: + resolution: {integrity: sha512-MSzE1qR2JNyb7ot3blIOT3O3H0Jn06iNDEgHRaqZUwBgx5EG+VIx24Y21tlKofzYryIOcWpIohLrIIyocD6LMA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@smithy/config-resolver@2.0.1: - resolution: {integrity: sha512-l83Pm7hV+8CBQOCmBRopWDtF+CURUJol7NsuPYvimiDhkC2F8Ba9T1imSFE+pD1UIJ9jlsDPAnZfPJT5cjnuEw==} + /@smithy/config-resolver@2.0.14: + resolution: {integrity: sha512-K1K+FuWQoy8j/G7lAmK85o03O89s2Vvh6kMFmzEmiHUoQCRH1rzbDtMnGNiaMHeSeYJ6y79IyTusdRG+LuWwtg==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.0.2 + '@smithy/node-config-provider': 2.1.1 + '@smithy/types': 2.3.5 '@smithy/util-config-provider': 2.0.0 - '@smithy/util-middleware': 2.0.0 - tslib: 2.6.1 + '@smithy/util-middleware': 2.0.4 + tslib: 2.6.2 dev: false optional: true - /@smithy/credential-provider-imds@2.0.1: - resolution: {integrity: sha512-8VxriuRINNEfVZjEFKBY75y9ZWAx73DZ5K/u+3LmB6r8WR2h3NaFxFKMlwlq0uzNdGhD1ouKBn9XWEGYHKiPLw==} + /@smithy/credential-provider-imds@2.0.16: + resolution: {integrity: sha512-tKa2xF+69TvGxJT+lnJpGrKxUuAZDLYXFhqnPEgnHz+psTpkpcB4QRjHj63+uj83KaeFJdTfW201eLZeRn6FfA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/node-config-provider': 2.0.1 - '@smithy/property-provider': 2.0.1 - '@smithy/types': 2.0.2 - '@smithy/url-parser': 2.0.1 - tslib: 2.6.1 + '@smithy/node-config-provider': 2.1.1 + '@smithy/property-provider': 2.0.12 + '@smithy/types': 2.3.5 + '@smithy/url-parser': 2.0.11 + tslib: 2.6.2 dev: false optional: true - /@smithy/eventstream-codec@2.0.1: - resolution: {integrity: sha512-/IiNB7gQM2y2ZC/GAWOWDa8+iXfhr1g9Xe5979cQEOdCWDISvrAiv18cn3OtIQUhbYOR3gm7QtCpkq1to2takQ==} + /@smithy/eventstream-codec@2.0.11: + resolution: {integrity: sha512-BQCTjxhCYRZIfXapa2LmZSaH8QUBGwMZw7XRN83hrdixbLjIcj+o549zjkedFS07Ve2TlvWUI6BTzP+nv7snBA==} requiresBuild: true dependencies: '@aws-crypto/crc32': 3.0.0 - '@smithy/types': 2.0.2 + '@smithy/types': 2.3.5 '@smithy/util-hex-encoding': 2.0.0 - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true - /@smithy/fetch-http-handler@2.0.1: - resolution: {integrity: sha512-/SoU/ClazgcdOxgE4zA7RX8euiELwpsrKCSvulVQvu9zpmqJRyEJn8ZTWYFV17/eHOBdHTs9kqodhNhsNT+cUw==} + /@smithy/fetch-http-handler@2.2.2: + resolution: {integrity: sha512-K7aRtRuaBjzlk+jWWeyfDTLAmRRvmA4fU8eHUXtjsuEDgi3f356ZE32VD2ssxIH13RCLVZbXMt5h7wHzYiSuVA==} requiresBuild: true dependencies: - '@smithy/protocol-http': 2.0.1 - '@smithy/querystring-builder': 2.0.1 - '@smithy/types': 2.0.2 + '@smithy/protocol-http': 3.0.7 + '@smithy/querystring-builder': 2.0.11 + '@smithy/types': 2.3.5 '@smithy/util-base64': 2.0.0 - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true - /@smithy/hash-node@2.0.1: - resolution: {integrity: sha512-oTKYimQdF4psX54ZonpcIE+MXjMUWFxLCNosjPkJPFQ9whRX0K/PFX/+JZGRQh3zO9RlEOEUIbhy9NO+Wha6hw==} + /@smithy/hash-node@2.0.11: + resolution: {integrity: sha512-PbleVugN2tbhl1ZoNWVrZ1oTFFas/Hq+s6zGO8B9bv4w/StTriTKA9W+xZJACOj9X7zwfoTLbscM+avCB1KqOQ==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.0.2 + '@smithy/types': 2.3.5 '@smithy/util-buffer-from': 2.0.0 '@smithy/util-utf8': 2.0.0 - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true - /@smithy/invalid-dependency@2.0.1: - resolution: {integrity: sha512-2q/Eb0AE662zwyMV+z+TL7deBwcHCgaZZGc0RItamBE8kak3MzCi/EZCNoFWoBfxgQ4jfR12wm8KKsSXhJzJtQ==} + /@smithy/invalid-dependency@2.0.11: + resolution: {integrity: sha512-zazq99ujxYv/NOf9zh7xXbNgzoVLsqE0wle8P/1zU/XdhPi/0zohTPKWUzIxjGdqb5hkkwfBkNkl5H+LE0mvgw==} requiresBuild: true dependencies: - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true @@ -2790,195 +2864,199 @@ packages: engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true - /@smithy/middleware-content-length@2.0.1: - resolution: {integrity: sha512-IZhRSk5GkVBcrKaqPXddBS2uKhaqwBgaSgbBb1OJyGsKe7SxRFbclWS0LqOR9fKUkDl+3lL8E2ffpo6EQg0igw==} + /@smithy/middleware-content-length@2.0.13: + resolution: {integrity: sha512-Md2kxWpaec3bXp1oERFPQPBhOXCkGSAF7uc1E+4rkwjgw3/tqAXRtbjbggu67HJdwaif76As8AV6XxbD1HzqTQ==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/protocol-http': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@smithy/protocol-http': 3.0.7 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@smithy/middleware-endpoint@2.0.1: - resolution: {integrity: sha512-uz/KI1MBd9WHrrkVFZO4L4Wyv24raf0oR4EsOYEeG5jPJO5U+C7MZGLcMxX8gWERDn1sycBDqmGv8fjUMLxT6w==} + /@smithy/middleware-endpoint@2.0.11: + resolution: {integrity: sha512-mCugsvB15up6fqpzUEpMT4CuJmFkEI+KcozA7QMzYguXCaIilyMKsyxgamwmr+o7lo3QdjN0//XLQ9bWFL129g==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/middleware-serde': 2.0.1 - '@smithy/types': 2.0.2 - '@smithy/url-parser': 2.0.1 - '@smithy/util-middleware': 2.0.0 - tslib: 2.6.1 + '@smithy/middleware-serde': 2.0.11 + '@smithy/types': 2.3.5 + '@smithy/url-parser': 2.0.11 + '@smithy/util-middleware': 2.0.4 + tslib: 2.6.2 dev: false optional: true - /@smithy/middleware-retry@2.0.1: - resolution: {integrity: sha512-NKHF4i0gjSyjO6C0ZyjEpNqzGgIu7s8HOK6oT/1Jqws2Q1GynR1xV8XTUs1gKXeaNRzbzKQRewHHmfPwZjOtHA==} + /@smithy/middleware-retry@2.0.16: + resolution: {integrity: sha512-Br5+0yoiMS0ugiOAfJxregzMMGIRCbX4PYo1kDHtLgvkA/d++aHbnHB819m5zOIAMPvPE7AThZgcsoK+WOsUTA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/protocol-http': 2.0.1 - '@smithy/service-error-classification': 2.0.0 - '@smithy/types': 2.0.2 - '@smithy/util-middleware': 2.0.0 - '@smithy/util-retry': 2.0.0 - tslib: 2.6.1 + '@smithy/node-config-provider': 2.1.1 + '@smithy/protocol-http': 3.0.7 + '@smithy/service-error-classification': 2.0.4 + '@smithy/types': 2.3.5 + '@smithy/util-middleware': 2.0.4 + '@smithy/util-retry': 2.0.4 + tslib: 2.6.2 uuid: 8.3.2 dev: false optional: true - /@smithy/middleware-serde@2.0.1: - resolution: {integrity: sha512-uKxPaC6ItH9ZXdpdqNtf8sda7GcU4SPMp0tomq/5lUg9oiMa/Q7+kD35MUrpKaX3IVXVrwEtkjCU9dogZ/RAUA==} + /@smithy/middleware-serde@2.0.11: + resolution: {integrity: sha512-NuxnjMyf4zQqhwwdh0OTj5RqpnuT6HcH5Xg5GrPijPcKzc2REXVEVK4Yyk8ckj8ez1XSj/bCmJ+oNjmqB02GWA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@smithy/middleware-stack@2.0.0: - resolution: {integrity: sha512-31XC1xNF65nlbc16yuh3wwTudmqs6qy4EseQUGF8A/p2m/5wdd/cnXJqpniy/XvXVwkHPz/GwV36HqzHtIKATQ==} + /@smithy/middleware-stack@2.0.5: + resolution: {integrity: sha512-bVQU/rZzBY7CbSxIrDTGZYnBWKtIw+PL/cRc9B7etZk1IKSOe0NvKMJyWllfhfhrTeMF6eleCzOihIQympAvPw==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - tslib: 2.6.1 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@smithy/node-config-provider@2.0.1: - resolution: {integrity: sha512-Zoel4CPkKRTQ2XxmozZUfqBYqjPKL53/SvTDhJHj+VBSiJy6MXRav1iDCyFPS92t40Uh+Yi+Km5Ch3hQ+c/zSA==} + /@smithy/node-config-provider@2.1.1: + resolution: {integrity: sha512-1lF6s1YWBi1LBu2O30tD3jyTgMtuvk/Z1twzXM4GPYe4dmZix4nNREPJIPOcfFikNU2o0eTYP80+izx5F2jIJA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/property-provider': 2.0.1 - '@smithy/shared-ini-file-loader': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@smithy/property-provider': 2.0.12 + '@smithy/shared-ini-file-loader': 2.2.0 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@smithy/node-http-handler@2.0.1: - resolution: {integrity: sha512-Zv3fxk3p9tsmPT2CKMsbuwbbxnq2gzLDIulxv+yI6aE+02WPYorObbbe9gh7SW3weadMODL1vTfOoJ9yFypDzg==} + /@smithy/node-http-handler@2.1.7: + resolution: {integrity: sha512-PQIKZXlp3awCDn/xNlCSTFE7aYG/5Tx33M05NfQmWYeB5yV1GZZOSz4dXpwiNJYTXb9jPqjl+ueXXkwtEluFFA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/abort-controller': 2.0.1 - '@smithy/protocol-http': 2.0.1 - '@smithy/querystring-builder': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@smithy/abort-controller': 2.0.11 + '@smithy/protocol-http': 3.0.7 + '@smithy/querystring-builder': 2.0.11 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@smithy/property-provider@2.0.1: - resolution: {integrity: sha512-pmJRyY9SF6sutWIktIhe+bUdSQDxv/qZ4mYr3/u+u45riTPN7nmRxPo+e4sjWVoM0caKFjRSlj3tf5teRFy0Vg==} + /@smithy/property-provider@2.0.12: + resolution: {integrity: sha512-Un/OvvuQ1Kg8WYtoMCicfsFFuHb/TKL3pCA6ZIo/WvNTJTR94RtoRnL7mY4XkkUAoFMyf6KjcQJ76y1FX7S5rw==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@smithy/protocol-http@2.0.1: - resolution: {integrity: sha512-mrkMAp0wtaDEIkgRObWYxI1Kun1tm6Iu6rK+X4utb6Ah7Uc3Kk4VIWwK/rBHdYGReiLIrxFCB1rq4a2gyZnSgg==} + /@smithy/protocol-http@3.0.7: + resolution: {integrity: sha512-HnZW8y+r66ntYueCDbLqKwWcMNWW8o3eVpSrHNluwtBJ/EUWfQHRKSiu6vZZtc6PGfPQWgVfucoCE/C3QufMAA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@smithy/querystring-builder@2.0.1: - resolution: {integrity: sha512-bp+93WFzx1FojVEIeFPtG0A1pKsFdCUcZvVdZdRlmNooOUrz9Mm9bneRd8hDwAQ37pxiZkCOxopSXXRQN10mYw==} + /@smithy/querystring-builder@2.0.11: + resolution: {integrity: sha512-b4kEbVMxpmfv2VWUITn2otckTi7GlMteZQxi+jlwedoATOGEyrCJPfRcYQJjbCi3fZ2QTfh3PcORvB27+j38Yg==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.0.2 + '@smithy/types': 2.3.5 '@smithy/util-uri-escape': 2.0.0 - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true - /@smithy/querystring-parser@2.0.1: - resolution: {integrity: sha512-h+e7k1z+IvI2sSbUBG9Aq46JsgLl4UqIUl6aigAlRBj+P6ocNXpM6Yn1vMBw5ijtXeZbYpd1YvCxwDgdw3jhmg==} + /@smithy/querystring-parser@2.0.11: + resolution: {integrity: sha512-YXe7jhi7s3dQ0Fu9dLoY/gLu6NCyy8tBWJL/v2c9i7/RLpHgKT+uT96/OqZkHizCJ4kr0ZD46tzMjql/o60KLg==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@smithy/service-error-classification@2.0.0: - resolution: {integrity: sha512-2z5Nafy1O0cTf69wKyNjGW/sNVMiqDnb4jgwfMG8ye8KnFJ5qmJpDccwIbJNhXIfbsxTg9SEec2oe1cexhMJvw==} + /@smithy/service-error-classification@2.0.4: + resolution: {integrity: sha512-77506l12I5gxTZqBkx3Wb0RqMG81bMYLaVQ+EqIWFwQDJRs5UFeXogKxSKojCmz1wLUziHZQXm03MBzPQiumQw==} engines: {node: '>=14.0.0'} requiresBuild: true + dependencies: + '@smithy/types': 2.3.5 dev: false optional: true - /@smithy/shared-ini-file-loader@2.0.1: - resolution: {integrity: sha512-a463YiZrPGvM+F336rIF8pLfQsHAdCRAn/BiI/EWzg5xLoxbC7GSxIgliDDXrOu0z8gT3nhVsif85eU6jyct3A==} + /@smithy/shared-ini-file-loader@2.2.0: + resolution: {integrity: sha512-xFXqs4vAb5BdkzHSRrTapFoaqS4/3m/CGZzdw46fBjYZ0paYuLAoMY60ICCn1FfGirG+PiJ3eWcqJNe4/SkfyA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@smithy/signature-v4@2.0.1: - resolution: {integrity: sha512-jztv5Mirca42ilxmMDjzLdXcoAmRhZskGafGL49sRo5u7swEZcToEFrq6vtX5YMbSyTVrE9Teog5EFexY5Ff2Q==} + /@smithy/signature-v4@2.0.11: + resolution: {integrity: sha512-EFVU1dT+2s8xi227l1A9O27edT/GNKvyAK6lZnIZ0zhIHq/jSLznvkk15aonGAM1kmhmZBVGpI7Tt0odueZK9A==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/eventstream-codec': 2.0.1 + '@smithy/eventstream-codec': 2.0.11 '@smithy/is-array-buffer': 2.0.0 - '@smithy/types': 2.0.2 + '@smithy/types': 2.3.5 '@smithy/util-hex-encoding': 2.0.0 - '@smithy/util-middleware': 2.0.0 + '@smithy/util-middleware': 2.0.4 '@smithy/util-uri-escape': 2.0.0 '@smithy/util-utf8': 2.0.0 - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true - /@smithy/smithy-client@2.0.1: - resolution: {integrity: sha512-LHC5m6tYpEu1iNbONfvMbwtErboyTZJfEIPoD78Ei5MVr36vZQCaCla5mvo36+q/a2NAk2//fA5Rx3I1Kf7+lQ==} + /@smithy/smithy-client@2.1.10: + resolution: {integrity: sha512-2OEmZDiW1Z196QHuQZ5M6cBE8FCSG0H2HADP1G+DY8P3agsvb0YJyfhyKuJbxIQy15tr3eDAK6FOrlbxgKOOew==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/middleware-stack': 2.0.0 - '@smithy/types': 2.0.2 - '@smithy/util-stream': 2.0.1 - tslib: 2.6.1 + '@smithy/middleware-stack': 2.0.5 + '@smithy/types': 2.3.5 + '@smithy/util-stream': 2.0.15 + tslib: 2.6.2 dev: false optional: true - /@smithy/types@2.0.2: - resolution: {integrity: sha512-wcymEjIXQ9+NEfE5Yt5TInAqe1o4n+Nh+rh00AwoazppmUt8tdo6URhc5gkDcOYrcvlDVAZE7uG69nDpEGUKxw==} + /@smithy/types@2.3.5: + resolution: {integrity: sha512-ehyDt8M9hehyxrLQGoA1BGPou8Js1Ocoh5M0ngDhJMqbFmNK5N6Xhr9/ZExWkyIW8XcGkiMPq3ZUEE0ScrhbuQ==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true - /@smithy/url-parser@2.0.1: - resolution: {integrity: sha512-NpHVOAwddo+OyyIoujDL9zGL96piHWrTNXqltWmBvlUoWgt1HPyBuKs6oHjioyFnNZXUqveTOkEEq0U5w6Uv8A==} + /@smithy/url-parser@2.0.11: + resolution: {integrity: sha512-h89yXMCCF+S5k9XIoKltMIWTYj+FcEkU/IIFZ6RtE222fskOTL4Iak6ZRG+ehSvZDt8yKEcxqheTDq7JvvtK3g==} requiresBuild: true dependencies: - '@smithy/querystring-parser': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@smithy/querystring-parser': 2.0.11 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true @@ -2988,7 +3066,7 @@ packages: requiresBuild: true dependencies: '@smithy/util-buffer-from': 2.0.0 - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true @@ -2996,16 +3074,16 @@ packages: resolution: {integrity: sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==} requiresBuild: true dependencies: - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true - /@smithy/util-body-length-node@2.0.0: - resolution: {integrity: sha512-ZV7Z/WHTMxHJe/xL/56qZwSUcl63/5aaPAGjkfynJm4poILjdD4GmFI+V+YWabh2WJIjwTKZ5PNsuvPQKt93Mg==} + /@smithy/util-body-length-node@2.1.0: + resolution: {integrity: sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true @@ -3015,7 +3093,7 @@ packages: requiresBuild: true dependencies: '@smithy/is-array-buffer': 2.0.0 - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true @@ -3024,33 +3102,35 @@ packages: engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true - /@smithy/util-defaults-mode-browser@2.0.1: - resolution: {integrity: sha512-w72Qwsb+IaEYEFtYICn0Do42eFju78hTaBzzJfT107lFOPdbjWjKnFutV+6GL/nZd5HWXY7ccAKka++C3NrjHw==} + /@smithy/util-defaults-mode-browser@2.0.14: + resolution: {integrity: sha512-NupG7SWUucm3vJrvlpt9jG1XeoPJphjcivgcUUXhDJbUPy4F04LhlTiAhWSzwlCNcF8OJsMvZ/DWbpYD3pselw==} engines: {node: '>= 10.0.0'} requiresBuild: true dependencies: - '@smithy/property-provider': 2.0.1 - '@smithy/types': 2.0.2 + '@smithy/property-provider': 2.0.12 + '@smithy/smithy-client': 2.1.10 + '@smithy/types': 2.3.5 bowser: 2.11.0 - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true - /@smithy/util-defaults-mode-node@2.0.1: - resolution: {integrity: sha512-dNF45caelEBambo0SgkzQ0v76m4YM+aFKZNTtSafy7P5dVF8TbjZuR2UX1A5gJABD9XK6lzN+v/9Yfzj/EDgGg==} + /@smithy/util-defaults-mode-node@2.0.18: + resolution: {integrity: sha512-+3jMom/b/Cdp21tDnY4vKu249Al+G/P0HbRbct7/aSZDlROzv1tksaYukon6UUv7uoHn+/McqnsvqZHLlqvQ0g==} engines: {node: '>= 10.0.0'} requiresBuild: true dependencies: - '@smithy/config-resolver': 2.0.1 - '@smithy/credential-provider-imds': 2.0.1 - '@smithy/node-config-provider': 2.0.1 - '@smithy/property-provider': 2.0.1 - '@smithy/types': 2.0.2 - tslib: 2.6.1 + '@smithy/config-resolver': 2.0.14 + '@smithy/credential-provider-imds': 2.0.16 + '@smithy/node-config-provider': 2.1.1 + '@smithy/property-provider': 2.0.12 + '@smithy/smithy-client': 2.1.10 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true @@ -3059,42 +3139,44 @@ packages: engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true - /@smithy/util-middleware@2.0.0: - resolution: {integrity: sha512-eCWX4ECuDHn1wuyyDdGdUWnT4OGyIzV0LN1xRttBFMPI9Ff/4heSHVxneyiMtOB//zpXWCha1/SWHJOZstG7kA==} + /@smithy/util-middleware@2.0.4: + resolution: {integrity: sha512-Pbu6P4MBwRcjrLgdTR1O4Y3c0sTZn2JdOiJNcgL7EcIStcQodj+6ZTXtbyU/WTEU3MV2NMA10LxFc3AWHZ3+4A==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - tslib: 2.6.1 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@smithy/util-retry@2.0.0: - resolution: {integrity: sha512-/dvJ8afrElasuiiIttRJeoS2sy8YXpksQwiM/TcepqdRVp7u4ejd9C4IQURHNjlfPUT7Y6lCDSa2zQJbdHhVTg==} + /@smithy/util-retry@2.0.4: + resolution: {integrity: sha512-b+n1jBBKc77C1E/zfBe1Zo7S9OXGBiGn55N0apfhZHxPUP/fMH5AhFUUcWaJh7NAnah284M5lGkBKuhnr3yK5w==} engines: {node: '>= 14.0.0'} requiresBuild: true dependencies: - '@smithy/service-error-classification': 2.0.0 - tslib: 2.6.1 + '@smithy/service-error-classification': 2.0.4 + '@smithy/types': 2.3.5 + tslib: 2.6.2 dev: false optional: true - /@smithy/util-stream@2.0.1: - resolution: {integrity: sha512-2a0IOtwIKC46EEo7E7cxDN8u2jwOiYYJqcFKA6rd5rdXqKakHT2Gc+AqHWngr0IEHUfW92zX12wRQKwyoqZf2Q==} + /@smithy/util-stream@2.0.15: + resolution: {integrity: sha512-A/hkYJPH2N5MCWYvky4tTpQihpYAEzqnUfxDyG3L/yMndy/2sLvxnyQal9Opuj1e9FiKSTeMyjnU9xxZGs0mRw==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/fetch-http-handler': 2.0.1 - '@smithy/node-http-handler': 2.0.1 - '@smithy/types': 2.0.2 + '@smithy/fetch-http-handler': 2.2.2 + '@smithy/node-http-handler': 2.1.7 + '@smithy/types': 2.3.5 '@smithy/util-base64': 2.0.0 '@smithy/util-buffer-from': 2.0.0 '@smithy/util-hex-encoding': 2.0.0 '@smithy/util-utf8': 2.0.0 - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true @@ -3103,7 +3185,7 @@ packages: engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true @@ -3113,7 +3195,7 @@ packages: requiresBuild: true dependencies: '@smithy/util-buffer-from': 2.0.0 - tslib: 2.6.1 + tslib: 2.6.2 dev: false optional: true @@ -4302,14 +4384,14 @@ packages: dev: false optional: true - /chai@4.3.7: - resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} + /chai@4.3.10: + resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} engines: {node: '>=4'} dependencies: assertion-error: 1.1.0 - check-error: 1.0.2 + check-error: 1.0.3 deep-eql: 4.1.3 - get-func-name: 2.0.0 + get-func-name: 2.0.2 loupe: 2.3.6 pathval: 1.1.1 type-detect: 4.0.8 @@ -4331,8 +4413,10 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 - /check-error@1.0.2: - resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 dev: true /chokidar@3.5.3: @@ -4347,7 +4431,7 @@ packages: normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /ci@2.2.0: @@ -4484,7 +4568,7 @@ packages: /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - /connect-mongo@4.6.0(express-session@1.17.3)(mongodb@4.16.0): + /connect-mongo@4.6.0(express-session@1.17.3)(mongodb@4.17.1): resolution: {integrity: sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==} engines: {node: '>=10'} peerDependencies: @@ -4494,7 +4578,7 @@ packages: debug: 4.3.4 express-session: 1.17.3 kruptein: 3.0.6 - mongodb: 4.16.0 + mongodb: 4.17.1 transitivePeerDependencies: - supports-color dev: false @@ -5302,7 +5386,7 @@ packages: node-forge: 1.3.1 uuid: 9.0.0 optionalDependencies: - '@google-cloud/firestore': 6.7.0 + '@google-cloud/firestore': 6.8.0 '@google-cloud/storage': 6.12.0 transitivePeerDependencies: - encoding @@ -5378,8 +5462,8 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true @@ -5433,8 +5517,8 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - /get-func-name@2.0.0: - resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} dev: true /get-intrinsic@1.2.1: @@ -6246,7 +6330,7 @@ packages: /loupe@2.3.6: resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} dependencies: - get-func-name: 2.0.0 + get-func-name: 2.0.2 dev: true /lru-cache@4.0.2: @@ -6492,27 +6576,27 @@ packages: whatwg-url: 11.0.0 dev: false - /mongodb@4.16.0: - resolution: {integrity: sha512-0EB113Fsucaq1wsY0dOhi1fmZOwFtLOtteQkiqOXGklvWMnSH3g2QS53f0KTP+/6qOkuoXE2JksubSZNmxeI+g==} + /mongodb@4.17.1: + resolution: {integrity: sha512-MBuyYiPUPRTqfH2dV0ya4dcr2E5N52ocBuZ8Sgg/M030nGF78v855B3Z27mZJnp8PxjnUquEnAtjOsphgMZOlQ==} engines: {node: '>=12.9.0'} dependencies: bson: 4.7.2 mongodb-connection-string-url: 2.6.0 socks: 2.7.1 optionalDependencies: - '@aws-sdk/credential-providers': 3.385.0 - saslprep: 1.0.3 + '@aws-sdk/credential-providers': 3.427.0 + '@mongodb-js/saslprep': 1.1.0 transitivePeerDependencies: - aws-crt dev: false - /mongoose@6.11.5: - resolution: {integrity: sha512-ZarPe1rCHG4aVb78xLuok4BBIm0HMz/Y/CjxYXCk3Qz1mEhS7bPMy6ZhSX2/Dng//R7ei8719j6K87UVM/1b3g==} + /mongoose@6.12.0: + resolution: {integrity: sha512-sd/q83C6TBRPBrrD2A/POSbA/exbCFM2WOuY7Lf2JuIJFlHFG39zYSDTTAEiYlzIfahNOLmXPxBGFxdAch41Mw==} engines: {node: '>=12.0.0'} dependencies: bson: 4.7.2 kareem: 2.5.1 - mongodb: 4.16.0 + mongodb: 4.17.1 mpath: 0.9.0 mquery: 4.0.3 ms: 2.1.3 @@ -6606,9 +6690,9 @@ packages: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} dev: false - /nodemon@2.0.22: - resolution: {integrity: sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==} - engines: {node: '>=8.10.0'} + /nodemon@3.0.1: + resolution: {integrity: sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==} + engines: {node: '>=10'} hasBin: true dependencies: chokidar: 3.5.3 @@ -6616,8 +6700,8 @@ packages: ignore-by-default: 1.0.1 minimatch: 3.1.2 pstree.remy: 1.1.8 - semver: 5.7.2 - simple-update-notifier: 1.1.0 + semver: 7.5.4 + simple-update-notifier: 2.0.0 supports-color: 5.5.0 touch: 3.1.0 undefsafe: 2.0.5 @@ -7035,7 +7119,7 @@ packages: engines: {node: '>=12.0.0'} requiresBuild: true dependencies: - protobufjs: 7.2.4 + protobufjs: 7.2.5 dev: false optional: true @@ -7081,6 +7165,26 @@ packages: dev: false optional: true + /protobufjs@7.2.5: + resolution: {integrity: sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==} + engines: {node: '>=12.0.0'} + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 20.4.7 + long: 5.2.3 + dev: false + optional: true + /proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -7542,7 +7646,7 @@ packages: engines: {node: '>=10.0.0'} hasBin: true optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: false /rope-sequence@1.3.4: @@ -7566,15 +7670,6 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: false - /saslprep@1.0.3: - resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==} - engines: {node: '>=6'} - requiresBuild: true - dependencies: - sparse-bitfield: 3.0.3 - dev: false - optional: true - /sax@1.2.1: resolution: {integrity: sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==} dev: false @@ -7588,17 +7683,13 @@ packages: /semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true + dev: false /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true dev: false - /semver@7.0.0: - resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==} - hasBin: true - dev: true - /semver@7.5.4: resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} engines: {node: '>=10'} @@ -7697,11 +7788,11 @@ packages: is-arrayish: 0.3.2 dev: false - /simple-update-notifier@1.1.0: - resolution: {integrity: sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==} - engines: {node: '>=8.10.0'} + /simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} dependencies: - semver: 7.0.0 + semver: 7.5.4 dev: true /slash@3.0.0: @@ -8074,6 +8165,10 @@ packages: resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==} dev: false + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: false + /type-check@0.3.2: resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} engines: {node: '>= 0.8.0'} From 8367363f0b635ac8aac5171a313286555f17b58e Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 7 Oct 2023 21:10:21 +0900 Subject: [PATCH 249/261] Fix: remove FRONT_URL in CI workflow --- .github/workflows/test_ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test_ci.yml b/.github/workflows/test_ci.yml index dab9f5a0..83d4c2e7 100644 --- a/.github/workflows/test_ci.yml +++ b/.github/workflows/test_ci.yml @@ -53,6 +53,5 @@ jobs: AWS_S3_BUCKET_NAME: ${{ secrets.AWS_S3_BUCKET_NAME }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} DB_PATH: ${{ secrets.DB_PATH }} - FRONT_URL: ${{ secrets.FRONT_URL }} PORT: ${{ secrets.PORT }} SESSION_KEY: ${{ secrets.SESSION_KEY }} From 8da001336239493bb3825ca2af5a10f08a8941c4 Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 7 Oct 2023 21:31:10 +0900 Subject: [PATCH 250/261] Refactor: move test codes into /test/services --- test/{ => services}/auth.replace.js | 4 +- test/{ => services}/locations.js | 30 +- test/{ => services}/logininfo.js | 4 +- test/{ => services}/reports.js | 8 +- test/{ => services}/rooms.js | 406 ++++++++++++++-------------- test/{ => services}/users.js | 280 +++++++++---------- 6 files changed, 366 insertions(+), 366 deletions(-) rename test/{ => services}/auth.replace.js (82%) rename test/{ => services}/locations.js (87%) rename test/{ => services}/logininfo.js (94%) rename test/{ => services}/reports.js (92%) rename test/{ => services}/rooms.js (95%) rename test/{ => services}/users.js (94%) diff --git a/test/auth.replace.js b/test/services/auth.replace.js similarity index 82% rename from test/auth.replace.js rename to test/services/auth.replace.js index 322d13ef..643eddf7 100644 --- a/test/auth.replace.js +++ b/test/services/auth.replace.js @@ -1,7 +1,7 @@ const request = require("supertest"); -const authHandlers = require("../src/services/auth.replace"); -const { userModel } = require("../src/modules/stores/mongo"); +const authHandlers = require("../../src/services/auth.replace"); +const { userModel } = require("../../src/modules/stores/mongo"); // auth.replace.js 관련 1개의 handler을 테스트 // 1. dev 환경에서 로그인이 성공적으로 이루어지는지 확인 diff --git a/test/locations.js b/test/services/locations.js similarity index 87% rename from test/locations.js rename to test/services/locations.js index 19308e51..7df01482 100644 --- a/test/locations.js +++ b/test/services/locations.js @@ -1,15 +1,15 @@ -const expect = require("chai").expect; -const locationHandlers = require("../src/services/locations"); -const httpMocks = require("node-mocks-http"); - -// locations.js 관련 1개의 handler을 테스트 -// 1. 모든 location 정보를 잘 가져오는지 확인 -describe("[locations] 1.getAllLocationsHandler", () => { - it("should return information of locations correctly", async () => { - let req = httpMocks.createRequest({}); - let res = httpMocks.createResponse(); - await locationHandlers.getAllLocationsHandler(req, res); - - expect(res._getJSONData().locations).not.to.have.lengthOf(0); - }); -}); +const expect = require("chai").expect; +const locationHandlers = require("../../src/services/locations"); +const httpMocks = require("node-mocks-http"); + +// locations.js 관련 1개의 handler을 테스트 +// 1. 모든 location 정보를 잘 가져오는지 확인 +describe("[locations] 1.getAllLocationsHandler", () => { + it("should return information of locations correctly", async () => { + let req = httpMocks.createRequest({}); + let res = httpMocks.createResponse(); + await locationHandlers.getAllLocationsHandler(req, res); + + expect(res._getJSONData().locations).not.to.have.lengthOf(0); + }); +}); diff --git a/test/logininfo.js b/test/services/logininfo.js similarity index 94% rename from test/logininfo.js rename to test/services/logininfo.js index 64e492f7..30204643 100644 --- a/test/logininfo.js +++ b/test/services/logininfo.js @@ -1,6 +1,6 @@ const expect = require("chai").expect; -const logininfoHandlers = require("../src/services/logininfo"); -const { userModel } = require("../src/modules/stores/mongo"); +const logininfoHandlers = require("../../src/services/logininfo"); +const { userModel } = require("../../src/modules/stores/mongo"); // 1-1. 로그인 한 유저가 없을 시 undefined를 return 하는지 확인 // 1-2. login 정보를 잘 return 하는지 확인 diff --git a/test/reports.js b/test/services/reports.js similarity index 92% rename from test/reports.js rename to test/services/reports.js index 67bf4001..86347d4a 100644 --- a/test/reports.js +++ b/test/services/reports.js @@ -1,7 +1,7 @@ const expect = require("chai").expect; -const reportHandlers = require("../src/services/reports"); -const { userModel } = require("../src/modules/stores/mongo"); -const { userGenerator, roomGenerator, testRemover } = require("./utils"); +const reportHandlers = require("../../src/services/reports"); +const { userModel } = require("../../src/modules/stores/mongo"); +const { userGenerator, roomGenerator, testRemover } = require("../utils"); const httpMocks = require("node-mocks-http"); let testData = { rooms: [], users: [], chat: [], location: [], report: [] }; @@ -24,7 +24,7 @@ describe("[reports] 1.createHandler", () => { type: "etc-reason", etcDetail: "etc-detail", time: Date.now(), - roomId: testRoom._id + roomId: testRoom._id, }, }); let res = httpMocks.createResponse(); diff --git a/test/rooms.js b/test/services/rooms.js similarity index 95% rename from test/rooms.js rename to test/services/rooms.js index 6ba0c232..e17ae6af 100644 --- a/test/rooms.js +++ b/test/services/rooms.js @@ -1,203 +1,203 @@ -const expect = require("chai").expect; -const express = require("express"); -const roomsHandlers = require("../src/services/rooms"); -const { - userModel, - roomModel, - locationModel, -} = require("../src/modules/stores/mongo"); -const { userGenerator, testRemover } = require("./utils"); -const app = express(); -const httpMocks = require("node-mocks-http"); - -let testData = { rooms: [], users: [], chat: [], location: [], report: [] }; -const removeTestData = async () => { - // drop all testData - await testRemover(testData); -}; - -// rooms.js 관련 9개의 handler을 테스트 -// 1. test1이 1분 뒤에 출발하는 test-room 방을 생성, 제대로 생성 되었는지 확인 -describe("[rooms] 1.createHandler", () => { - it("should create room which departs after 1 minute", async () => { - const testUser1 = await userGenerator("test1", testData); - const testFrom = await locationModel.findOne({ koName: "대전역" }); - const testTo = await locationModel.findOne({ koName: "택시승강장" }); - let req = httpMocks.createRequest({ - body: { - name: "test-room", - from: testFrom._id, - to: testTo._id, - time: Date.now() + 60 * 1000, - maxPartLength: 4, - }, - userId: testUser1.id, - app, - }); - let res = httpMocks.createResponse(); - await roomsHandlers.createHandler(req, res); - - const testRoom = await roomModel.findOne({ name: "test-room" }); - testData["rooms"].push(testRoom); - const resData = res._getData(); - expect(resData).to.has.property("name", "test-room"); - }); -}); - -// 2. test1을 통하여 방의 정보를 제대로 가져오는지 확인 -describe("[rooms] 2.infoHandler", () => { - it("should return information of room", async () => { - const testUser1 = await userModel.findOne({ id: "test1" }); - const testRoom = await roomModel.findOne({ name: "test-room" }); - let req = httpMocks.createRequest({ - query: { id: testRoom._id }, - userId: testUser1.id, - }); - let res = httpMocks.createResponse(); - await roomsHandlers.infoHandler(req, res); - - const resData = res._getData(); - expect(resData).to.has.property("name", "test-room"); - expect(resData).to.has.property("isOver"); - }); -}); - -// 3. 로그인되지 않은 유저가 방의 정보를 제대로 가져오는지 확인 -describe("[rooms] 3.publicInfoHandler", () => { - it("should return information of room", async () => { - const testRoom = await roomModel.findOne({ name: "test-room" }); - let req = httpMocks.createRequest({ - query: { id: testRoom._id }, - }); - let res = httpMocks.createResponse(); - await roomsHandlers.publicInfoHandler(req, res); - - const resData = res._getData(); - expect(resData).to.has.property("name", "test-room"); - expect(resData).to.has.property("isOver", undefined); - }); -}); - -// 4. test2가 test-room에 join, 방에 잘 join 했는지 확인 -describe("[rooms] 4.joinHandler", () => { - it("should return information of room and join", async () => { - const testUser2 = await userGenerator("test2", testData); - const testRoom = await roomModel.findOne({ name: "test-room" }); - let req = httpMocks.createRequest({ - body: { - roomId: testRoom._id, - }, - userId: testUser2.id, - app, - }); - let res = httpMocks.createResponse(); - await roomsHandlers.joinHandler(req, res); - - const resData = res._getData(); - expect(resData).to.has.property("name", "test-room"); - expect(resData.part).to.have.lengthOf(2); - }); -}); - -// 5. 방의 정보를 통해 검색, 검색 정보가 예상과 일치하는지 확인 -describe("[rooms] 5.searchHandler", () => { - it("should return information of searching room", async () => { - const testFrom = await locationModel.findOne({ koName: "대전역" }); - const testTo = await locationModel.findOne({ koName: "택시승강장" }); - let req = httpMocks.createRequest({ - query: { - name: "test-room", - from: testFrom._id, - to: testTo._id, - time: Date.now(), - withTime: true, - maxPartLength: 4, - }, - }); - let res = httpMocks.createResponse(); - await roomsHandlers.searchHandler(req, res); - - const resJson = res._getJSONData(); - expect(resJson[0]).to.has.property("name", "test-room"); - expect(resJson[0].settlementTotal).to.be.undefined; - }); -}); - -// 6. 방에 속한 유저를 통해 검색 -// ongoing은 test-room이 검색되고, done은 아무것도 검색되지 않아야함 -describe("[rooms] 6.searchByUserHandler", () => { - it("should return information of searching room", async () => { - const testUser1 = await userModel.findOne({ id: "test1" }); - let req = httpMocks.createRequest({ - userId: testUser1.id, - }); - let res = httpMocks.createResponse(); - await roomsHandlers.searchByUserHandler(req, res); - - const resJson = res._getJSONData(); - expect(resJson["ongoing"][0]).to.has.property("name", "test-room"); - expect(resJson["done"][0]).to.be.undefined; - }); -}); - -// 7. 1분이 지난 후, 정산 정보를 불러옴. 예상과 같은 정보를 불러오는지 확인 -describe("[rooms] 7.commitPaymentHandler", () => { - it("should return information of room and commit payment", async () => { - const testUser1 = await userModel.findOne({ id: "test1" }); - const testRoom = await roomModel.findOne({ name: "test-room" }); - let req = httpMocks.createRequest({ - body: { roomId: testRoom._id }, - userId: testUser1.id, - timestamp: Date.now() + 60 * 1000, - app, - }); - let res = httpMocks.createResponse(); - await roomsHandlers.commitPaymentHandler(req, res); - - const resData = res._getData(); - expect(resData).to.has.property("name", "test-room"); - expect(resData).to.has.property("isOver", true); - expect(resData).to.has.property("settlementTotal", 1); - }); -}); - -// 8. 도착 정보를 불러옴. 예상과 같은 정보를 불러오는지 확인 -describe("[rooms] 8.settlementHandler", () => { - it("should return information of room and set settlement", async () => { - const testUser2 = await userModel.findOne({ id: "test2" }); - const testRoom = await roomModel.findOne({ name: "test-room" }); - let req = httpMocks.createRequest({ - body: { roomId: testRoom._id }, - userId: testUser2.id, - app, - }); - let res = httpMocks.createResponse(); - await roomsHandlers.settlementHandler(req, res); - - const resData = res._getData(); - expect(resData).to.has.property("name", "test-room"); - expect(resData).to.has.property("isOver", true); - expect(resData).to.has.property("settlementTotal", 2); - }); -}); - -// 9. test2 방에서 퇴장, 제대로 방에서 나갔는지 확인하고 생성해준 data 모두 삭제 -describe("[rooms] 9.abortHandler", () => { - it("should return information of room and abort user", async () => { - const testUser2 = await userModel.findOne({ id: "test2" }); - const testRoom = await roomModel.findOne({ name: "test-room" }); - let req = httpMocks.createRequest({ - body: { roomId: testRoom._id }, - userId: testUser2.id, - session: {}, - app, - }); - let res = httpMocks.createResponse(); - await roomsHandlers.abortHandler(req, res); - afterEach(removeTestData); - - const resData = res._getData(); - expect(resData).to.has.property("name", "test-room"); - expect(resData.part).to.have.lengthOf(1); - }); -}); +const expect = require("chai").expect; +const express = require("express"); +const roomsHandlers = require("../../src/services/rooms"); +const { + userModel, + roomModel, + locationModel, +} = require("../../src/modules/stores/mongo"); +const { userGenerator, testRemover } = require("../utils"); +const app = express(); +const httpMocks = require("node-mocks-http"); + +let testData = { rooms: [], users: [], chat: [], location: [], report: [] }; +const removeTestData = async () => { + // drop all testData + await testRemover(testData); +}; + +// rooms.js 관련 9개의 handler을 테스트 +// 1. test1이 1분 뒤에 출발하는 test-room 방을 생성, 제대로 생성 되었는지 확인 +describe("[rooms] 1.createHandler", () => { + it("should create room which departs after 1 minute", async () => { + const testUser1 = await userGenerator("test1", testData); + const testFrom = await locationModel.findOne({ koName: "대전역" }); + const testTo = await locationModel.findOne({ koName: "택시승강장" }); + let req = httpMocks.createRequest({ + body: { + name: "test-room", + from: testFrom._id, + to: testTo._id, + time: Date.now() + 60 * 1000, + maxPartLength: 4, + }, + userId: testUser1.id, + app, + }); + let res = httpMocks.createResponse(); + await roomsHandlers.createHandler(req, res); + + const testRoom = await roomModel.findOne({ name: "test-room" }); + testData["rooms"].push(testRoom); + const resData = res._getData(); + expect(resData).to.has.property("name", "test-room"); + }); +}); + +// 2. test1을 통하여 방의 정보를 제대로 가져오는지 확인 +describe("[rooms] 2.infoHandler", () => { + it("should return information of room", async () => { + const testUser1 = await userModel.findOne({ id: "test1" }); + const testRoom = await roomModel.findOne({ name: "test-room" }); + let req = httpMocks.createRequest({ + query: { id: testRoom._id }, + userId: testUser1.id, + }); + let res = httpMocks.createResponse(); + await roomsHandlers.infoHandler(req, res); + + const resData = res._getData(); + expect(resData).to.has.property("name", "test-room"); + expect(resData).to.has.property("isOver"); + }); +}); + +// 3. 로그인되지 않은 유저가 방의 정보를 제대로 가져오는지 확인 +describe("[rooms] 3.publicInfoHandler", () => { + it("should return information of room", async () => { + const testRoom = await roomModel.findOne({ name: "test-room" }); + let req = httpMocks.createRequest({ + query: { id: testRoom._id }, + }); + let res = httpMocks.createResponse(); + await roomsHandlers.publicInfoHandler(req, res); + + const resData = res._getData(); + expect(resData).to.has.property("name", "test-room"); + expect(resData).to.has.property("isOver", undefined); + }); +}); + +// 4. test2가 test-room에 join, 방에 잘 join 했는지 확인 +describe("[rooms] 4.joinHandler", () => { + it("should return information of room and join", async () => { + const testUser2 = await userGenerator("test2", testData); + const testRoom = await roomModel.findOne({ name: "test-room" }); + let req = httpMocks.createRequest({ + body: { + roomId: testRoom._id, + }, + userId: testUser2.id, + app, + }); + let res = httpMocks.createResponse(); + await roomsHandlers.joinHandler(req, res); + + const resData = res._getData(); + expect(resData).to.has.property("name", "test-room"); + expect(resData.part).to.have.lengthOf(2); + }); +}); + +// 5. 방의 정보를 통해 검색, 검색 정보가 예상과 일치하는지 확인 +describe("[rooms] 5.searchHandler", () => { + it("should return information of searching room", async () => { + const testFrom = await locationModel.findOne({ koName: "대전역" }); + const testTo = await locationModel.findOne({ koName: "택시승강장" }); + let req = httpMocks.createRequest({ + query: { + name: "test-room", + from: testFrom._id, + to: testTo._id, + time: Date.now(), + withTime: true, + maxPartLength: 4, + }, + }); + let res = httpMocks.createResponse(); + await roomsHandlers.searchHandler(req, res); + + const resJson = res._getJSONData(); + expect(resJson[0]).to.has.property("name", "test-room"); + expect(resJson[0].settlementTotal).to.be.undefined; + }); +}); + +// 6. 방에 속한 유저를 통해 검색 +// ongoing은 test-room이 검색되고, done은 아무것도 검색되지 않아야함 +describe("[rooms] 6.searchByUserHandler", () => { + it("should return information of searching room", async () => { + const testUser1 = await userModel.findOne({ id: "test1" }); + let req = httpMocks.createRequest({ + userId: testUser1.id, + }); + let res = httpMocks.createResponse(); + await roomsHandlers.searchByUserHandler(req, res); + + const resJson = res._getJSONData(); + expect(resJson["ongoing"][0]).to.has.property("name", "test-room"); + expect(resJson["done"][0]).to.be.undefined; + }); +}); + +// 7. 1분이 지난 후, 정산 정보를 불러옴. 예상과 같은 정보를 불러오는지 확인 +describe("[rooms] 7.commitPaymentHandler", () => { + it("should return information of room and commit payment", async () => { + const testUser1 = await userModel.findOne({ id: "test1" }); + const testRoom = await roomModel.findOne({ name: "test-room" }); + let req = httpMocks.createRequest({ + body: { roomId: testRoom._id }, + userId: testUser1.id, + timestamp: Date.now() + 60 * 1000, + app, + }); + let res = httpMocks.createResponse(); + await roomsHandlers.commitPaymentHandler(req, res); + + const resData = res._getData(); + expect(resData).to.has.property("name", "test-room"); + expect(resData).to.has.property("isOver", true); + expect(resData).to.has.property("settlementTotal", 1); + }); +}); + +// 8. 도착 정보를 불러옴. 예상과 같은 정보를 불러오는지 확인 +describe("[rooms] 8.settlementHandler", () => { + it("should return information of room and set settlement", async () => { + const testUser2 = await userModel.findOne({ id: "test2" }); + const testRoom = await roomModel.findOne({ name: "test-room" }); + let req = httpMocks.createRequest({ + body: { roomId: testRoom._id }, + userId: testUser2.id, + app, + }); + let res = httpMocks.createResponse(); + await roomsHandlers.settlementHandler(req, res); + + const resData = res._getData(); + expect(resData).to.has.property("name", "test-room"); + expect(resData).to.has.property("isOver", true); + expect(resData).to.has.property("settlementTotal", 2); + }); +}); + +// 9. test2 방에서 퇴장, 제대로 방에서 나갔는지 확인하고 생성해준 data 모두 삭제 +describe("[rooms] 9.abortHandler", () => { + it("should return information of room and abort user", async () => { + const testUser2 = await userModel.findOne({ id: "test2" }); + const testRoom = await roomModel.findOne({ name: "test-room" }); + let req = httpMocks.createRequest({ + body: { roomId: testRoom._id }, + userId: testUser2.id, + session: {}, + app, + }); + let res = httpMocks.createResponse(); + await roomsHandlers.abortHandler(req, res); + afterEach(removeTestData); + + const resData = res._getData(); + expect(resData).to.has.property("name", "test-room"); + expect(resData.part).to.have.lengthOf(1); + }); +}); diff --git a/test/users.js b/test/services/users.js similarity index 94% rename from test/users.js rename to test/services/users.js index 1efa0d86..1bb2f586 100644 --- a/test/users.js +++ b/test/services/users.js @@ -1,140 +1,140 @@ -const expect = require("chai").expect; -const usersHandlers = require("../src/services/users"); -const { userModel } = require("../src/modules/stores/mongo"); -const { userGenerator, testRemover } = require("./utils"); -const httpMocks = require("node-mocks-http"); - -let testData = { rooms: [], users: [], chat: [], location: [], report: [] }; -const removeTestData = async () => { - await testRemover(testData); -}; - -// users.js 관련 5개의 handler을 테스트 -// 1. test1 유저를 생성 후, agreeOnTermsOfServiceHandler가 제대로 msg를 send 하는지 확인 -describe("[users] 1.agreeOnTermsOfServiceHandler", () => { - it("should return correct response from handler", async () => { - const testUser1 = await userGenerator("test1", testData); - const msg = - "User/agreeOnTermsOfService : agree on Terms of Service successful"; - let req = httpMocks.createRequest({ - userId: testUser1.id, - }); - let res = httpMocks.createResponse(); - await usersHandlers.agreeOnTermsOfServiceHandler(req, res); - - const resData = res._getData(); - expect(res).to.has.property("statusCode", 200); - expect(resData).to.equal(msg); - }); -}); - -// 2. test1 유저의 agreeOnTermsOfService 정보를 가져와서 true인지 확인 -describe("[users] 2.getAgreeOnTermsOfServiceHandler", () => { - it("should return AgreeOnTermsOfService of user", async () => { - const testUser1 = await userModel.findOne({ id: "test1" }); - let req = httpMocks.createRequest({ - userId: testUser1.id, - }); - let res = httpMocks.createResponse(); - await usersHandlers.getAgreeOnTermsOfServiceHandler(req, res); - - const resJson = res._getJSONData(); - expect(res).to.has.property("statusCode", 200); - expect(resJson).to.has.property("agreeOnTermsOfService", true); - }); -}); - -// 3. test1 유저의 nickname을 test-nickname으로 변경, 성공 메세지가 제대로 오는지 확인 -describe("[users] 3.editNicknameHandler", () => { - const testNickname = "test-nickname"; - - it("should return correct response from handler", async () => { - const testUser1 = await userModel.findOne({ id: "test1" }); - const msg = "User/editNickname : edit user nickname successful"; - let req = httpMocks.createRequest({ - userId: testUser1.id, - body: { - nickname: testNickname, - }, - }); - let res = httpMocks.createResponse(); - await usersHandlers.editNicknameHandler(req, res); - - const resData = res._getData(); - expect(res).to.has.property("statusCode", 200); - expect(resData).to.equal(msg); - }); - - it("should be changed to new nickname", async () => { - const testUser1 = await userModel.findOne({ id: "test1" }); - expect(testUser1).to.have.property("nickname", testNickname); - }); -}); - -// 3. test1 유저의 계좌번호를 testAccount으로 변경, 성공 메세지가 제대로 오는지 확인 -describe("[users] 4.editAccountHandler", () => { - const testAccount = "신한 0123456789012"; - - it("should return correct response from handler", async () => { - const testUser1 = await userModel.findOne({ id: "test1" }); - const msg = "User/editAccount : edit user account successful"; - let req = httpMocks.createRequest({ - userId: testUser1.id, - body: { - account: testAccount, - }, - }); - let res = httpMocks.createResponse(); - await usersHandlers.editAccountHandler(req, res); - - const resData = res._getData(); - expect(res).to.has.property("statusCode", 200); - expect(resData).to.equal(msg); - }); - - it("should be changed to new account", async () => { - const testUser1 = await userModel.findOne({ id: "test1" }); - expect(testUser1).to.have.property("account", testAccount); - }); -}); - -// 5. test1 유저의 프로필 업로드를 위한 PUrl을 제대로 받았는지 확인 -// 추가 검증을 위해, key와 Content-Type이 일치하는지 확인 -describe("[users] 5.editProfileImgGetPUrlHandler", () => { - it("should return url and fields of data", async () => { - const testUser1 = await userModel.findOne({ id: "test1" }); - const testImgType = "image/jpg"; - let req = httpMocks.createRequest({ - userId: testUser1.id, - body: { - type: testImgType, - }, - }); - let res = httpMocks.createResponse(); - await usersHandlers.editProfileImgGetPUrlHandler(req, res); - - const resJson = res._getJSONData(); - expect(res).to.has.property("statusCode", 200); - expect(resJson).to.has.property("url"); - expect(resJson.fields).to.has.property( - "key", - `profile-img/${testUser1._id}` - ); - expect(resJson.fields).to.has.property("Content-Type", testImgType); - }); -}); - -// 6. test1 유저의 프로필 업로드가 정상적으로 완료되었는지 확인 -describe("[users] 6.editProfileImgDoneHandler", () => { - it("should return correct result and new profileImageUrl", async () => { - const testUser1 = await userModel.findOne({ id: "test1" }); - let req = httpMocks.createRequest({ - userId: testUser1.id, - }); - let res = httpMocks.createResponse(); - await usersHandlers.editProfileImgDoneHandler(req, res); - afterEach(removeTestData); - - expect(res).to.has.property("statusCode", 200); - }); -}); +const expect = require("chai").expect; +const usersHandlers = require("../../src/services/users"); +const { userModel } = require("../../src/modules/stores/mongo"); +const { userGenerator, testRemover } = require("../utils"); +const httpMocks = require("node-mocks-http"); + +let testData = { rooms: [], users: [], chat: [], location: [], report: [] }; +const removeTestData = async () => { + await testRemover(testData); +}; + +// users.js 관련 5개의 handler을 테스트 +// 1. test1 유저를 생성 후, agreeOnTermsOfServiceHandler가 제대로 msg를 send 하는지 확인 +describe("[users] 1.agreeOnTermsOfServiceHandler", () => { + it("should return correct response from handler", async () => { + const testUser1 = await userGenerator("test1", testData); + const msg = + "User/agreeOnTermsOfService : agree on Terms of Service successful"; + let req = httpMocks.createRequest({ + userId: testUser1.id, + }); + let res = httpMocks.createResponse(); + await usersHandlers.agreeOnTermsOfServiceHandler(req, res); + + const resData = res._getData(); + expect(res).to.has.property("statusCode", 200); + expect(resData).to.equal(msg); + }); +}); + +// 2. test1 유저의 agreeOnTermsOfService 정보를 가져와서 true인지 확인 +describe("[users] 2.getAgreeOnTermsOfServiceHandler", () => { + it("should return AgreeOnTermsOfService of user", async () => { + const testUser1 = await userModel.findOne({ id: "test1" }); + let req = httpMocks.createRequest({ + userId: testUser1.id, + }); + let res = httpMocks.createResponse(); + await usersHandlers.getAgreeOnTermsOfServiceHandler(req, res); + + const resJson = res._getJSONData(); + expect(res).to.has.property("statusCode", 200); + expect(resJson).to.has.property("agreeOnTermsOfService", true); + }); +}); + +// 3. test1 유저의 nickname을 test-nickname으로 변경, 성공 메세지가 제대로 오는지 확인 +describe("[users] 3.editNicknameHandler", () => { + const testNickname = "test-nickname"; + + it("should return correct response from handler", async () => { + const testUser1 = await userModel.findOne({ id: "test1" }); + const msg = "User/editNickname : edit user nickname successful"; + let req = httpMocks.createRequest({ + userId: testUser1.id, + body: { + nickname: testNickname, + }, + }); + let res = httpMocks.createResponse(); + await usersHandlers.editNicknameHandler(req, res); + + const resData = res._getData(); + expect(res).to.has.property("statusCode", 200); + expect(resData).to.equal(msg); + }); + + it("should be changed to new nickname", async () => { + const testUser1 = await userModel.findOne({ id: "test1" }); + expect(testUser1).to.have.property("nickname", testNickname); + }); +}); + +// 3. test1 유저의 계좌번호를 testAccount으로 변경, 성공 메세지가 제대로 오는지 확인 +describe("[users] 4.editAccountHandler", () => { + const testAccount = "신한 0123456789012"; + + it("should return correct response from handler", async () => { + const testUser1 = await userModel.findOne({ id: "test1" }); + const msg = "User/editAccount : edit user account successful"; + let req = httpMocks.createRequest({ + userId: testUser1.id, + body: { + account: testAccount, + }, + }); + let res = httpMocks.createResponse(); + await usersHandlers.editAccountHandler(req, res); + + const resData = res._getData(); + expect(res).to.has.property("statusCode", 200); + expect(resData).to.equal(msg); + }); + + it("should be changed to new account", async () => { + const testUser1 = await userModel.findOne({ id: "test1" }); + expect(testUser1).to.have.property("account", testAccount); + }); +}); + +// 5. test1 유저의 프로필 업로드를 위한 PUrl을 제대로 받았는지 확인 +// 추가 검증을 위해, key와 Content-Type이 일치하는지 확인 +describe("[users] 5.editProfileImgGetPUrlHandler", () => { + it("should return url and fields of data", async () => { + const testUser1 = await userModel.findOne({ id: "test1" }); + const testImgType = "image/jpg"; + let req = httpMocks.createRequest({ + userId: testUser1.id, + body: { + type: testImgType, + }, + }); + let res = httpMocks.createResponse(); + await usersHandlers.editProfileImgGetPUrlHandler(req, res); + + const resJson = res._getJSONData(); + expect(res).to.has.property("statusCode", 200); + expect(resJson).to.has.property("url"); + expect(resJson.fields).to.has.property( + "key", + `profile-img/${testUser1._id}` + ); + expect(resJson.fields).to.has.property("Content-Type", testImgType); + }); +}); + +// 6. test1 유저의 프로필 업로드가 정상적으로 완료되었는지 확인 +describe("[users] 6.editProfileImgDoneHandler", () => { + it("should return correct result and new profileImageUrl", async () => { + const testUser1 = await userModel.findOne({ id: "test1" }); + let req = httpMocks.createRequest({ + userId: testUser1.id, + }); + let res = httpMocks.createResponse(); + await usersHandlers.editProfileImgDoneHandler(req, res); + afterEach(removeTestData); + + expect(res).to.has.property("statusCode", 200); + }); +}); From 660a23251477e92c7f9e93546074adb61e58e858 Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 7 Oct 2023 22:21:59 +0900 Subject: [PATCH 251/261] Refactor: bump jsonwebtoken version to ^9.0.2 --- loadenv.js | 1 + package.json | 2 +- pnpm-lock.yaml | 22 ++++++---------------- test/modules/auths/jwt.js | 29 +++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 test/modules/auths/jwt.js diff --git a/loadenv.js b/loadenv.js index 3dfc5be6..12634228 100644 --- a/loadenv.js +++ b/loadenv.js @@ -25,6 +25,7 @@ module.exports = { secretKey: process.env.JWT_SECRET_KEY || "TAXI_JWT_KEY", option: { algorithm: "HS256", + // FIXME: remove FRONT_URL from issuer. 단, issuer를 변경하면 이전에 발급했던 모든 JWT가 무효화됩니다. issuer: process.env.FRONT_URL || "http://localhost:3000", // optional (default = "http://localhost:3000") }, TOKEN_EXPIRED: -3, diff --git a/package.json b/package.json index 10e08d1f..fecc4769 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "express-session": "^1.17.3", "express-validator": "^6.14.0", "firebase-admin": "^11.4.1", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.2", "mongoose": "^6.12.0", "node-cron": "3.0.2", "node-mocks-http": "^1.12.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44ff861f..d197fbe1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,8 +72,8 @@ dependencies: specifier: ^11.4.1 version: 11.10.1 jsonwebtoken: - specifier: ^8.5.1 - version: 8.5.1 + specifier: ^9.0.2 + version: 9.0.2 mongoose: specifier: ^6.12.0 version: 6.12.0 @@ -5381,7 +5381,7 @@ packages: '@firebase/database-compat': 0.3.4 '@firebase/database-types': 0.10.4 '@types/node': 20.4.7 - jsonwebtoken: 9.0.1 + jsonwebtoken: 9.0.2 jwks-rsa: 3.0.1 node-forge: 1.3.1 uuid: 9.0.0 @@ -6065,9 +6065,9 @@ packages: hasBin: true dev: false - /jsonwebtoken@8.5.1: - resolution: {integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==} - engines: {node: '>=4', npm: '>=1.4.28'} + /jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} dependencies: jws: 3.2.2 lodash.includes: 4.3.0 @@ -6078,16 +6078,6 @@ packages: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 5.7.2 - dev: false - - /jsonwebtoken@9.0.1: - resolution: {integrity: sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==} - engines: {node: '>=12', npm: '>=6'} - dependencies: - jws: 3.2.2 - lodash: 4.17.21 - ms: 2.1.3 semver: 7.5.4 dev: false diff --git a/test/modules/auths/jwt.js b/test/modules/auths/jwt.js new file mode 100644 index 00000000..bac7edfa --- /dev/null +++ b/test/modules/auths/jwt.js @@ -0,0 +1,29 @@ +const { expect } = require("chai"); +const { sign, verify } = require("../../../src/modules/auths/jwt"); + +// jwt.js 관련 2개의 함수를 테스트 +// 1. jwt 서명과 검증이 성공적으로 되는지 테스트 +describe("[jwt] 1.sign & verify", () => { + it("should sign and verify jwt correctly", async () => { + // JWT 서명에 사용되는 사용자 + const user = { + _id: "507f191e810c19729de860ea", + }; + + // 토큰 생성이 성공적으로 되는지 테스트 + const { token: accessToken } = await sign({ + id: user._id, + type: "access", + }); + const { token: refreshToken } = await sign({ + id: user._id, + type: "refresh", + }); + + // 토큰 검증이 성공적으로 되는지 테스트 + const accessTokenStatus = await verify(accessToken); + expect(accessTokenStatus).to.has.property("id", user._id); + const refreshTokenStatus = await verify(refreshToken); + expect(refreshTokenStatus).to.has.property("id", user._id); + }); +}); From b9fa44315e80ca0e3e7b1f894b07ac08817aeba1 Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 7 Oct 2023 23:04:14 +0900 Subject: [PATCH 252/261] Docs: add issue link to fixme --- loadenv.js | 1 + 1 file changed, 1 insertion(+) diff --git a/loadenv.js b/loadenv.js index 12634228..a0954d4d 100644 --- a/loadenv.js +++ b/loadenv.js @@ -26,6 +26,7 @@ module.exports = { option: { algorithm: "HS256", // FIXME: remove FRONT_URL from issuer. 단, issuer를 변경하면 이전에 발급했던 모든 JWT가 무효화됩니다. + // See https://github.com/sparcs-kaist/taxi-back/issues/415 issuer: process.env.FRONT_URL || "http://localhost:3000", // optional (default = "http://localhost:3000") }, TOKEN_EXPIRED: -3, From 8bda357273d40fea0abb7f24a760f6b8d8d43c6e Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 7 Oct 2023 23:25:11 +0900 Subject: [PATCH 253/261] Fix: remove wrong step name in CI workflow --- .github/workflows/test_ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_ci.yml b/.github/workflows/test_ci.yml index 83d4c2e7..29f7f21b 100644 --- a/.github/workflows/test_ci.yml +++ b/.github/workflows/test_ci.yml @@ -17,15 +17,14 @@ jobs: steps: - name: Start MongoDB run: sudo docker run --name mongodb -d -p 27017:27017 mongo:${{ matrix.mongodb-version }} - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/checkout@v3 + - uses: actions/checkout@v3 with: submodules: true - name: Install pnpm uses: pnpm/action-setup@v2 with: version: 8 - - name: Install Node.js + - name: Install Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} From e55481cb8c830411b96add7239ce230757dc8bbe Mon Sep 17 00:00:00 2001 From: withsang Date: Sat, 7 Oct 2023 23:49:05 +0900 Subject: [PATCH 254/261] Docs: update Node.js version in README --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index fbd01b8a..26cda418 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ Taxi는 KAIST 구성원들의 택시 동승 인원 모집을 위한 서비스입 - Notion : [Sparcs Notion Taxi page](https://www.notion.so/sparcs/Taxi-9d371e8ac5ac4f0c9b9c35869682a0eb) (Only SPARCS members can access it) - Slack : #taxi-main, #taxi-notice, #taxi-bug-report, #taxi-github-bot, #taxi-notion-bot (Only SPARCS members can access it) -## Prerequisites -- Recommended npm version : 8.5.5 (with node v.16.15.0) -- Recommended mognoDB version : 5.0.8 -- [Issue with node version](https://github.com/sparcs-kaist/taxi-front/issues/76) +## Prerequisite + +- Recommended node version : >=18.0.0 (Node v18.18.0, for example) +- Recommended pnpm version : >=8.0.0 (pmpm v8.8.0, for example) +- Recommended mongoDB version : 5.0.8 ## Project Setup @@ -24,7 +25,7 @@ $ git clone https://github.com/sparcs-kaist/taxi-back ### Install Requirements ```bash -$ npm install --save +$ pnpm install ``` ### Set Environment Configuration @@ -32,7 +33,9 @@ See [notion page](https://www.notion.so/sparcs/Environment-Variables-1b404bd385f Refer to [.env.example](.env.example) and write your own `.env`. ## Backend Route Information -See [Backend Route Documentation](src/routes/docs/README.md) +API specification is defined on Swagger. +Start development server and visit `/docs` to see the specification of each endpoint. +Some endpoints are not documented in Swagger yet. For those endpoints, refer to [routes/docs/README.md](./src/routes/docs/README.md). ## License This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details From 916856c471931f41d3d79f5c95197a6c1edc8527 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 9 Oct 2023 20:39:20 +0900 Subject: [PATCH 255/261] Fix: loadenv.js --- loadenv.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loadenv.js b/loadenv.js index 3dfc5be6..d8a3be78 100644 --- a/loadenv.js +++ b/loadenv.js @@ -42,6 +42,6 @@ module.exports = { JSON.parse(process.env.EVENT_CONFIG)) || { mode: "2023fall", startAt: "2023-09-25T00:00:00+09:00", - endAt: "2023-10-10T00:00:00+09:00", + endAt: "2023-10-12T00:00:00+09:00", }, }; From 7a59b033965c4e0270ac110391cd20c5ec25f56f Mon Sep 17 00:00:00 2001 From: withsang Date: Mon, 9 Oct 2023 21:38:38 +0900 Subject: [PATCH 256/261] Fix: specify push: true in Docker Action --- .github/workflows/push_image_ecr.yml | 16 +++------------- .github/workflows/push_image_ecr_dev.yml | 14 +++----------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/.github/workflows/push_image_ecr.yml b/.github/workflows/push_image_ecr.yml index 232535a1..6b3ce161 100644 --- a/.github/workflows/push_image_ecr.yml +++ b/.github/workflows/push_image_ecr.yml @@ -58,31 +58,21 @@ jobs: id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - - name: Build Image - id: build_image + - name: Build Image and Push to AWS ECR + id: build_image_and_push uses: docker/build-push-action@v5 env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ steps.tag.outputs.tag }} ECR_REPOSITORY: taxi-back with: + push: true tags: | "${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}" "${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:latest" cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache-new - - name: Push to AWS ECR - id: push_image - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - IMAGE_TAG: ${{ steps.tag.outputs.tag }} - ECR_REPOSITORY: taxi-back - run: | - docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG - docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest - echo "Push image : $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG and latest" - - name: Remove old cache run: | rm -rf /tmp/.buildx-cache diff --git a/.github/workflows/push_image_ecr_dev.yml b/.github/workflows/push_image_ecr_dev.yml index 701bfa7d..d1c1dae4 100644 --- a/.github/workflows/push_image_ecr_dev.yml +++ b/.github/workflows/push_image_ecr_dev.yml @@ -43,25 +43,17 @@ jobs: id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - - name: Build Image - id: build_image + - name: Build Image and Push to AWS ECR + id: build_image_and_push uses: docker/build-push-action@v5 env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY: taxi-back with: + push: true tags: "${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:dev" cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache-new - - - name: Push to AWS ECR - id: push_image - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - ECR_REPOSITORY: taxi-back - run: | - docker push $ECR_REGISTRY/$ECR_REPOSITORY:dev - echo "Push image : $ECR_REGISTRY/$ECR_REPOSITORY:dev" - name: Remove old cache run: | From fd3c8235e571f43025fb4eac3ed29829ef68ed85 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 24 Oct 2023 21:27:55 +0900 Subject: [PATCH 257/261] Remove: default eventConfig value --- loadenv.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/loadenv.js b/loadenv.js index 02843dc5..ce0d9dc4 100644 --- a/loadenv.js +++ b/loadenv.js @@ -40,10 +40,5 @@ module.exports = { slackWebhookUrl: { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, - eventConfig: (process.env.EVENT_CONFIG && - JSON.parse(process.env.EVENT_CONFIG)) || { - mode: "2023fall", - startAt: "2023-09-25T00:00:00+09:00", - endAt: "2023-10-12T00:00:00+09:00", - }, + eventConfig: (process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG)) }; From 3f56ed023a07b2e92b91006740d5e3522c422de5 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Thu, 26 Oct 2023 23:57:25 +0900 Subject: [PATCH 258/261] Add: reset user nickname and reset user profile image --- src/routes/users.js | 6 ++++++ src/services/auth.js | 2 +- src/services/users.js | 45 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/routes/users.js b/src/routes/users.js index f7736cd6..81ff7f03 100755 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -31,6 +31,9 @@ router.post( userHandlers.editNicknameHandler ); +// 넥네임을 기본값으로 재설정합니다. +router.get("/resetNickname", userHandlers.resetNicknameHandler); + // 새 계좌번호를 받아 로그인된 유저의 계좌번호를 변경합니다. router.post( "/editAccount", @@ -50,4 +53,7 @@ router.post( // 프로필 이미지가 S3에 정상적으로 업로드가 되었는지 확인합니다. router.get("/editProfileImg/done", userHandlers.editProfileImgDoneHandler); +// 프로필 이미지를 기본값으로 재설정합니다. +router.get("/resetProfileImg", userHandlers.resetProfileImg); + module.exports = router; diff --git a/src/services/auth.js b/src/services/auth.js index 83b598de..740a7aab 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -43,7 +43,7 @@ const joinus = async (req, userData) => { const newUser = new userModel({ id: userData.id, name: userData.name, - nickname: generateNickname(userData.id), + nickname: userData.id, profileImageUrl: generateProfileImageUrl(), joinat: req.timestamp, subinfo: { diff --git a/src/services/users.js b/src/services/users.js index f4a00d6a..82eb6129 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -4,6 +4,10 @@ const aws = require("../modules/stores/aws"); // 이벤트 코드입니다. const { contracts } = require("../lottery"); +const { + generateNickname, + generateProfileImageUrl, +} = require("../modules/modifyProfile"); const agreeOnTermsOfServiceHandler = async (req, res) => { try { @@ -151,6 +155,45 @@ const editProfileImgDoneHandler = async (req, res) => { } }; +const resetNicknameHandler = async (req, res) => { + try { + const result = await userModel.findOneAndUpdate( + { id: req.userId }, + { nickname: generateNickname(req.body.id) }, + { new: true } + ); + if (result) { + res + .status(200) + .send("User/resetNickname : reset user nickname successful"); + } else { + res.status(400).send("User/resetNickname : such user does not exist"); + } + } catch (err) { + logger.error(err); + res.status(500).send("User/resetNickname : internal server error"); + } +}; + +const resetProfileImg = async (req, res) => { + try { + const result = await userModel.findOneAndUpdate( + { id: req.userId }, + { profileImageUrl: generateProfileImageUrl() }, + { new: true } + ); + if (result) { + res + .status(200) + .send("User/resetProfileImg : reset user profile image successful"); + } else { + res.status(400).send("User/resetProfileImg : such user does not exist"); + } + } catch (err) { + res.status(500).send("User/resetProfileImg : internal server error"); + } +}; + module.exports = { agreeOnTermsOfServiceHandler, getAgreeOnTermsOfServiceHandler, @@ -158,4 +201,6 @@ module.exports = { editAccountHandler, editProfileImgGetPUrlHandler, editProfileImgDoneHandler, + resetNicknameHandler, + resetProfileImg, }; From e1aefcb48201c99527bad748778aa040bb2505c3 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Fri, 27 Oct 2023 01:39:39 +0900 Subject: [PATCH 259/261] Add: swagger docs users --- src/routes/docs/swaggerDocs.js | 6 +++ src/routes/docs/users.js | 68 ++++++++++++++++++++++++++++++++++ src/routes/users.js | 2 +- src/services/users.js | 4 +- 4 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 src/routes/docs/users.js diff --git a/src/routes/docs/swaggerDocs.js b/src/routes/docs/swaggerDocs.js index cf52ff2a..c6c13060 100644 --- a/src/routes/docs/swaggerDocs.js +++ b/src/routes/docs/swaggerDocs.js @@ -2,6 +2,7 @@ const reportsSchema = require("./reportsSchema"); const reportsDocs = require("./reports"); const logininfoDocs = require("./logininfo"); const locationsDocs = require("./locations"); +const usersDocs = require("./users"); const swaggerDocs = { openapi: "3.0.3", @@ -23,6 +24,10 @@ const swaggerDocs = { name: "reports", description: "사용자 신고 및 신고 기록 조회", }, + { + name: "users", + description: "유저 계정 정보 수정 및 조회", + }, ], consumes: ["application/json"], produces: ["application/json"], @@ -30,6 +35,7 @@ const swaggerDocs = { ...reportsDocs, ...logininfoDocs, ...locationsDocs, + ...usersDocs, }, components: { schemas: { diff --git a/src/routes/docs/users.js b/src/routes/docs/users.js new file mode 100644 index 00000000..c40e08f2 --- /dev/null +++ b/src/routes/docs/users.js @@ -0,0 +1,68 @@ +const tag = "users"; +const apiPrefix = "/users"; + +const usersDocs = {}; +usersDocs[`${apiPrefix}/resetNickname`] = { + get: { + tags: [`${tag}`], + summary: "유저 닉네임 기본값으로 재설정", + description: "유저의 별명을 기본값(랜덤한 닉네임)으로 초기화합니다", + responses: { + 200: { + content: { + "text/html": { + example: "User/resetNickname : reset user nickname successful", + }, + }, + }, + 400: { + content: { + "text/html": { + example: "User/resetNickname : such user does not exist", + }, + }, + }, + 500: { + content: { + "text/html": { + example: "User/resetNickname : internal server error", + }, + }, + }, + }, + }, +}; + +usersDocs[`${apiPrefix}/resetProfileImg`] = { + get: { + tags: [`${tag}`], + summary: "유저 프로필 사진 기본값으로 재설정", + description: "유저의 프로필 사진을 기본값(랜덤한 사진)으로 초기화합니다", + responses: { + 200: { + content: { + "text/html": { + example: + "User/resetProfileImg : reset user profile image successful", + }, + }, + }, + 400: { + content: { + "text/html": { + example: "User/resetProfileImg : such user does not exist", + }, + }, + }, + 500: { + content: { + "text/html": { + example: "User/resetProfileImg : internal server error", + }, + }, + }, + }, + }, +}; + +module.exports = usersDocs; diff --git a/src/routes/users.js b/src/routes/users.js index 81ff7f03..31bde597 100755 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -54,6 +54,6 @@ router.post( router.get("/editProfileImg/done", userHandlers.editProfileImgDoneHandler); // 프로필 이미지를 기본값으로 재설정합니다. -router.get("/resetProfileImg", userHandlers.resetProfileImg); +router.get("/resetProfileImg", userHandlers.resetProfileImgHandler); module.exports = router; diff --git a/src/services/users.js b/src/services/users.js index 82eb6129..06c580c4 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -175,7 +175,7 @@ const resetNicknameHandler = async (req, res) => { } }; -const resetProfileImg = async (req, res) => { +const resetProfileImgHandler = async (req, res) => { try { const result = await userModel.findOneAndUpdate( { id: req.userId }, @@ -202,5 +202,5 @@ module.exports = { editProfileImgGetPUrlHandler, editProfileImgDoneHandler, resetNicknameHandler, - resetProfileImg, + resetProfileImgHandler, }; From 7ba86763ccbbdc6a634080a038e173da2cf248fa Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Fri, 27 Oct 2023 01:52:28 +0900 Subject: [PATCH 260/261] Fix: auth nickname generation --- src/services/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/auth.js b/src/services/auth.js index 740a7aab..83b598de 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -43,7 +43,7 @@ const joinus = async (req, userData) => { const newUser = new userModel({ id: userData.id, name: userData.name, - nickname: userData.id, + nickname: generateNickname(userData.id), profileImageUrl: generateProfileImageUrl(), joinat: req.timestamp, subinfo: { From c234823fe70e54b3cd7ab61b2434f739f1d2fc6a Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Fri, 27 Oct 2023 02:46:04 +0900 Subject: [PATCH 261/261] Refactor: early return pattern --- src/routes/docs/users.js | 4 ++-- src/services/users.js | 26 ++++++++++++-------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/routes/docs/users.js b/src/routes/docs/users.js index c40e08f2..e76cd2b4 100644 --- a/src/routes/docs/users.js +++ b/src/routes/docs/users.js @@ -4,7 +4,7 @@ const apiPrefix = "/users"; const usersDocs = {}; usersDocs[`${apiPrefix}/resetNickname`] = { get: { - tags: [`${tag}`], + tags: [tag], summary: "유저 닉네임 기본값으로 재설정", description: "유저의 별명을 기본값(랜덤한 닉네임)으로 초기화합니다", responses: { @@ -35,7 +35,7 @@ usersDocs[`${apiPrefix}/resetNickname`] = { usersDocs[`${apiPrefix}/resetProfileImg`] = { get: { - tags: [`${tag}`], + tags: [tag], summary: "유저 프로필 사진 기본값으로 재설정", description: "유저의 프로필 사진을 기본값(랜덤한 사진)으로 초기화합니다", responses: { diff --git a/src/services/users.js b/src/services/users.js index 06c580c4..3c26b164 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -162,13 +162,11 @@ const resetNicknameHandler = async (req, res) => { { nickname: generateNickname(req.body.id) }, { new: true } ); - if (result) { - res - .status(200) - .send("User/resetNickname : reset user nickname successful"); - } else { - res.status(400).send("User/resetNickname : such user does not exist"); - } + if (!result) + return res + .status(400) + .send("User/resetNickname : such user does not exist"); + res.status(200).send("User/resetNickname : reset user nickname successful"); } catch (err) { logger.error(err); res.status(500).send("User/resetNickname : internal server error"); @@ -182,13 +180,13 @@ const resetProfileImgHandler = async (req, res) => { { profileImageUrl: generateProfileImageUrl() }, { new: true } ); - if (result) { - res - .status(200) - .send("User/resetProfileImg : reset user profile image successful"); - } else { - res.status(400).send("User/resetProfileImg : such user does not exist"); - } + if (!result) + return res + .status(400) + .send("User/resetProfileImg : such user does not exist"); + res + .status(200) + .send("User/resetProfileImg : reset user profile image successful"); } catch (err) { res.status(500).send("User/resetProfileImg : internal server error"); }