From b679c036caf8c5e72dd05c05e4cc16287f6381d1 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 12 Aug 2024 21:40:51 +0900 Subject: [PATCH 01/25] Add: create/join feature restriction --- src/modules/ban.js | 33 +++++++++++++++++++++++++++++++++ src/services/rooms.js | 26 ++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/modules/ban.js diff --git a/src/modules/ban.js b/src/modules/ban.js new file mode 100644 index 00000000..3e57289a --- /dev/null +++ b/src/modules/ban.js @@ -0,0 +1,33 @@ +const logger = require("./logger"); +const { banModel } = require("./stores/mongo"); + +const getMaxValidServiceBanRecord = async (req) => { + try { + // 현재 시각이 expireAt 보다 작고, 본인인 경우(ban의 userId가 userOid랑 같은 경우) 중 serviceName이 "service"인 record를 모두 가져옴 + const bans = await banModel.find({ + userId: req.userOid, + expireAt: { + $gte: req.timestamp, + }, + "services.serviceName": "service", + }); + if (bans.length > 0) { + // 가장 expireAt이 큰 정지 기록만 반환함. + const latestBan = bans.reduce( + (max, ban) => (ban.expireAt > max.expireAt ? ban : max), + bans[0] + ); + return latestBan; + } + return; + } catch (err) { + logger.error( + "Error occured while getValidServiceBanRecord: " + err.message + ); + return; + } +}; + +module.exports = { + getMaxValidServiceBanRecord, +}; diff --git a/src/services/rooms.js b/src/services/rooms.js index 218a23d5..5029b2a2 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -2,6 +2,7 @@ const { roomModel, locationModel, userModel, + banModel, } = require("../modules/stores/mongo"); const { emitChatEvent } = require("../modules/socket"); const logger = require("../modules/logger"); @@ -21,11 +22,24 @@ const eventPeriod = eventConfig && { endAt: new Date(eventConfig.period.endAt), }; const { contracts } = require("../lottery"); +const { getMaxValidServiceBanRecord } = require("../modules/ban"); const createHandler = async (req, res) => { const { name, from, to, time, maxPartLength } = req.body; try { + // 사용자가 방 생성 기능에 대하여 이용 정지 상태인지 확인합니다. + const banRecord = await getMaxValidServiceBanRecord(req); + if (banRecord != undefined) { + const formattedExpireAt = banRecord.expireAt + .toISOString() + .replace("T", " ") + .split(".")[0]; + return res.status(400).json({ + error: `Rooms/create : user ${req.userId} is temporarily restricted from creating rooms until ${formattedExpireAt}.`, + }); + } + if (from === to) { return res.status(400).json({ error: "Rooms/create : locations are same", @@ -237,6 +251,18 @@ const joinHandler = async (req, res) => { .findOne({ id: req.userId }) .populate("ongoingRoom"); + // 사용자가 방 참여 기능에 대하여 이용 정지 상태인지 확인합니다. + const banRecord = await getMaxValidServiceBanRecord(req); + if (banRecord != undefined) { + const formattedExpireAt = banRecord.expireAt + .toISOString() + .replace("T", " ") + .split(".")[0]; + return res.status(400).json({ + error: `Rooms/join : user ${req.userId} is temporarily restricted from joining rooms until ${formattedExpireAt}.`, + }); + } + // 사용자의 참여중인 진행중인 방이 5개 이상이면 오류를 반환합니다. if (user.ongoingRoom.length >= 5) { return res.status(400).json({ From 5f4a9b976b82016be9d701c4574dc81fb440ef25 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 12 Aug 2024 21:58:55 +0900 Subject: [PATCH 02/25] Add: BanSchema --- src/modules/ban.js | 2 +- src/modules/stores/mongo.js | 25 ++++++++++--------------- src/routes/docs/users.js | 28 ++++++++-------------------- 3 files changed, 19 insertions(+), 36 deletions(-) diff --git a/src/modules/ban.js b/src/modules/ban.js index 3e57289a..3190b533 100644 --- a/src/modules/ban.js +++ b/src/modules/ban.js @@ -9,7 +9,7 @@ const getMaxValidServiceBanRecord = async (req) => { expireAt: { $gte: req.timestamp, }, - "services.serviceName": "service", + serviceName: "service", }); if (bans.length > 0) { // 가장 expireAt이 큰 정지 기록만 반환함. diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 8f837775..6469d0d9 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -42,21 +42,16 @@ const banSchema = Schema({ type: Date, // 정지 만료 시각 required: true, }, - services: [ - { - // 정지를 당한 서비스를 기제함 - serviceName: { - type: String, - required: true, - // 필요시 이곳에 정지를 시킬 서비스를 추가함. - enum: [ - "all", // all -> 과거/미래 모든 서비스 및 이벤트 이용 제한 - "service", // service -> 방 생성/참여 제한 - "2023-fall-event", // event -> 특정 이벤트 참여 제한 - ], - }, - }, - ], + // 정지를 당한 서비스를 기제함 + serviceName: { + type: String, + required: true, + // 필요시 이곳에 정지를 시킬 서비스를 추가함. + enum: [ + "service", // service: 방 생성/참여 제한 + "2023-fall-event", // xxxx-xxxx-event: 특정 이벤트 참여 제한 + ], + }, }); const participantSchema = Schema({ diff --git a/src/routes/docs/users.js b/src/routes/docs/users.js index deaeb113..a42c4f4c 100644 --- a/src/routes/docs/users.js +++ b/src/routes/docs/users.js @@ -364,16 +364,10 @@ usersDocs[`${apiPrefix}/isBanned`] = { description: "정지 만료 시각", example: "2024-05-21 12:00", }, - services: { - type: "array", - items: { - properties: { - serviceName: { - type: "string", - description: "정지를 당한 서비스 또는 이벤트 이름", - }, - }, - }, + serviceName: { + type: "string", + description: "정지를 당한 서비스 또는 이벤트 이름", + example: "2023-fall-event", }, }, }, @@ -433,16 +427,10 @@ usersDocs[`${apiPrefix}/getBanRecord`] = { description: "정지 만료 시각", example: "2024-05-21 12:00", }, - services: { - type: "array", - items: { - properties: { - serviceName: { - type: "string", - description: "정지를 당한 서비스 또는 이벤트 이름", - }, - }, - }, + serviceName: { + type: "string", + description: "정지를 당한 서비스 또는 이벤트 이름", + example: "2023-fall-event", }, }, }, From 89cc169a1a8808a5d51ab185fc3d7cd8ceac1de0 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 12 Aug 2024 22:06:51 +0900 Subject: [PATCH 03/25] Add: Swagger docs --- src/routes/docs/rooms.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/routes/docs/rooms.js b/src/routes/docs/rooms.js index 3ac66e6b..0c9303a2 100644 --- a/src/routes/docs/rooms.js +++ b/src/routes/docs/rooms.js @@ -72,6 +72,12 @@ roomsDocs[`${apiPrefix}/create`] = { }, }, examples: { + "방 생성 기능이 정지당한 경우": { + value: { + error: + "Rooms/join : user monday is temporarily restricted from creating rooms until 2024-08-23 15:00:00.", + }, + }, "출발지와 도착지가 같음": { value: { error: "Rooms/create : locations are same", @@ -309,6 +315,12 @@ roomsDocs[`${apiPrefix}/join`] = { }, }, examples: { + "방 참여 기능이 정지당한 경우": { + value: { + error: + "Rooms/join : user monday is temporarily restricted from joining rooms until 2024-08-23 15:00:00.", + }, + }, "사용자가 참여하는 진행 중 방이 5개 이상": { value: { error: "Rooms/join : participating in too many rooms", From 2c280c88758da41ead26e519e7045113ede4b31a Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 12 Aug 2024 22:14:01 +0900 Subject: [PATCH 04/25] Refactor: unnecessary code deletion --- src/services/rooms.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/rooms.js b/src/services/rooms.js index 5029b2a2..e9beb5fb 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -2,7 +2,6 @@ const { roomModel, locationModel, userModel, - banModel, } = require("../modules/stores/mongo"); const { emitChatEvent } = require("../modules/socket"); const logger = require("../modules/logger"); From 1f52f5c5f8fd008bc8013f6f7fc029056df31c86 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 2 Sep 2024 16:21:17 +0900 Subject: [PATCH 05/25] Add: ban user by sso id --- src/modules/ban.js | 4 ++-- src/modules/stores/mongo.js | 17 ++++------------- src/routes/docs/users.js | 8 ++++---- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/modules/ban.js b/src/modules/ban.js index 3190b533..290e8f86 100644 --- a/src/modules/ban.js +++ b/src/modules/ban.js @@ -3,9 +3,9 @@ const { banModel } = require("./stores/mongo"); const getMaxValidServiceBanRecord = async (req) => { try { - // 현재 시각이 expireAt 보다 작고, 본인인 경우(ban의 userId가 userOid랑 같은 경우) 중 serviceName이 "service"인 record를 모두 가져옴 + // 현재 시각이 expireAt 보다 작고, 본인인 경우(ban의 userId가 userId랑 같은 경우) 중 serviceName이 "service"인 record를 모두 가져옴 const bans = await banModel.find({ - userId: req.userOid, + userSid: req.session.loginInfo.sid, expireAt: { $gte: req.timestamp, }, diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 6469d0d9..f236b829 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -28,20 +28,11 @@ const userSchema = Schema({ const banSchema = Schema({ // 정지 시킬 사용자를 기제함. - userId: { type: mongoose.Types.ObjectId, ref: "User", required: true }, + userSid: { type: String, required: true }, // 정지 사유 - reason: { - type: String, - required: true, - }, - bannedAt: { - type: Date, // 정지 당한 시각 - required: true, - }, - expireAt: { - type: Date, // 정지 만료 시각 - required: true, - }, + reason: { type: String, required: true }, + bannedAt: { type: Date, required: true }, // 정지 당한 시각 + expireAt: { type: Date, required: true }, // 정지 만료 시각 // 정지를 당한 서비스를 기제함 serviceName: { type: String, diff --git a/src/routes/docs/users.js b/src/routes/docs/users.js index a42c4f4c..ec4532a5 100644 --- a/src/routes/docs/users.js +++ b/src/routes/docs/users.js @@ -346,8 +346,8 @@ usersDocs[`${apiPrefix}/isBanned`] = { properties: { userId: { type: "string", - description: "사용자의 ObjectId", - pattern: objectId.source, + description: "사용자의 SSO ID", + pattern: "monday-sid", }, reason: { type: "string", @@ -409,8 +409,8 @@ usersDocs[`${apiPrefix}/getBanRecord`] = { properties: { userId: { type: "string", - description: "사용자의 ObjectId", - pattern: objectId.source, + description: "사용자의 SSO ID", + example: "monday-sid", }, reason: { type: "string", From f6c49ae70e54bff9b963b7c96faef6c352cdb11c Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 2 Sep 2024 16:34:54 +0900 Subject: [PATCH 06/25] Add: pull origin dev --- loadenv.js | 13 +- src/lottery/index.js | 4 +- src/lottery/modules/contracts.js | 150 +++--- src/lottery/modules/populates/transactions.js | 5 +- src/lottery/modules/quests.js | 10 +- src/lottery/modules/stores/mongo.js | 48 +- src/lottery/routes/docs/globalState.js | 84 ++-- .../routes/docs/{invite.js => invites.js} | 41 +- src/lottery/routes/docs/items.js | 199 ++++++-- src/lottery/routes/docs/publicNotice.js | 142 +++--- src/lottery/routes/docs/quests.js | 39 +- .../routes/docs/schemas/globalStateSchema.js | 1 - .../{inviteSchema.js => invitesSchema.js} | 6 +- .../routes/docs/schemas/itemsSchema.js | 109 +---- .../routes/docs/schemas/questsSchema.js | 4 +- src/lottery/routes/docs/swaggerDocs.js | 34 +- src/lottery/routes/docs/transactions.js | 35 +- src/lottery/routes/globalState.js | 3 +- src/lottery/routes/invite.js | 20 - src/lottery/routes/invites.js | 21 + src/lottery/routes/items.js | 34 +- src/lottery/routes/publicNotice.js | 6 +- src/lottery/routes/quests.js | 9 +- src/lottery/routes/transactions.js | 2 +- src/lottery/services/globalState.js | 112 ++--- src/lottery/services/invite.js | 66 --- src/lottery/services/invites.js | 73 +++ src/lottery/services/items.js | 458 ++++++++++++------ src/lottery/services/quests.js | 26 +- src/lottery/services/transactions.js | 31 +- src/modules/fare.js | 35 +- src/sampleGenerator/sampleData.json | 4 +- src/services/fare.js | 83 +--- src/services/rooms.js | 14 +- 34 files changed, 1036 insertions(+), 885 deletions(-) rename src/lottery/routes/docs/{invite.js => invites.js} (60%) rename src/lottery/routes/docs/schemas/{inviteSchema.js => invitesSchema.js} (67%) delete mode 100644 src/lottery/routes/invite.js create mode 100644 src/lottery/routes/invites.js delete mode 100644 src/lottery/services/invite.js create mode 100644 src/lottery/services/invites.js diff --git a/loadenv.js b/loadenv.js index cbf47d88..4e589543 100644 --- a/loadenv.js +++ b/loadenv.js @@ -43,7 +43,18 @@ 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: "2024fall", + credit: { + name: "송편코인", + initialAmount: 0, + }, + period: { + startAt: new Date("2024-09-07T00:00:00+09:00"), + endAt: new Date("2024-09-24T00:00:00+09:00"), + }, + }, // optional naverMap: { apiId: process.env.NAVER_MAP_API_ID, // optional apiKey: process.env.NAVER_MAP_API_KEY, //optional diff --git a/src/lottery/index.js b/src/lottery/index.js index d485dfe1..a00e3598 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -28,10 +28,10 @@ lotteryRouter.use(require("../middlewares/originValidator")); // [Router] APIs lotteryRouter.use("/globalState", require("./routes/globalState")); -lotteryRouter.use("/invite", require("./routes/invite")); +lotteryRouter.use("/invites", require("./routes/invites")); lotteryRouter.use("/transactions", require("./routes/transactions")); lotteryRouter.use("/items", require("./routes/items")); -lotteryRouter.use("/publicNotice", require("./routes/publicNotice")); +// lotteryRouter.use("/publicNotice", require("./routes/publicNotice")); lotteryRouter.use("/quests", require("./routes/quests")); // [AdminJS] AdminJS에 표시할 Resource 생성 diff --git a/src/lottery/modules/contracts.js b/src/lottery/modules/contracts.js index 72a62deb..a415b0af 100644 --- a/src/lottery/modules/contracts.js +++ b/src/lottery/modules/contracts.js @@ -13,19 +13,10 @@ const quests = buildQuests({ firstLogin: { name: "첫 발걸음", description: - "로그인만 해도 넙죽코인을 얻을 수 있다고?? 이벤트 기간에 처음으로 SPARCS Taxi 서비스에 로그인하여 넙죽코인을 받아보세요.", + "이벤트 참여만 해도 송편코인을 얻을 수 있다고?? 이벤트 참여에 동의하고 송편코인을 받아 보세요.", imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_firstLogin.png", - reward: 50, - }, - payingAndSending: { - name: "함께하는 택시의 여정", - description: - "2명 이상과 함께 택시를 타고 정산/송금까지 완료해보세요. 최대 3번까지 넙죽코인을 받을 수 있어요. 정산/송금 버튼은 채팅 페이지 좌측 하단의 +버튼을 눌러 확인할 수 있어요.", - imageUrl: - "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_payingAndSending.png", - reward: 150, - maxCount: 0, + reward: 200, }, firstRoomCreation: { name: "첫 방 개설", @@ -33,33 +24,33 @@ const quests = buildQuests({ "원하는 택시팟을 찾을 수 없다면? 원하는 조건으로 방 개설 페이지에서 방을 직접 개설해보세요.", imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_firstRoomCreation.png", - reward: 50, + reward: 500, }, roomSharing: { - name: "너 T야? Taxi", + name: "이 택시팟은 진짜 유명한 택시팟임", description: - "방을 공유해 친구들을 택시에 초대해보세요. 채팅창 상단의 햄버거(☰) 버튼을 누르면 공유하기 버튼을 찾을 수 있어요.", + "방을 공유해 친구들을 택시팟에 초대해 보세요. 채팅창 상단의 햄버거(☰) 버튼을 누르면 공유하기 버튼을 찾을 수 있어요.", imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_roomSharing.png", - reward: 50, + reward: 500, isApiRequired: true, }, - paying: { - name: "정산해요 택시의 숲", + fareSettlement: { + name: "정산의 신, 신팍스", description: - "2명 이상과 함께 택시를 타고 택시비를 결제한 후 정산하기를 요청해보세요. 정산하기 버튼은 채팅 페이지 좌측 하단의 +버튼을 눌러 확인할 수 있어요.", + "2명 이상과 함께 택시를 타고 택시비를 결제한 후 정산을 요청해 보세요. 정산하기 버튼은 채팅 페이지 좌측 하단의 + 버튼을 눌러 찾을 수 있어요.", imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_paying.png", - reward: 100, + reward: 2000, maxCount: 0, }, - sending: { + farePayment: { name: "송금 완료면 I am 신뢰에요", description: "2명 이상과 함께 택시를 타고 택시비를 결제한 분께 송금해주세요. 송금하기 버튼은 채팅 페이지 좌측 하단의 +버튼을 눌러 확인할 수 있어요.", imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_sending.png", - reward: 50, + reward: 2000, maxCount: 0, }, nicknameChanging: { @@ -68,7 +59,7 @@ const quests = buildQuests({ "닉네임을 변경하여 자신을 표현하세요. 마이페이지수정하기 버튼을 눌러 닉네임을 수정할 수 있어요.", imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_nicknameChanging.png", - reward: 50, + reward: 500, }, accountChanging: { name: "계좌 등록을 해야 능률이 올라갑니다", @@ -76,7 +67,7 @@ const quests = buildQuests({ "정산하기 기능을 더욱 빠르고 이용할 수 있다고? 계좌번호를 등록하면 정산하기를 할 때 계좌가 자동으로 입력돼요. 마이페이지수정하기 버튼을 눌러 계좌번호를 등록 또는 수정할 수 있어요.", imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_accountChanging.png", - reward: 50, + reward: 500, }, adPushAgreement: { name: "Taxi의 소울메이트", @@ -84,25 +75,30 @@ const quests = buildQuests({ "Taxi 서비스를 잊지 않도록 가끔 찾아갈게요! 광고성 푸시 알림 수신 동의를 해주시면 방이 많이 모이는 시즌, 주변에 택시앱 사용자가 있을 때 알려드릴 수 있어요.", imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_adPushAgreement.png", - reward: 50, + reward: 500, }, eventSharing: { - name: "너 나랑 ㅌ태태택 (1명)", - description: - "내가 초대한 사람이 Taxi에 가입하여 이벤트에 참여하면 넙죽코인을 드려요. 내가 초대한 사람도 넙죽코인을 받아요. 이벤트 안내 페이지의 이벤트 공유하기 버튼을 통해 카카오톡으로 초대 문자를 보낼 수 있어요!", + name: "Taxi를 아십니까", + description: "내가 초대한 사람이 이벤트에 참여하면 송편코인을 드려요.", imageUrl: "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_eventSharing.png", - reward: 50, + reward: 700, maxCount: 0, }, - eventSharing5: { - name: "너 나랑 ㅌ태태택 (5명)", + dailyAttendance: { + name: "하루 한 번 Taxi!", description: - "내가 초대한 사람이 5명이 Taxi에 가입하여 이벤트에 참여하면 넙죽코인을 드려요. 내가 초대한 사람도 넙죽코인을 받아요. 이벤트 안내 페이지의 이벤트 공유하기 버튼을 통해 카카오톡으로 초대 문자를 보낼 수 있어요!", - imageUrl: - "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_eventSharing.png", - reward: 250, - maxCount: 0, + "매일 Taxi에 접속하여 출석 체크를 하면 송편코인을 드려요! 하루에 한 번, 택시팟도 둘러보고 송편코인도 받아 가세요. 송편코인을 얻으려면 출석 체크 페이지에서 출석 버튼을 눌러야 해요.", + imageUrl: "", + reward: 700, + maxCount: 17, + isApiRequired: true, + }, + itemPurchase: { + name: "itemPurchase", + description: "itemPurchase", + imageUrl: "", + reward: 500, }, }); @@ -111,40 +107,12 @@ const quests = buildQuests({ * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @returns {Promise} - * @usage lottery/globalState/createUserGlobalStateHandler + * @usage lottery/globalState - createUserGlobalStateHandler */ const completeFirstLoginQuest = async (userId, timestamp) => { return await completeQuest(userId, timestamp, quests.firstLogin); }; -/** - * payingAndSending 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. - * @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 - commitSettlementHandler, rooms - commitPaymentHandler - */ -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 ( - !eventPeriod || - roomObject.time >= eventPeriod.endAt || - roomObject.time < eventPeriod.startAt - ) - return null; // 택시 출발 시각이 이벤트 기간 내에 포함되지 않는 경우 퀘스트 완료 요청을 하지 않습니다. - - return await completeQuest(userId, timestamp, quests.payingAndSending); -}; - /** * firstRoomCreation 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. @@ -158,7 +126,7 @@ const completeFirstRoomCreationQuest = async (userId, timestamp) => { }; /** - * paying 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. + * fareSettlement 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {Object} roomObject - 방의 정보입니다. @@ -169,9 +137,9 @@ const completeFirstRoomCreationQuest = async (userId, timestamp) => { * @description 정산 요청이 이루어질 때마다 호출해 주세요. * @usage rooms - commitSettlementHandler */ -const completePayingQuest = async (userId, timestamp, roomObject) => { +const completeFareSettlementQuest = async (userId, timestamp, roomObject) => { logger.info( - `User ${userId} requested to complete payingQuest in Room ${roomObject._id}` + `User ${userId} requested to complete fareSettlementQuest in Room ${roomObject._id}` ); if (roomObject.part.length < 2) return null; @@ -182,11 +150,11 @@ const completePayingQuest = async (userId, timestamp, roomObject) => { ) return null; // 택시 출발 시각이 이벤트 기간 내에 포함되지 않는 경우 퀘스트 완료 요청을 하지 않습니다. - return await completeQuest(userId, timestamp, quests.paying); + return await completeQuest(userId, timestamp, quests.fareSettlement); }; /** - * sending 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. + * farePayment 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {Object} roomObject - 방의 정보입니다. @@ -197,9 +165,9 @@ const completePayingQuest = async (userId, timestamp, roomObject) => { * @description 송금이 이루어질 때마다 호출해 주세요. * @usage rooms - commitPaymentHandler */ -const completeSendingQuest = async (userId, timestamp, roomObject) => { +const completeFarePaymentQuest = async (userId, timestamp, roomObject) => { logger.info( - `User ${userId} requested to complete sendingQuest in Room ${roomObject._id}` + `User ${userId} requested to complete farePaymentQuest in Room ${roomObject._id}` ); if (roomObject.part.length < 2) return null; @@ -210,7 +178,7 @@ const completeSendingQuest = async (userId, timestamp, roomObject) => { ) return null; // 택시 출발 시각이 이벤트 기간 내에 포함되지 않는 경우 퀘스트 완료 요청을 하지 않습니다. - return await completeQuest(userId, timestamp, quests.sending); + return await completeQuest(userId, timestamp, quests.farePayment); }; /** @@ -241,13 +209,13 @@ const completeAccountChangingQuest = async (userId, timestamp, newAccount) => { }; /** - * adPushAgreementQuest 퀘스트의 완료를 요청합니다. + * adPushAgreement 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @param {boolean} advertisement - 변경된 광고성 알림 수신 동의 여부입니다. * @returns {Promise} * @description 알림 옵션을 변경할 때마다 호출해 주세요. - * @usage notifications/editOptionsHandler + * @usage notifications - editOptionsHandler */ const completeAdPushAgreementQuest = async ( userId, @@ -260,38 +228,36 @@ const completeAdPushAgreementQuest = async ( }; /** - * eventSharing, eventSharing5 퀘스트의 완료를 요청합니다. + * eventSharing 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. * @returns {Promise} - * @description 초대 링크를 통해 사용자가 이벤트에 참여할 때마다, 초대한 사용자 및 초대받은 사용자에 대해 각각 호출해 주세요. + * @usage lottery/globalState - createUserGlobalStateHandler */ const completeEventSharingQuest = async (userId, timestamp) => { - const eventSharingResult = await completeQuest( - userId, - timestamp, - quests.eventSharing - ); - if (!eventSharingResult || eventSharingResult.questCount % 5 !== 0) - return [eventSharingResult, null]; + return await completeQuest(userId, timestamp, quests.eventSharing); +}; - const eventSharing5Result = await completeQuest( - userId, - timestamp, - quests.eventSharing5 - ); - return [eventSharingResult, eventSharing5Result]; +/** + * itemPurchase 퀘스트의 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다. + * @returns {Promise} + * @description 상품을 구입할 때마다 호출해 주세요. + */ +const completeItemPurchaseQuest = async (userId, timestamp) => { + return await completeQuest(userId, timestamp, quests.itemPurchase); }; module.exports = { quests, completeFirstLoginQuest, - completePayingAndSendingQuest, completeFirstRoomCreationQuest, - completePayingQuest, - completeSendingQuest, + completeFareSettlementQuest, + completeFarePaymentQuest, completeNicknameChangingQuest, completeAccountChangingQuest, completeAdPushAgreementQuest, completeEventSharingQuest, + completeItemPurchaseQuest, }; diff --git a/src/lottery/modules/populates/transactions.js b/src/lottery/modules/populates/transactions.js index 6d965258..a09428c5 100644 --- a/src/lottery/modules/populates/transactions.js +++ b/src/lottery/modules/populates/transactions.js @@ -1,8 +1,7 @@ const transactionPopulateOption = [ { - path: "item", - select: - "name imageUrl instagramStoryStickerImageUrl price description isDisabled stock itemType", + path: "itemId", + select: "name imageUrl", }, ]; diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index 04c6cd4c..0d79ac81 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -14,6 +14,7 @@ const eventPeriod = eventConfig && { }; const requiredQuestFields = ["name", "description", "imageUrl", "reward"]; + const buildQuests = (quests) => { for (const [id, quest] of Object.entries(quests)) { // quest에 필수 필드가 모두 포함되어 있는지 확인합니다. @@ -61,7 +62,7 @@ const buildQuests = (quests) => { * @param {number} quest.reward.credit - 퀘스트의 완료 보상 중 재화의 양입니다. * @param {number} quest.reward.ticket1 - 퀘스트의 완료 보상 중 일반 티켓의 개수입니다. * @param {number} quest.maxCount - 퀘스트의 최대 완료 가능 횟수입니다. - * @returns {Object|null} 성공한 경우 Object를, 실패한 경우 null을 반환합니다. 이미 최대 완료 횟수에 도달했거나, 퀘스트가 원격으로 비활성화 된 경우에도 실패로 처리됩니다. + * @returns {Object|null} 성공한 경우 Object를, 실패한 경우 null을 반환합니다. 이미 최대 완료 횟수에 도달했거나, 퀘스트가 원격으로 비활성화된 경우에도 실패로 처리됩니다. */ const completeQuest = async (userId, timestamp, quest) => { try { @@ -118,7 +119,10 @@ const completeQuest = async (userId, timestamp, quest) => { ticket1Amount: quest.reward.ticket1, }, $push: { - completedQuests: quest.id, + completedQuests: { + questId: quest.id, + completedAt: timestamp, + }, }, } ); @@ -143,7 +147,7 @@ const completeQuest = async (userId, timestamp, quest) => { amount: 0, userId, questId: quest.id, - item: ticket1._id, + itemId: ticket1._id, comment: `"${quest.name}" 퀘스트를 완료해 "${ticket1.name}" ${quest.reward.ticket1}개를 획득했습니다.`, }); await transaction.save(); diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 600c99ec..09b6c80e 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -10,6 +10,17 @@ const integerValidator = { message: "{VALUE} is not an integer value", }; +const completedQuestSchema = Schema({ + questId: { + type: String, + required: true, + }, + completedAt: { + type: Date, + required: true, + }, +}); + const eventStatusSchema = Schema({ userId: { type: Schema.Types.ObjectId, @@ -17,7 +28,7 @@ const eventStatusSchema = Schema({ required: true, }, completedQuests: { - type: [String], + type: [completedQuestSchema], default: [], }, creditAmount: { @@ -42,17 +53,11 @@ const eventStatusSchema = Schema({ type: Boolean, default: false, }, - group: { - type: Number, - required: true, - min: 1, - validate: integerValidator, - }, // 소속된 새터반 inviter: { type: Schema.Types.ObjectId, ref: "User", }, // 이 사용자를 초대한 사용자 - isEnabledInviteUrl: { + isInviteUrlEnabled: { type: Boolean, default: false, }, // 초대 링크 활성화 여부 @@ -101,7 +106,13 @@ const itemSchema = Schema({ required: true, min: 0, validate: integerValidator, - }, + }, // 의미 없는 값, 기존 코드와의 호환성을 위해 남겨둡니다. + realStock: { + type: Number, + required: true, + min: 1, + validate: integerValidator, + }, // 상품의 실제 재고 itemType: { type: Number, enum: [0, 1, 2, 3], @@ -124,13 +135,13 @@ const transactionSchema = Schema({ type: String, enum: ["get", "use"], required: true, - }, + }, // get: 재화 획득, use: 재화 사용 amount: { type: Number, required: true, min: 0, validate: integerValidator, - }, + }, // 재화의 변화량의 절댓값 userId: { type: Schema.Types.ObjectId, ref: "User", @@ -138,22 +149,23 @@ const transactionSchema = Schema({ }, questId: { type: String, - }, - item: { + }, // 완료한 퀘스트의 ID + itemId: { type: Schema.Types.ObjectId, ref: `${modelNamePrefix}Item`, - }, - itemType: { + }, // 획득한 상품의 ID + itemAmount: { type: Number, - enum: [0, 1, 2, 3], - }, + min: 1, + validate: integerValidator, + }, // 획득한 상품의 개수 comment: { type: String, required: true, }, }); transactionSchema.set("timestamps", { - createdAt: "createAt", + createdAt: "createdAt", updatedAt: false, }); diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 4af3493e..1bbf23f4 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -5,24 +5,21 @@ const globalStateDocs = {}; globalStateDocs[`${apiPrefix}/`] = { get: { tags: [`${apiPrefix}`], - summary: "Frontend에서 Global state로 관리하는 정보 반환", + summary: "Frontend에서 Global State로 관리하는 정보 반환", description: - "유저의 재화 개수, 퀘스트 완료 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다.", + "유저의 재화 개수, 퀘스트 완료 상태 등 Frontend에서 Global State로 관리하는 정보를 가져옵니다.", responses: { 200: { - description: "", content: { "application/json": { schema: { type: "object", required: [ "isAgreeOnTermsOfEvent", - "isEligible", + "isBanned", "creditAmount", - "groupCreditAmount", - "completedQuests", - "group", "quests", + "completedQuests", ], properties: { isAgreeOnTermsOfEvent: { @@ -30,44 +27,19 @@ globalStateDocs[`${apiPrefix}/`] = { description: "유저의 이벤트 참여 동의 여부", example: true, }, - isEligible: { - type: "boolean", - description: "유저의 이벤트 참여 가능 여부", - example: true, - }, - creditAmount: { - type: "number", - description: "재화 개수. 0 이상입니다.", - example: 1000, - }, - groupCreditAmount: { - type: "number", - description: "소속 새터반에 소속된 유저의 전체 재화 개수", - example: 35000, - }, - completedQuests: { - type: "array", - description: - "유저가 완료한 퀘스트의 배열. 여러 번 완료할 수 있는 퀘스트의 경우 배열 내에 같은 퀘스트가 여러 번 포함됩니다.", - items: { - type: "string", - description: "Quest의 Id", - example: "QUEST ID", - }, - }, isBanned: { type: "boolean", - description: "해당 유저 제재 대상 여부", + description: "유저의 이벤트 참여 제한 여부", example: false, }, - group: { + creditAmount: { type: "number", - description: "유저의 소속 새터반", - example: 16, + description: "유저의 재화 개수. 0 이상의 정수입니다.", + example: 1000, }, quests: { type: "array", - description: "Quest의 배열", + description: "전체 퀘스트의 배열", items: { type: "object", required: [ @@ -82,7 +54,7 @@ globalStateDocs[`${apiPrefix}/`] = { properties: { id: { type: "string", - description: "Quest의 Id", + description: "퀘스트의 Id", example: "QUEST ID", }, name: { @@ -98,34 +70,54 @@ globalStateDocs[`${apiPrefix}/`] = { }, imageUrl: { type: "string", - description: "이미지 썸네일 URL", + description: "퀘스트의 썸네일 이미지 URL", example: "THUMBNAIL URL", }, reward: { type: "object", - description: "완료 보상", required: ["credit"], properties: { credit: { type: "number", - description: "완료 보상 중 재화의 개수입니다.", + description: "퀘스트의 완료 보상 중 재화의 개수", example: 100, }, }, }, maxCount: { type: "number", - description: "최대 완료 가능 횟수", + description: "퀘스트의 최대 완료 가능 횟수", example: 1, }, isApiRequired: { type: "boolean", - description: `/events/${eventConfig?.mode}/quests/complete/:questId API를 통해 퀘스트 완료를 요청할 수 있는지 여부`, + description: `/events/${eventConfig?.mode}/quests/complete/:questId API를 통해 퀘스트 완료를 요청해야 하는지의 여부`, example: false, }, }, }, }, + completedQuests: { + type: "array", + description: + "유저가 완료한 퀘스트의 배열. 여러 번 완료한 퀘스트의 경우 배열 내에 같은 퀘스트가 여러 번 포함됩니다.", + items: { + type: "object", + required: ["id", "completedAt"], + properties: { + id: { + type: "string", + description: "퀘스트의 Id", + example: "QUEST ID", + }, + completedAt: { + type: "string", + description: "퀘스트의 완료 시각", + example: "2023-01-01 00:00:00", + }, + }, + }, + }, }, }, }, @@ -137,11 +129,10 @@ globalStateDocs[`${apiPrefix}/`] = { globalStateDocs[`${apiPrefix}/create`] = { post: { tags: [`${apiPrefix}`], - summary: "Frontend에서 Global state로 관리하는 정보 생성", + summary: "Frontend에서 Global State로 관리할 정보 생성", description: - "유저의 재화 개수, 퀘스트 완료 상태 등 Frontend에서 Global state로 관리할 정보를 생성합니다.", + "유저의 재화 개수, 퀘스트 완료 상태 등 Frontend에서 Global State로 관리할 정보를 생성합니다.", requestBody: { - description: "", content: { "application/json": { schema: { @@ -152,7 +143,6 @@ globalStateDocs[`${apiPrefix}/create`] = { }, responses: { 200: { - description: "", content: { "application/json": { schema: { diff --git a/src/lottery/routes/docs/invite.js b/src/lottery/routes/docs/invites.js similarity index 60% rename from src/lottery/routes/docs/invite.js rename to src/lottery/routes/docs/invites.js index 3a3972da..cfe37214 100644 --- a/src/lottery/routes/docs/invite.js +++ b/src/lottery/routes/docs/invites.js @@ -1,25 +1,23 @@ const { eventConfig } = require("../../../../loadenv"); -const apiPrefix = `/events/${eventConfig?.mode}/invite`; +const apiPrefix = `/events/${eventConfig?.mode}/invites`; -const inviteDocs = {}; -inviteDocs[`${apiPrefix}/search/:inviter`] = { +const invitesDocs = {}; +invitesDocs[`${apiPrefix}/search/{inviter}`] = { get: { tags: [`${apiPrefix}`], - summary: "초대자 정보 조회", - description: "초대자의 정보를 조회합니다.", - requestBody: { - description: "", - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/searchInviterHandler", - }, - }, + summary: "초대한 유저의 정보 반환", + description: "초대한 유저의 정보를 가져옵니다.", + parameters: [ + { + in: "path", + name: "inviter", + required: true, + description: "초대한 유저의 eventStatus ObjectId", + example: "INVITER ID", }, - }, + ], responses: { 200: { - description: "", content: { "application/json": { schema: { @@ -28,13 +26,13 @@ inviteDocs[`${apiPrefix}/search/:inviter`] = { properties: { nickname: { type: "string", - description: "초대자의 닉네임", - example: "asdf", + description: "초대한 유저의 닉네임", + example: "static", }, profileImageUrl: { type: "string", - description: "초대자의 프로필 이미지 URL", - example: "IMAGE URL", + description: "초대한 유저의 프로필 이미지 URL", + example: "PROFILE URL", }, }, }, @@ -44,14 +42,13 @@ inviteDocs[`${apiPrefix}/search/:inviter`] = { }, }, }; -inviteDocs[`${apiPrefix}/create`] = { +invitesDocs[`${apiPrefix}/create`] = { post: { tags: [`${apiPrefix}`], summary: "초대 링크 생성", description: "초대 링크를 생성합니다.", responses: { 200: { - description: "", content: { "application/json": { schema: { @@ -72,4 +69,4 @@ inviteDocs[`${apiPrefix}/create`] = { }, }; -module.exports = inviteDocs; +module.exports = invitesDocs; diff --git a/src/lottery/routes/docs/items.js b/src/lottery/routes/docs/items.js index b08aeaf7..06a6c118 100644 --- a/src/lottery/routes/docs/items.js +++ b/src/lottery/routes/docs/items.js @@ -2,15 +2,13 @@ const { eventConfig } = require("../../../../loadenv"); const apiPrefix = `/events/${eventConfig?.mode}/items`; const itemsDocs = {}; -itemsDocs[`${apiPrefix}/list`] = { +itemsDocs[`${apiPrefix}/`] = { get: { tags: [`${apiPrefix}`], - summary: "상점에서 판매하는 모든 상품의 목록 반환", - description: - "상점에서 판매하는 모든 상품의 목록을 가져옵니다. 매진된 상품도 가져옵니다.", + summary: "상점에서 판매하는 상품의 목록 반환", + description: "상점에서 판매하는 상품의 목록을 가져옵니다.", responses: { 200: { - description: "", content: { "application/json": { schema: { @@ -19,9 +17,61 @@ itemsDocs[`${apiPrefix}/list`] = { properties: { items: { type: "array", - description: "Item의 배열", + description: "상품의 배열", items: { - $ref: "#/components/schemas/item", + type: "object", + required: [ + "_id", + "name", + "description", + "imageUrl", + "price", + "isDisabled", + "itemType", + ], + properties: { + _id: { + type: "string", + description: "상품의 ObjectId", + example: "ITEM ID", + }, + name: { + type: "string", + description: "상품의 이름", + example: "진짜 송편", + }, + description: { + type: "string", + description: "상품의 설명", + example: "먹을 수 있는 송편입니다.", + }, + imageUrl: { + type: "string", + description: "상품의 썸네일 이미지 URL", + example: "THUMBNAIL URL", + }, + instagramStoryStickerImageUrl: { + type: "string", + description: "인스타그램 스토리 스티커 이미지 URL", + example: "STICKER URL", + }, + price: { + type: "number", + description: "상품의 가격. 0 이상의 정수입니다.", + example: 400, + }, + isDisabled: { + type: "boolean", + description: "상품의 판매 중지 여부", + example: false, + }, + itemType: { + type: "number", + description: + "상품의 유형. 0: 일반 상품, 1: 일반 티켓, 2: 고급 티켓, 3: 랜덤박스입니다.", + example: 0, + }, + }, }, }, }, @@ -32,24 +82,121 @@ itemsDocs[`${apiPrefix}/list`] = { }, }, }; -itemsDocs[`${apiPrefix}/purchase/:itemId`] = { +itemsDocs[`${apiPrefix}/leaderboard/{itemId}`] = { + get: { + tags: [`${apiPrefix}`], + summary: "상품 리더보드 반환", + description: "상품 리더보드를 가져옵니다. 일반 상품만 리더보드를 갖습니다.", + parameters: [ + { + in: "path", + name: "itemId", + required: true, + description: "리더보드를 조회할 상품의 ObjectId", + example: "ITEM ID", + }, + ], + responses: { + 200: { + content: { + "application/json": { + schema: { + type: "object", + required: ["leaderboard", "totalAmount", "totalUser"], + properties: { + leaderboard: { + type: "array", + description: "상품 리더보드. 상위 20등까지만 반환됩니다.", + items: { + type: "object", + required: [ + "nickname", + "profileImageUrl", + "amount", + "probability", + ], + properties: { + nickname: { + type: "string", + description: "유저의 닉네임", + example: "static", + }, + profileImageUrl: { + type: "string", + description: "유저의 프로필 이미지 URL", + example: "PROFILE URL", + }, + amount: { + type: "number", + description: "유저가 상품을 구입한 횟수", + example: 3, + }, + probability: { + type: "number", + description: "유저가 상품에 당첨될 확률", + example: 0.1, + }, + }, + }, + }, + totalAmount: { + type: "number", + description: "상품의 총 판매량", + example: 100, + }, + totalUser: { + type: "number", + description: "상품을 구입한 유저의 수", + example: 50, + }, + rank: { + type: "number", + description: "현재 유저의 리더보드 순위. 1부터 시작합니다.", + example: 1, + }, + amount: { + type: "number", + description: "현재 유저가 상품을 구입한 횟수", + example: 3, + }, + probability: { + type: "number", + description: "현재 유저가 상품에 당첨될 확률", + example: 0.1, + }, + }, + }, + }, + }, + }, + }, + }, +}; +itemsDocs[`${apiPrefix}/purchase/{itemId}`] = { post: { tags: [`${apiPrefix}`], - summary: "상품 구매", - description: "상품을 구매합니다.", + summary: "상품 구입", + description: "상품을 구입합니다.", + parameters: [ + { + in: "path", + name: "itemId", + required: true, + description: "리더보드를 조회할 상품의 ObjectId", + example: "ITEM ID", + }, + ], requestBody: { - description: "", content: { "application/json": { schema: { - $ref: "#/components/schemas/purchaseHandler", + $ref: "#/components/schemas/purchaseItemHandlerBody", }, }, }, }, responses: { 200: { - description: "", content: { "application/json": { schema: { @@ -61,27 +208,11 @@ itemsDocs[`${apiPrefix}/purchase/:itemId`] = { description: "성공 여부. 항상 true입니다.", example: true, }, - reward: { - $ref: "#/components/schemas/rewardItem", - }, - }, - }, - }, - }, - }, - 400: { - description: - "checkBanned에서 이벤트에 동의하지 않은 사람과 제재 대상을 선별합니다.", - content: { - "application/json": { - schema: { - type: "object", - required: ["error"], - properties: { - error: { - type: "string", - description: "", - example: "checkBanned: banned user", + isJackpot: { + type: "boolean", + description: + "대박 여부. 랜덤박스를 구입한 경우에만 포함됩니다.", + example: true, }, }, }, diff --git a/src/lottery/routes/docs/publicNotice.js b/src/lottery/routes/docs/publicNotice.js index 23a410b2..bcf2cc78 100644 --- a/src/lottery/routes/docs/publicNotice.js +++ b/src/lottery/routes/docs/publicNotice.js @@ -2,7 +2,7 @@ const { eventConfig } = require("../../../../loadenv"); const apiPrefix = `/events/${eventConfig?.mode}/publicNotice`; const publicNoticeDocs = {}; -// 다음 Endpoint는 2024 봄학기 이벤트에서 사용되지 않습니다. +// 다음 Endpoint들은 2024 추석 이벤트에서 사용되지 않습니다. // // publicNoticeDocs[`${apiPrefix}/recentTransactions`] = { // get: { @@ -35,75 +35,75 @@ const publicNoticeDocs = {}; // }, // }, // }; -publicNoticeDocs[`${apiPrefix}/leaderboard`] = { - get: { - tags: [`${apiPrefix}`], - summary: "리더보드 반환", - description: - "새터반 별 재화 개수 기준의 리더보드와 관련된 정보를 가져옵니다.", - responses: { - 200: { - description: "", - content: { - "application/json": { - schema: { - type: "object", - required: ["leaderboard"], - properties: { - leaderboard: { - type: "array", - description: "이벤트에 참여한 새터반 전체가 포함된 리더보드", - items: { - type: "object", - required: [ - "group", - "creditAmount", - "mvpNickname", - "mvpProfileImageUrl", - ], - properties: { - group: { - type: "number", - description: "새터반", - example: 16, - }, - creditAmount: { - type: "number", - description: "새터반에 소속된 유저의 전체 재화 개수", - example: 3000, - }, - mvpNickname: { - type: "string", - description: - "MVP(새터반 내에서 가장 많은 재화를 가진 유저)의 닉네임", - example: "asdf", - }, - mvpProfileImageUrl: { - type: "string", - description: "MVP의 프로필 이미지 URL", - example: "IMAGE URL", - }, - }, - }, - }, - group: { - type: "number", - description: "유저의 소속 새터반", - example: 16, - }, - rank: { - type: "number", - description: - "유저의 소속 새터반의 리더보드 순위. 1부터 시작합니다.", - example: 1, - }, - }, - }, - }, - }, - }, - }, - }, -}; +// publicNoticeDocs[`${apiPrefix}/leaderboard`] = { +// get: { +// tags: [`${apiPrefix}`], +// summary: "리더보드 반환", +// description: +// "새터반 별 재화 개수 기준의 리더보드와 관련된 정보를 가져옵니다.", +// responses: { +// 200: { +// description: "", +// content: { +// "application/json": { +// schema: { +// type: "object", +// required: ["leaderboard"], +// properties: { +// leaderboard: { +// type: "array", +// description: "이벤트에 참여한 새터반 전체가 포함된 리더보드", +// items: { +// type: "object", +// required: [ +// "group", +// "creditAmount", +// "mvpNickname", +// "mvpProfileImageUrl", +// ], +// properties: { +// group: { +// type: "number", +// description: "새터반", +// example: 16, +// }, +// creditAmount: { +// type: "number", +// description: "새터반에 소속된 유저의 전체 재화 개수", +// example: 3000, +// }, +// mvpNickname: { +// type: "string", +// description: +// "MVP(새터반 내에서 가장 많은 재화를 가진 유저)의 닉네임", +// example: "asdf", +// }, +// mvpProfileImageUrl: { +// type: "string", +// description: "MVP의 프로필 이미지 URL", +// example: "IMAGE URL", +// }, +// }, +// }, +// }, +// group: { +// type: "number", +// description: "유저의 소속 새터반", +// example: 16, +// }, +// rank: { +// type: "number", +// description: +// "유저의 소속 새터반의 리더보드 순위. 1부터 시작합니다.", +// example: 1, +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// }; module.exports = publicNoticeDocs; diff --git a/src/lottery/routes/docs/quests.js b/src/lottery/routes/docs/quests.js index 14694f3e..42a9c022 100644 --- a/src/lottery/routes/docs/quests.js +++ b/src/lottery/routes/docs/quests.js @@ -2,24 +2,22 @@ const { eventConfig } = require("../../../../loadenv"); const apiPrefix = `/events/${eventConfig?.mode}/quests`; const questsDocs = {}; -questsDocs[`${apiPrefix}/complete/:questId`] = { +questsDocs[`${apiPrefix}/complete/{questId}`] = { post: { tags: [`${apiPrefix}`], summary: "퀘스트 완료 요청", description: "퀘스트의 완료를 요청합니다.", - requestBody: { - description: "", - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/completeHandler", - }, - }, + parameters: [ + { + in: "path", + name: "questId", + required: true, + description: "완료를 요청할 퀘스트의 ID", + example: "QUEST ID", }, - }, + ], responses: { 200: { - description: "", content: { "application/json": { schema: { @@ -36,25 +34,6 @@ questsDocs[`${apiPrefix}/complete/:questId`] = { }, }, }, - 400: { - description: - "checkBanned에서 이벤트에 동의하지 않은 사람과 제재 대상을 선별합니다.", - content: { - "application/json": { - schema: { - type: "object", - required: ["error"], - properties: { - error: { - type: "string", - description: "", - example: "checkBanned: banned user", - }, - }, - }, - }, - }, - }, }, }, }; diff --git a/src/lottery/routes/docs/schemas/globalStateSchema.js b/src/lottery/routes/docs/schemas/globalStateSchema.js index 0c3c55e6..15055525 100644 --- a/src/lottery/routes/docs/schemas/globalStateSchema.js +++ b/src/lottery/routes/docs/schemas/globalStateSchema.js @@ -6,7 +6,6 @@ const globalStateZod = { createUserGlobalStateHandler: z .object({ phoneNumber: z.string().regex(user.phoneNumber), - group: z.number().gte(1).lte(26), inviter: z.string().regex(objectId), }) .partial({ inviter: true }), diff --git a/src/lottery/routes/docs/schemas/inviteSchema.js b/src/lottery/routes/docs/schemas/invitesSchema.js similarity index 67% rename from src/lottery/routes/docs/schemas/inviteSchema.js rename to src/lottery/routes/docs/schemas/invitesSchema.js index e3016557..dfc33c5c 100644 --- a/src/lottery/routes/docs/schemas/inviteSchema.js +++ b/src/lottery/routes/docs/schemas/invitesSchema.js @@ -2,12 +2,12 @@ const { z } = require("zod"); const { zodToSchemaObject } = require("../../../../routes/docs/utils"); const { objectId } = require("../../../../modules/patterns"); -const inviteZod = { +const invitesZod = { searchInviterHandler: z.object({ inviter: z.string().regex(objectId), }), }; -const inviteSchema = zodToSchemaObject(inviteZod); +const invitesSchema = zodToSchemaObject(invitesZod); -module.exports = { inviteSchema, inviteZod }; +module.exports = { invitesZod, invitesSchema }; diff --git a/src/lottery/routes/docs/schemas/itemsSchema.js b/src/lottery/routes/docs/schemas/itemsSchema.js index 80912cfb..7e570b5a 100644 --- a/src/lottery/routes/docs/schemas/itemsSchema.js +++ b/src/lottery/routes/docs/schemas/itemsSchema.js @@ -1,98 +1,19 @@ -/* Item에 대한 기본적인 프로퍼티를 갖고 있는 스키마입니다. - * TODO: 추후 코드 재사용시 상황에 맞춰 zod로 이전이 필요합니다. - */ -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", - }, - instagramStoryStickerImageUrl: { - type: "string", - description: "인스타그램 스토리 스티커 이미지 URL", - example: "STICKER URL", - }, - price: { - type: "number", - description: "상품의 가격. 0 이상입니다.", - example: 400, - }, - description: { - type: "string", - description: "상품의 설명", - example: "맛있는 송편입니다.", - }, - isDisabled: { - type: "boolean", - description: "판매 중지 여부", - example: false, - }, - stock: { - type: "number", - description: "남은 상품 재고. 재고가 있는 경우 1, 없는 경우 0입니다.", - example: 1, - }, - }, -}; +const { z } = require("zod"); +const { zodToSchemaObject } = require("../../../../routes/docs/utils"); +const { objectId } = require("../../../../modules/patterns"); -/** itemBase에 itemType(상품 유형) 프로퍼티가 추가된 스키마입니다. */ -const itemWithType = { - type: itemBase.type, - required: itemBase.required.concat(["itemType"]), - properties: { - ...itemBase.properties, - itemType: { - type: "number", - description: - "상품 유형. 0: 일반 상품, 1: 일반 티켓, 2: 고급 티켓, 3: 랜덤박스입니다.", - example: 0, - }, - }, +const itemsZod = { + getItemLeaderboardHandler: z.object({ + itemId: z.string().regex(objectId), + }), + purchaseItemHandlerParams: z.object({ + itemId: z.string().regex(objectId), + }), + purchaseItemHandlerBody: z.object({ + amount: z.number().int().positive(), + }), }; -const itemsSchema = { - item: itemWithType, - relatedItem: { - ...itemWithType, - description: - "Transaction과 관련된 아이템의 Object. 아이템과 관련된 Transaction인 경우에만 포함됩니다.", - }, - rewardItem: { - ...itemBase, - description: "랜덤박스를 구입한 경우에만 포함됩니다.", - }, - purchaseHandler: { - type: "object", - required: ["itemId"], - properties: { - itemId: { - type: "string", - pattern: "^[a-fA-F\\d]{24}$", - }, - }, - errorMessage: "validation: bad request", - }, -}; +const itemsSchema = zodToSchemaObject(itemsZod); -module.exports = itemsSchema; +module.exports = { itemsZod, itemsSchema }; diff --git a/src/lottery/routes/docs/schemas/questsSchema.js b/src/lottery/routes/docs/schemas/questsSchema.js index 2efd11cd..8daf560d 100644 --- a/src/lottery/routes/docs/schemas/questsSchema.js +++ b/src/lottery/routes/docs/schemas/questsSchema.js @@ -2,7 +2,9 @@ const { z } = require("zod"); const { zodToSchemaObject } = require("../../../../routes/docs/utils"); const questsZod = { - completeHandler: z.object({ questId: z.enum(["roomSharing"]) }), + completeQuestHandler: z.object({ + questId: z.enum(["roomSharing", "dailyAttendance"]), + }), }; const questsSchema = zodToSchemaObject(questsZod); diff --git a/src/lottery/routes/docs/swaggerDocs.js b/src/lottery/routes/docs/swaggerDocs.js index 38ca8298..0b6702da 100644 --- a/src/lottery/routes/docs/swaggerDocs.js +++ b/src/lottery/routes/docs/swaggerDocs.js @@ -1,13 +1,13 @@ const globalStateDocs = require("./globalState"); -const inviteDocs = require("./invite"); +const invitesDocs = require("./invites"); const itemsDocs = require("./items"); const publicNoticeDocs = require("./publicNotice"); const questsDocs = require("./quests"); const transactionsDocs = require("./transactions"); const { globalStateSchema } = require("./schemas/globalStateSchema"); -const { inviteSchema } = require("./schemas/inviteSchema"); -const itemsSchema = require("./schemas/itemsSchema"); +const { invitesSchema } = require("./schemas/invitesSchema"); +const { itemsSchema } = require("./schemas/itemsSchema"); const { questsSchema } = require("./schemas/questsSchema"); const { eventConfig } = require("../../../../loadenv"); @@ -20,41 +20,39 @@ const eventSwaggerDocs = { description: "이벤트 - Global State 관련 API", }, { - name: `${apiPrefix}/invite`, + name: `${apiPrefix}/invites`, description: "이벤트 - 초대 링크 관련 API", }, - // 이 태그는 2024 봄학기 이벤트에서 사용되지 않습니다. - // - // { - // name: `${apiPrefix}/items`, - // description: "이벤트 - 아이템 관련 API", - // }, { - name: `${apiPrefix}/publicNotice`, - description: "이벤트 - 아이템 구매, 뽑기, 획득 공지 관련 API", + name: `${apiPrefix}/items`, + description: "이벤트 - 상품 관련 API", }, + // { + // name: `${apiPrefix}/publicNotice`, + // description: "이벤트 - 상품 구매, 뽑기, 획득 공지 관련 API", + // }, { name: `${apiPrefix}/quests`, description: "이벤트 - 퀘스트 관련 API", }, { name: `${apiPrefix}/transactions`, - description: "이벤트 - 입출금 내역 관련 API", + description: "이벤트 - 재화 입출금 내역 관련 API", }, ], paths: { ...globalStateDocs, - ...inviteDocs, - //...itemsDocs, - ...publicNoticeDocs, + ...invitesDocs, + ...itemsDocs, + // ...publicNoticeDocs, ...questsDocs, ...transactionsDocs, }, components: { schemas: { ...globalStateSchema, - ...inviteSchema, - //...itemsSchema, + ...invitesSchema, + ...itemsSchema, ...questsSchema, }, }, diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index fa78238b..a041b949 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -6,10 +6,9 @@ transactionsDocs[`${apiPrefix}/`] = { get: { tags: [`${apiPrefix}`], summary: "재화 입출금 내역 반환", - description: "유저의 재화 입출금 내역을 가져옵니다.", + description: "재화 입출금 내역을 가져옵니다.", responses: { 200: { - description: "", content: { "application/json": { schema: { @@ -18,16 +17,11 @@ transactionsDocs[`${apiPrefix}/`] = { properties: { transactions: { type: "array", - description: "유저의 재화 입출금 기록의 배열", + description: "유저의 재화 입출금 내역의 배열", items: { type: "object", - required: ["_id", "type", "amount", "comment", "createAt"], + required: ["type", "amount", "comment", "createdAt"], properties: { - _id: { - type: "string", - description: "Transaction의 ObjectId", - example: "OBJECT ID", - }, type: { type: "string", description: @@ -41,18 +35,33 @@ transactionsDocs[`${apiPrefix}/`] = { }, questId: { type: "string", - description: - "Transaction과 관련된 퀘스트의 Id. 퀘스트와 관련된 Transaction인 경우에만 포함됩니다.", + description: "입출금 내역과 관련된 퀘스트의 Id", example: "QUEST ID", }, + item: { + type: "object", + required: ["name", "imageUrl"], + properties: { + name: { + type: "string", + description: "상품의 이름", + example: "랜덤 상자", + }, + imageUrl: { + type: "string", + description: "상품의 썸네일 이미지 URL", + example: "IMAGE URL", + }, + }, + }, comment: { type: "string", description: "입출금 내역에 대한 설명", example: "랜덤 상자 구입 - 50개 차감", }, - createAt: { + createdAt: { type: "string", - description: "입출금이 일어난 시각", + description: "입출금 내역이 생성된 시각", example: "2023-01-01 00:00:00", }, }, diff --git a/src/lottery/routes/globalState.js b/src/lottery/routes/globalState.js index 1f2b4327..c4f37d39 100644 --- a/src/lottery/routes/globalState.js +++ b/src/lottery/routes/globalState.js @@ -1,7 +1,8 @@ const express = require("express"); +const router = express.Router(); + const { validateBody } = require("../../middlewares/zod"); const { globalStateZod } = require("./docs/schemas/globalStateSchema"); -const router = express.Router(); const globalStateHandlers = require("../services/globalState"); router.get("/", globalStateHandlers.getUserGlobalStateHandler); diff --git a/src/lottery/routes/invite.js b/src/lottery/routes/invite.js deleted file mode 100644 index eafa09cb..00000000 --- a/src/lottery/routes/invite.js +++ /dev/null @@ -1,20 +0,0 @@ -const express = require("express"); -const { validateParams } = require("../../middlewares/zod"); -const { inviteZod } = require("./docs/schemas/inviteSchema"); -const router = express.Router(); -const inviteHandlers = require("../services/invite"); - -router.get( - "/search/:inviter", - validateParams(inviteZod.searchInviterHandler), - inviteHandlers.searchInviterHandler -); - -// 아래의 Endpoint 접근 시 로그인, 차단 여부 체크 및 시각 체크 필요 -router.use(require("../../middlewares/auth")); -router.use(require("../middlewares/checkBanned")); -router.use(require("../middlewares/timestampValidator")); - -router.post("/create", inviteHandlers.createInviteUrlHandler); - -module.exports = router; diff --git a/src/lottery/routes/invites.js b/src/lottery/routes/invites.js new file mode 100644 index 00000000..65e4271e --- /dev/null +++ b/src/lottery/routes/invites.js @@ -0,0 +1,21 @@ +const express = require("express"); +const router = express.Router(); + +const { validateParams } = require("../../middlewares/zod"); +const { invitesZod } = require("./docs/schemas/invitesSchema"); +const invitesHandlers = require("../services/invites"); + +router.get( + "/search/:inviter", + validateParams(invitesZod.searchInviterHandler), + invitesHandlers.searchInviterHandler +); + +// 아래의 Endpoint 접근 시 로그인, 차단 여부 및 시각 체크 필요 +router.use(require("../../middlewares/auth")); +router.use(require("../middlewares/checkBanned")); +router.use(require("../middlewares/timestampValidator")); + +router.post("/create", invitesHandlers.createInviteUrlHandler); + +module.exports = router; diff --git a/src/lottery/routes/items.js b/src/lottery/routes/items.js index 5cdf98a8..135617b8 100644 --- a/src/lottery/routes/items.js +++ b/src/lottery/routes/items.js @@ -1,23 +1,27 @@ const express = require("express"); - const router = express.Router(); -// TODO: 추후 코드 재사용시 상황에 맞춰 zod로 이전이 필요합니다. + +const { validateBody, validateParams } = require("../../middlewares/zod"); +const { itemsZod } = require("./docs/schemas/itemsSchema"); const itemsHandlers = require("../services/items"); -const itemsSchema = require("./docs/schemas/itemsSchema"); -// 아래의 Endpoint는 2024 봄학기 이벤트에서 사용되지 않습니다. -// -// router.get("/list", itemsHandlers.listHandler); +router.get("/", itemsHandlers.getItemsHandler); +router.get( + "/leaderboard/:itemId", + validateParams(itemsZod.getItemLeaderboardHandler), + itemsHandlers.getItemLeaderboardHandler +); -// // 아래의 Endpoint 접근 시 로그인, 차단 여부 체크 및 시각 체크 필요 -// router.use(require("../../middlewares/auth")); -// router.use(require("../middlewares/checkBanned")); -// router.use(require("../middlewares/timestampValidator")); +// 아래의 Endpoint 접근 시 로그인, 차단 여부 및 시각 체크 필요 +router.use(require("../../middlewares/auth")); +router.use(require("../middlewares/checkBanned")); +router.use(require("../middlewares/timestampValidator")); -// router.post( -// "/purchase/:itemId", -// validateParams(itemsSchema.purchaseHandler), -// itemsHandlers.purchaseHandler -// ); +router.post( + "/purchase/:itemId", + validateParams(itemsZod.purchaseItemHandlerParams), + validateBody(itemsZod.purchaseItemHandlerBody), + itemsHandlers.purchaseItemHandler +); module.exports = router; diff --git a/src/lottery/routes/publicNotice.js b/src/lottery/routes/publicNotice.js index 4698a193..f6646061 100644 --- a/src/lottery/routes/publicNotice.js +++ b/src/lottery/routes/publicNotice.js @@ -3,10 +3,10 @@ const express = require("express"); const router = express.Router(); const publicNoticeHandlers = require("../services/publicNotice"); -router.get("/leaderboard", publicNoticeHandlers.getGroupLeaderboardHandler); - -// 아래의 Endpoint는 2024 봄학기 이벤트에서 사용되지 않습니다. +// 아래의 Endpoint들은 2024 추석 이벤트에서 사용되지 않습니다. // +// router.get("/leaderboard", publicNoticeHandlers.getGroupLeaderboardHandler); + // router.get( // "/recentTransactions", // publicNoticeHandlers.getRecentPurchaceItemListHandler diff --git a/src/lottery/routes/quests.js b/src/lottery/routes/quests.js index 4941c8d2..e9845434 100644 --- a/src/lottery/routes/quests.js +++ b/src/lottery/routes/quests.js @@ -1,18 +1,19 @@ const express = require("express"); +const router = express.Router(); + const { validateParams } = require("../../middlewares/zod"); const { questsZod } = require("./docs/schemas/questsSchema"); -const router = express.Router(); const questsHandlers = require("../services/quests"); -// 아래의 Endpoint 접근 시 로그인, 차단 여부 체크 및 시각 체크 필요 +// 아래의 Endpoint 접근 시 로그인, 차단 여부 및 시각 체크 필요 router.use(require("../../middlewares/auth")); router.use(require("../middlewares/checkBanned")); router.use(require("../middlewares/timestampValidator")); router.post( "/complete/:questId", - validateParams(questsZod.completeHandler), - questsHandlers.completeHandler + validateParams(questsZod.completeQuestHandler), + questsHandlers.completeQuestHandler ); module.exports = router; diff --git a/src/lottery/routes/transactions.js b/src/lottery/routes/transactions.js index aee05d90..f9e375ca 100644 --- a/src/lottery/routes/transactions.js +++ b/src/lottery/routes/transactions.js @@ -1,6 +1,6 @@ const express = require("express"); - const router = express.Router(); + const transactionsHandlers = require("../services/transactions"); // 아래의 Endpoint 접근 시 로그인 필요 diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 5459f851..ee01f47e 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -8,61 +8,57 @@ const { eventConfig } = require("../../../loadenv"); const contracts = require("../modules/contracts"); const quests = Object.values(contracts.quests); -// 유저가 이벤트에 참여할 수 있는지 확인하는 함수입니다. -const checkIsUserEligible = (user) => { - // production 환경이 아닌 경우 테스트를 위해 참여 조건을 확인하지 않습니다. - if (nodeEnv !== "production") return true; +// 아래의 함수는 2024 추석 이벤트에서 사용되지 않습니다. +// +// // 유저가 이벤트에 참여할 수 있는지 확인하는 함수입니다. +// const checkIsUserEligible = (user) => { +// // production 환경이 아닌 경우 테스트를 위해 참여 조건을 확인하지 않습니다. +// if (nodeEnv !== "production") return true; - const kaistId = parseInt(user?.subinfo?.kaist || "0"); - return 20240001 <= kaistId && kaistId <= 20241500; -}; +// const kaistId = parseInt(user?.subinfo?.kaist || "0"); +// return 20240001 <= kaistId && kaistId <= 20241500; +// }; const getUserGlobalStateHandler = async (req, res) => { try { const userId = isLogin(req) ? getLoginInfo(req).oid : null; - const user = userId && (await userModel.findOne({ _id: userId }).lean()); - const eventStatus = userId && (await eventStatusModel - .findOne({ userId }, "completedQuests creditAmount isBanned group") + .findOne({ userId }, "completedQuests creditAmount isBanned") .lean()); if (!eventStatus) return res.json({ isAgreeOnTermsOfEvent: false, - isEligible: checkIsUserEligible(user) || !!user?.isAdmin, // 테스트를 위해 관리자인 경우 true로 설정합니다. 하지만 관리자이더라도 이벤트에 참여할 수 없습니다. - completedQuests: [], + isBanned: false, creditAmount: 0, - group: 0, - groupCreditAmount: 0, quests, + completedQuests: [], }); // group이 eventStatus.group과 같은 사용자들의 creditAmount를 합산합니다. - const groupCreditAmount = await eventStatusModel.aggregate([ - { - $match: { - group: eventStatus.group, - }, - }, - { - $group: { - _id: null, - creditAmount: { $sum: "$creditAmount" }, - }, - }, - ]); - const groupCreditAmountReal = groupCreditAmount?.[0].creditAmount; - if (!groupCreditAmountReal && groupCreditAmountReal !== 0) - return res - .status(500) - .json({ error: "GlobalState/ : internal server error" }); + // const groupCreditAmount = await eventStatusModel.aggregate([ + // { + // $match: { + // group: eventStatus.group, + // }, + // }, + // { + // $group: { + // _id: null, + // creditAmount: { $sum: "$creditAmount" }, + // }, + // }, + // ]); + // const groupCreditAmountReal = groupCreditAmount?.[0].creditAmount; + // if (!groupCreditAmountReal && groupCreditAmountReal !== 0) + // return res + // .status(500) + // .json({ error: "GlobalState/ : internal server error" }); return res.json({ - isAgreeOnTermsOfEvent: true, - isEligible: true, ...eventStatus, - groupCreditAmount: groupCreditAmountReal, + isAgreeOnTermsOfEvent: true, quests, }); } catch (err) { @@ -79,7 +75,7 @@ const createUserGlobalStateHandler = async (req, res) => { if (eventStatus) return res .status(400) - .json({ error: "GlobalState/Create : already created" }); + .json({ error: "GlobalState/create : already created" }); /* Request의 inviter 필드가 설정되어 있는데, 1. 해당되는 유저가 이벤트에 참여하지 않았거나, @@ -88,47 +84,53 @@ const createUserGlobalStateHandler = async (req, res) => { 에러를 발생시킵니다. 개인정보 보호를 위해 오류 메세지는 하나로 통일하였습니다. */ const inviterStatus = req.body.inviter && - (await eventStatusModel.findOne({ _id: req.body.inviter }).lean()); + (await eventStatusModel.findById(req.body.inviter).lean()); if ( req.body.inviter && (!inviterStatus || inviterStatus.isBanned || - !inviterStatus.isEnabledInviteUrl) + !inviterStatus.isInviteUrlEnabled) ) return res.status(400).json({ - error: "GlobalState/Create : inviter did not participate in the event", + error: "GlobalState/create : invalid inviter", }); - const user = await userModel.findOne({ _id: req.userOid }); + const user = await userModel.findById(req.userOid); if (!user) return res .status(500) - .json({ error: "GlobalState/Create : internal server error" }); + .json({ error: "GlobalState/create : internal server error" }); // 유저가 이벤트에 참여할 수 있는지 확인합니다. - const isEligible = checkIsUserEligible(user); - if (!isEligible) - return res.status(400).json({ - error: "GlobalState/Create : not eligible to participate in the event", - }); - - // 수집한 전화번호를 User Document에 저장합니다. - // 다른 이벤트 참여 과정에서 문제가 생길 수 있으므로, 이벤트 참여 자격이 있는 경우에만 저장합니다. - user.phoneNumber = req.body.phoneNumber; - await user.save(); + // const isEligible = checkIsUserEligible(user); + // if (!isEligible) + // return res.status(400).json({ + // error: "GlobalState/create : not eligible to participate in the event", + // }); + + // 필요한 경우 유저의 전화번호를 업데이트합니다. + if (user.phoneNumber !== req.body.phoneNumber) { + if (user.phoneNumber) { + logger.info(`Past user phone number: ${user.phoneNumber}`); + logger.info(`Update user phone number: ${req.body.phoneNumber}`); + } + + user.phoneNumber = req.body.phoneNumber; + await user.save(); + } // EventStatus Document를 생성합니다. eventStatus = new eventStatusModel({ userId: req.userOid, creditAmount: eventConfig?.credit.initialAmount ?? 0, - group: req.body.group, - inviter: req.body.inviter, + inviter: inviterStatus?.userId ?? undefined, }); await eventStatus.save(); + // 퀘스트를 완료 처리합니다. await contracts.completeFirstLoginQuest(req.userOid, req.timestamp); - if (req.body.inviter) { + if (inviterStatus) { await contracts.completeEventSharingQuest(req.userOid, req.timestamp); await contracts.completeEventSharingQuest( inviterStatus.userId, @@ -141,7 +143,7 @@ const createUserGlobalStateHandler = async (req, res) => { logger.error(err); res .status(500) - .json({ error: "GlobalState/Create : internal server error" }); + .json({ error: "GlobalState/create : internal server error" }); } }; diff --git a/src/lottery/services/invite.js b/src/lottery/services/invite.js deleted file mode 100644 index c7871273..00000000 --- a/src/lottery/services/invite.js +++ /dev/null @@ -1,66 +0,0 @@ -const { eventStatusModel } = require("../modules/stores/mongo"); -const { userModel } = require("../../modules/stores/mongo"); -const logger = require("../../modules/logger"); - -const { eventConfig } = require("../../../loadenv"); - -const searchInviterHandler = async (req, res) => { - try { - const { inviter } = req.params; - const inviterStatus = await eventStatusModel.findOne({ _id: inviter }); - if ( - !inviterStatus || - !inviterStatus.isEnabledInviteUrl || - inviterStatus.isBanned - ) - return res.status(400).json({ error: "Invite/Search : invalid inviter" }); - - const inviterInfo = await userModel.findOne({ _id: inviterStatus.userId }); - if (!inviterInfo) - return res - .status(500) - .json({ error: "Invite/Search : internal server error" }); - - return res.json({ - nickname: inviterInfo.nickname, - profileImageUrl: inviterInfo.profileImageUrl, - }); - } catch (err) { - logger.error(err); - res.status(500).json({ error: "Invite/Search : internal server error" }); - } -}; - -const createInviteUrlHandler = async (req, res) => { - try { - const inviteUrl = `${req.origin}/event/${eventConfig?.mode}-invite/${req.eventStatus._id}`; - - if (req.eventStatus.isEnabledInviteUrl) return res.json({ inviteUrl }); - - const eventStatus = await eventStatusModel - .findOneAndUpdate( - { - _id: req.eventStatus._id, - isEnabledInviteUrl: false, - }, - { - isEnabledInviteUrl: true, - } - ) - .lean(); - if (!eventStatus) - return res - .status(500) - .json({ error: "Invite/Create : internal server error" }); - - return res.json({ inviteUrl }); - } catch (err) { - logger.error(err); - res.status(500).json({ error: "Invite/Create : internal server error" }); - } -}; - -module.exports = { - searchInviterHandler, - createInviteUrlHandler, -}; diff --git a/src/lottery/services/invites.js b/src/lottery/services/invites.js new file mode 100644 index 00000000..6479bce8 --- /dev/null +++ b/src/lottery/services/invites.js @@ -0,0 +1,73 @@ +const { eventStatusModel } = require("../modules/stores/mongo"); +const { userModel } = require("../../modules/stores/mongo"); +const logger = require("../../modules/logger"); + +const { eventConfig } = require("../../../loadenv"); + +const searchInviterHandler = async (req, res) => { + try { + /* 1. 해당되는 유저가 이벤트에 참여하지 않았거나, + 2. 해당되는 유저의 이벤트 참여가 제한된 상태이거나, + 3. 해당되는 유저의 초대 링크가 활성화되지 않았으면, + 에러를 발생시킵니다. 개인정보 보호를 위해 오류 메세지는 하나로 통일하였습니다. */ + const inviterStatus = await eventStatusModel + .findById(req.params.inviter) + .lean(); + if ( + !inviterStatus || + inviterStatus.isBanned || + !inviterStatus.isInviteUrlEnabled + ) + return res + .status(400) + .json({ error: "Invites/search : invalid inviter" }); + + // 해당되는 유저의 닉네임과 프로필 이미지를 가져옵니다. + const inviter = await userModel + .findById(inviterStatus.userId, "nickname profileImageUrl") + .lean(); + if (!inviter) + return res + .status(500) + .json({ error: "Invites/search : internal server error" }); + + return res.json(inviter); + } catch (err) { + logger.error(err); + res.status(500).json({ error: "Invites/search : internal server error" }); + } +}; + +const createInviteUrlHandler = async (req, res) => { + try { + const inviteUrl = `${req.origin}/event/${eventConfig?.mode}-invite/${req.eventStatus._id}`; + + // 이미 초대 링크가 활성화된 경우 링크를 즉시 반환합니다. + if (req.eventStatus.isInviteUrlEnabled) return res.json({ inviteUrl }); + + // 초대 링크를 활성화합니다. + const { modifiedCount } = await eventStatusModel.updateOne( + { + _id: req.eventStatus._id, + isInviteUrlEnabled: false, + }, + { + isInviteUrlEnabled: true, + } + ); + if (modifiedCount !== 1) + return res + .status(500) + .json({ error: "Invites/create : internal server error" }); + + return res.json({ inviteUrl }); + } catch (err) { + logger.error(err); + res.status(500).json({ error: "Invites/create : internal server error" }); + } +}; + +module.exports = { + searchInviterHandler, + createInviteUrlHandler, +}; diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 9189bae4..9196ad53 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -3,9 +3,140 @@ const { itemModel, transactionModel, } = require("../modules/stores/mongo"); +const { userModel } = require("../../modules/stores/mongo"); +const { isLogin, getLoginInfo } = require("../../modules/auths/login"); const logger = require("../../modules/logger"); const { eventConfig } = require("../../../loadenv"); +const contracts = require("../modules/contracts"); + +const getItemsHandler = async (req, res) => { + try { + const items = await itemModel + .find( + {}, + "_id name description imageUrl instagramStoryStickerImageUrl price isDisabled itemType" + ) + .lean(); + res.json({ items }); + } catch (err) { + logger.error(err); + res.status(500).json({ error: "Items/ : internal server error" }); + } +}; + +// 유도 과정은 services/publicNotice.js 파일에 정의된 calculateProbabilityV2 함수의 주석 참조 +const calculateWinProbability = (realStock, users, amount, totalAmount) => { + if (users.length <= realStock) return 1; + + const base = Math.pow( + 1 - realStock / users.length, + users.length / totalAmount + ); + return 1 - Math.pow(base, amount); +}; + +const getItemLeaderboardHandler = async (req, res) => { + try { + // 상품 정보를 가져옵니다. + const { itemId } = req.params; + const item = await itemModel.findOne({ _id: itemId, itemType: 0 }).lean(); + if (!item) + return res + .status(400) + .json({ error: "Items/leaderboard : invalid item" }); + + // 해당 상품을 구매한 유저들의 목록을 가져옵니다. + const users = await transactionModel.aggregate([ + { + $match: { + type: "use", + itemId: item._id, + }, + }, + { + $group: { + _id: "$userId", + amount: { $sum: "$itemAmount" }, + }, + }, + { + $lookup: { + from: eventStatusModel.collection.name, + localField: "_id", + foreignField: "userId", + as: "eventStatus", + }, + }, + { + $match: { + "eventStatus.0.isBanned": false, + }, + }, + { + $sort: { amount: -1 }, + }, + ]); + + // 리더보드 생성을 위해 필요한 정보를 계산합니다. + const totalAmount = users.reduce((acc, user) => acc + user.amount, 0); + const rankMap = new Map( + users + .map((user) => user.amount) + .reduce((acc, amount, index) => { + if (acc.length === 0 || acc[acc.length - 1][0] !== amount) { + acc.push([amount, index + 1]); + } + return acc; + }, []) + ); + + // 리더보드를 생성합니다. + const leaderboardBase = users.map((user) => ({ + userId: user._id, + amount: user.amount, + probability: calculateWinProbability( + item.realStock, + users, + user.amount, + totalAmount + ), + rank: rankMap.get(user.amount), + })); + const leaderboard = await Promise.all( + leaderboardBase + .filter((user) => user.rank <= 20) + .map(async (user) => { + const userInfo = await userModel.findById(user.userId).lean(); + return { + nickname: userInfo.nickname, + profileImageUrl: userInfo.profileImageUrl, + amount: user.amount, + probability: user.probability, + }; + }) + ); + + const userId = isLogin(req) ? getLoginInfo(req).oid : null; + const user = leaderboardBase.find( + (user) => user.userId.toString() === userId + ); + + return res.json({ + leaderboard, + totalAmount, + totalUser: users.length, + amount: user?.amount, + probability: user?.probability, + rank: user?.rank, + }); + } catch (err) { + logger.error(err); + res + .status(500) + .json({ error: "Items/leaderboard : internal server error" }); + } +}; const updateEventStatus = async ( userId, @@ -22,199 +153,216 @@ const updateEventStatus = async ( } ); -const hideItemStock = (item) => { - item.stock = item.stock > 0 ? 1 : 0; - return item; -}; +// 아래의 함수는 2024 추석 이벤트에서 사용되지 않습니다. +// +// const getRandomItem = async (req, depth) => { +// if (depth >= 10) { +// logger.error(`User ${req.userOid} failed to open random box`); +// return null; +// } -const getRandomItem = async (req, depth) => { - if (depth >= 10) { - logger.error(`User ${req.userOid} failed to open random box`); - return null; - } +// const items = await itemModel +// .find({ +// isRandomItem: true, +// stock: { $gt: 0 }, +// isDisabled: false, +// }) +// .lean(); +// const randomItems = items +// .map((item) => Array(item.randomWeight).fill(item)) +// .reduce((a, b) => a.concat(b), []); +// const dumpRandomItems = randomItems +// .map((item) => item._id.toString()) +// .join(","); - const items = await itemModel - .find({ - isRandomItem: true, - stock: { $gt: 0 }, - isDisabled: false, - }) - .lean(); - const randomItems = items - .map((item) => Array(item.randomWeight).fill(item)) - .reduce((a, b) => a.concat(b), []); - const dumpRandomItems = randomItems - .map((item) => item._id.toString()) - .join(","); - - logger.info( - `User ${req.userOid}'s ${ - depth + 1 - }th random box probability is: [${dumpRandomItems}]` - ); +// logger.info( +// `User ${req.userOid}'s ${ +// depth + 1 +// }th random box probability is: [${dumpRandomItems}]` +// ); - if (randomItems.length === 0) return null; +// if (randomItems.length === 0) return null; - const randomItem = - randomItems[Math.floor(Math.random() * randomItems.length)]; - try { - // 1단계: 재고를 차감합니다. - const newRandomItem = await itemModel - .findOneAndUpdate( - { _id: randomItem._id, stock: { $gt: 0 } }, - { - $inc: { - stock: -1, - }, - }, - { - new: true, - fields: { - itemType: 0, - isRandomItem: 0, - randomWeight: 0, - }, - } - ) - .lean(); - if (!newRandomItem) { - throw new Error(`Item ${randomItem._id.toString()} was already sold out`); - } +// const randomItem = +// randomItems[Math.floor(Math.random() * randomItems.length)]; +// try { +// // 1단계: 재고를 차감합니다. +// const newRandomItem = await itemModel +// .findOneAndUpdate( +// { _id: randomItem._id, stock: { $gt: 0 } }, +// { +// $inc: { +// stock: -1, +// }, +// }, +// { +// new: true, +// fields: { +// itemType: 0, +// isRandomItem: 0, +// randomWeight: 0, +// }, +// } +// ) +// .lean(); +// if (!newRandomItem) { +// throw new Error(`Item ${randomItem._id.toString()} was already sold out`); +// } - // 2단계: 유저 정보를 업데이트합니다. - await updateEventStatus(req.userOid, { - ticket1Delta: randomItem.itemType === 1 ? 1 : 0, - ticket2Delta: randomItem.itemType === 2 ? 1 : 0, - }); +// // 2단계: 유저 정보를 업데이트합니다. +// await updateEventStatus(req.userOid, { +// ticket1Delta: randomItem.itemType === 1 ? 1 : 0, +// ticket2Delta: randomItem.itemType === 2 ? 1 : 0, +// }); - // 3단계: Transaction을 추가합니다. - const transaction = new transactionModel({ - type: "use", - amount: 0, - userId: req.userOid, - item: randomItem._id, - itemType: randomItem.itemType, - comment: `랜덤박스에서 "${randomItem.name}" 1개를 획득했습니다.`, - }); - await transaction.save(); +// // 3단계: Transaction을 추가합니다. +// const transaction = new transactionModel({ +// type: "use", +// amount: 0, +// userId: req.userOid, +// itemId: randomItem._id, +// comment: `랜덤박스에서 "${randomItem.name}" 1개를 획득했습니다.`, +// }); +// await transaction.save(); - return newRandomItem; - } catch (err) { - logger.error(err); - logger.warn( - `User ${req.userOid}'s ${depth + 1}th random box failed due to exception` - ); +// return newRandomItem; +// } catch (err) { +// logger.error(err); +// logger.warn( +// `User ${req.userOid}'s ${depth + 1}th random box failed due to exception` +// ); - return await getRandomItem(req, depth + 1); - } -}; +// return await getRandomItem(req, depth + 1); +// } +// }; -const listHandler = async (_, res) => { - try { - const items = await itemModel - .find( - {}, - "name imageUrl instagramStoryStickerImageUrl price description isDisabled stock itemType" - ) - .lean(); - res.json({ items: items.map(hideItemStock) }); - } catch (err) { - logger.error(err); - res.status(500).json({ error: "Items/List : internal server error" }); - } -}; - -const purchaseHandler = async (req, res) => { +const purchaseItemHandler = async (req, res) => { try { const { itemId } = req.params; - const item = await itemModel.findOne({ _id: itemId }).lean(); + const item = await itemModel.findById(itemId).lean(); if (!item) - return res.status(400).json({ error: "Items/Purchase : invalid Item" }); + return res.status(400).json({ error: "Items/purchase : invalid Item" }); + + const { amount } = req.body; + const totalPrice = item.price * amount; - // 구매 가능 조건: 크레딧이 충분하며, 재고가 남아있으며, 판매 중인 아이템이어야 합니다. + // 구매 가능 조건: 재화가 충분하며, 재고가 남아있으며, 판매 중인 상품이어야 합니다. if (item.isDisabled) - return res.status(400).json({ error: "Items/Purchase : disabled item" }); - if (req.eventStatus.creditAmount < item.price) + return res.status(400).json({ error: "Items/purchase : disabled item" }); + if (req.eventStatus.creditAmount < totalPrice) return res .status(400) - .json({ error: "Items/Purchase : not enough credit" }); - if (item.stock <= 0) + .json({ error: "Items/purchase : not enough credit" }); + if (item.stock < amount) return res .status(400) - .json({ error: "Items/Purchase : item out of stock" }); + .json({ error: "Items/purchase : item out of stock" }); // 1단계: 재고를 차감합니다. const { modifiedCount } = await itemModel.updateOne( - { _id: item._id, stock: { $gt: 0 } }, - { - $inc: { - stock: -1, - }, - } + { _id: item._id, stock: { $gte: amount } }, + { $inc: { stock: -amount } } ); if (modifiedCount === 0) return res .status(400) - .json({ error: "Items/Purchase : item out of stock" }); + .json({ error: "Items/purchase : item out of stock" }); - // 2단계: 유저 정보를 업데이트합니다. - await updateEventStatus(req.userOid, { - creditDelta: -item.price, - ticket1Delta: item.itemType === 1 ? 1 : 0, - ticket2Delta: item.itemType === 2 ? 1 : 0, - }); + if (item.itemType !== 3) { + // 랜덤박스가 아닌 상품을 구입한 경우 + // 2단계: 유저 정보를 업데이트합니다. + await updateEventStatus(req.userOid, { + creditDelta: -totalPrice, + ticket1Delta: item.itemType === 1 ? amount : 0, + ticket2Delta: item.itemType === 2 ? amount : 0, + }); - // 3단계: Transaction을 추가합니다. - const transaction = new transactionModel({ - type: "use", - amount: item.price, - userId: req.userOid, - item: item._id, - itemType: item.itemType, - comment: `${eventConfig?.credit.name} ${item.price}개를 사용해 "${item.name}" 1개를 획득했습니다.`, - }); - await transaction.save(); + // 3단계: 출금 내역을 추가합니다. + const transaction = new transactionModel({ + type: "use", + amount: totalPrice, + userId: req.userOid, + itemId: item._id, + itemAmount: amount, + comment: `${eventConfig?.credit.name} ${totalPrice}개를 사용해 "${item.name}" ${amount}개를 획득했습니다.`, + }); + await transaction.save(); - // 4단계: 랜덤박스인 경우 아이템을 추첨합니다. - if (item.itemType !== 3) return res.json({ result: true }); + // 4단계: 퀘스트를 완료 처리합니다. + await contracts.completeItemPurchaseQuest( + req.userOid, + transaction.createdAt + ); - const randomItem = await getRandomItem(req, 0); - if (!randomItem) { - // 랜덤박스가 실패한 경우, 상태를 구매 이전으로 되돌립니다. - // TODO: Transactions 도입 후 이 코드는 삭제합니다. - logger.info(`User ${req.userOid}'s status will be restored`); + return res.json({ result: true }); + } else { + // 랜덤박스를 구입한 경우 + // 2단계: 대박(40%)인지 쪽박(60%)인지 결정합니다. + const isJackpot = Math.random() < 0.4; + const creditDelta = isJackpot ? totalPrice : -totalPrice; - await transactionModel.deleteOne({ _id: transaction._id }); - await updateEventStatus(req.userOid, { - creditDelta: item.price, - }); - await itemModel.updateOne( - { _id: item._id }, - { - $inc: { - stock: 1, - }, - } - ); + // 3단계: 유저 정보를 업데이트합니다. + await updateEventStatus(req.userOid, { creditDelta }); - logger.info(`User ${req.userOid}'s status was successfully restored`); + // 4단계: 입출금 내역을 추가합니다. + if (isJackpot) { + const transaction = new transactionModel({ + type: "get", + amount: creditDelta, + userId: req.userOid, + itemId: item._id, + itemAmount: amount, + comment: `${eventConfig?.credit.name} ${totalPrice}개를 "${item.name}"에 사용해 대박을 터뜨렸습니다.`, + }); + await transaction.save(); + } else { + const transaction = new transactionModel({ + type: "use", + amount: creditDelta, + userId: req.userOid, + itemId: item._id, + itemAmount: amount, + comment: `${eventConfig?.credit.name} ${totalPrice}개를 "${item.name}"에 사용했지만 쪽박을 맞았습니다.`, + }); + await transaction.save(); + } - return res - .status(500) - .json({ error: "Items/Purchase : random box error" }); + return res.json({ result: true, isJackpot }); } - res.json({ - result: true, - reward: hideItemStock(randomItem), - }); + // const randomItem = await getRandomItem(req, 0); + // 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" }); + // } } catch (err) { logger.error(err); - res.status(500).json({ error: "Items/Purchase : internal server error" }); + res.status(500).json({ error: "Items/purchase : internal server error" }); } }; module.exports = { - listHandler, - purchaseHandler, + getItemsHandler, + getItemLeaderboardHandler, + purchaseItemHandler, }; diff --git a/src/lottery/services/quests.js b/src/lottery/services/quests.js index ae1472bc..5a0c6ae6 100644 --- a/src/lottery/services/quests.js +++ b/src/lottery/services/quests.js @@ -3,20 +3,38 @@ const logger = require("../../modules/logger"); const contracts = require("../modules/contracts"); -const completeHandler = async (req, res) => { +const completeQuestHandler = async (req, res) => { try { const quest = contracts.quests[req.params.questId]; if (!quest || !quest.isApiRequired) - return res.status(400).json({ error: "Quests/Complete: invalid Quest" }); + return res.status(400).json({ error: "Quests/complete: invalid quest" }); + + // 출석 체크 퀘스트는 하루에 1번만 완료하도록 제한합니다. + if (quest.id === "dailyAttendance") { + const todayMidnight = new Date(req.timestamp); + todayMidnight.setHours(0, 0, 0, 0); + + const tomorrowMidnight = new Date(todayMidnight); + tomorrowMidnight.setDate(tomorrowMidnight.getDate() + 1); + + // 오늘 완료된 dailyAttendance 퀘스트가 있는지 확인합니다. + const completedQuest = req.eventStatus.completedQuests.find( + ({ questId, completedAt }) => + questId === quest.id && + completedAt >= todayMidnight && + completedAt < tomorrowMidnight + ); + if (completedQuest) return res.json({ result: false }); + } 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/Complete: internal server error" }); + res.status(500).json({ error: "Quests/complete: internal server error" }); } }; module.exports = { - completeHandler, + completeQuestHandler, }; diff --git a/src/lottery/services/transactions.js b/src/lottery/services/transactions.js index 1d920870..fe976e28 100644 --- a/src/lottery/services/transactions.js +++ b/src/lottery/services/transactions.js @@ -1,25 +1,34 @@ const { transactionModel } = require("../modules/stores/mongo"); +const { + transactionPopulateOption, +} = require("../modules/populates/transactions"); const logger = require("../../modules/logger"); -const hideItemStock = (transaction) => { - if (transaction.item) { - transaction.item.stock = transaction.item.stock > 0 ? 1 : 0; +const formatTransaction = (transaction) => { + if (transaction.itemId) { + transaction.item = transaction.itemId; + delete transaction.itemId; } return transaction; }; const getUserTransactionsHandler = async (req, res) => { try { - // userId는 이미 Frontend에서 알고 있고, 중복되는 값이므로 제외합니다. const transactions = await transactionModel - .find({ userId: req.userOid }, "_id type amount questId comment createAt") + .find( + { userId: req.userOid }, + "type amount questId itemId comment createdAt" + ) + .populate(transactionPopulateOption) .lean(); - if (transactions) - res.json({ - transactions, - }); - else - res.status(500).json({ error: "Transactions/ : internal server error" }); + if (!transactions) + return res + .status(500) + .json({ error: "Transactions/ : internal server error" }); + + res.json({ + transactions: transactions.map(formatTransaction), + }); } catch (err) { logger.error(err); res.status(500).json({ error: "Transactions/ : internal server error" }); diff --git a/src/modules/fare.js b/src/modules/fare.js index b2da72a5..350b8207 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -32,8 +32,8 @@ const scaledTime = (time) => { const initializeDatabase = async () => { try { if ( - naverMapApi["X-NCP-APIGW-API-KEY"] === undefined || - naverMapApi["X-NCP-APIGW-API-KEY-ID"] === undefined + !naverMapApi["X-NCP-APIGW-API-KEY"] || + !naverMapApi["X-NCP-APIGW-API-KEY-ID"] ) { logger.error( "There is no credential for Naver Map. Taxi Fare functions are disabled." @@ -60,7 +60,7 @@ const initializeDatabase = async () => { { fare: true } ) .lean() - ).fare; + )?.fare; const fare = prevTaxiFare ? prevTaxiFare : await callTaxiFare(from, to); @@ -126,8 +126,8 @@ const initializeDatabase = async () => { */ const updateTaxiFare = async (sTime, isMajor) => { if ( - naverMapApi["X-NCP-APIGW-API-KEY"] === undefined || - naverMapApi["X-NCP-APIGW-API-KEY-ID"] === undefined + !naverMapApi["X-NCP-APIGW-API-KEY"] || + !naverMapApi["X-NCP-APIGW-API-KEY-ID"] ) { logger.error( "There is no credential for Naver Map. Taxi Fare functions are disabled." @@ -145,22 +145,12 @@ const updateTaxiFare = async (sTime, isMajor) => { const to = await locationModel.findOne({ _id: item.to }); await acc; - await callTaxiFare - .catch((err) => { - logger.error(err.message); - }) + await callTaxiFare(from, to) .then(async (fare) => { if (fare) { await taxiFareModel.updateOne( { from: item.from, to: item.to, time: sTime }, - { fare: fare }, - (err, docs) => { - if (err) - logger.error( - "Error occured while updating Taxi Fare document: " + - err.message - ); - } + { fare: fare } ); } }) @@ -179,8 +169,8 @@ const updateTaxiFare = async (sTime, isMajor) => { */ const callTaxiFare = async (from, to) => { if ( - naverMapApi["X-NCP-APIGW-API-KEY"] === undefined || - naverMapApi["X-NCP-APIGW-API-KEY-ID"] === undefined + !naverMapApi["X-NCP-APIGW-API-KEY"] || + !naverMapApi["X-NCP-APIGW-API-KEY-ID"] ) { logger.error( "There is no credential for Naver Map. Taxi Fare functions are disabled." @@ -189,12 +179,7 @@ const callTaxiFare = async (from, to) => { } return ( await axios.get( - `${ - "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=" + - from.longitude + - "," + - from.latitude - }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, + `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${from.longitude},${from.latitude}}&goal=${to.longitude},${to.latitude}&options=traoptimal`, { headers: naverMapApi } ) ).data.route.traoptimal[0].summary.taxiFare; diff --git a/src/sampleGenerator/sampleData.json b/src/sampleGenerator/sampleData.json index 546812cd..b2e84816 100644 --- a/src/sampleGenerator/sampleData.json +++ b/src/sampleGenerator/sampleData.json @@ -45,8 +45,8 @@ { "koName": "대전복합터미널", "enName": "Daejeon Terminal Complex", - "longitude": 127.350161, - "latitude": 36.362785 + "longitude": 127.436880, + "latitude": 36.349766 }, { "koName": "만년중학교", diff --git a/src/services/fare.js b/src/services/fare.js index 55d1ad33..638faa65 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -21,14 +21,14 @@ const naverMapApi = { const getTaxiFareHandler = async (req, res) => { try { if ( - naverMapApi["X-NCP-APIGW-API-KEY"] === false || - naverMapApi["X-NCP-APIGW-API-KEY-ID"] === false + !naverMapApi["X-NCP-APIGW-API-KEY"] || + !naverMapApi["X-NCP-APIGW-API-KEY-ID"] ) { - res.status(503).json({ + return res.status(503).json({ error: "fare/getTaxiFareHandler: Naver Map API credential not found", }); - return; } + const from = await locationModel .findOne({ _id: { $eq: req.query.from }, @@ -40,46 +40,21 @@ const getTaxiFareHandler = async (req, res) => { const sTime = scaledTime(new Date(req.query.time)); if (!from || !to) { - res + return res .status(400) .json({ error: "fare/getTaxiFareHandler: Wrong location" }); - return; + } else if (req.query.from === req.query.to) { + // 프론트엔드에서 예상 택시비를 숨기기 위해 0원을 반환 + return res.status(200).json({ fare: 0 }); } - const isMajor = ( - await taxiFareModel - .findOne( - { from: from._id, to: to._id, time: 0 }, - { isMajor: true }, - (err, docs) => { - if (err) - logger.error( - "Error occured while finding Taxi Fare documents: " + - err.message - ); - } - ) - .lean() - ).isMajor; - // 시간대별 정보 관리 (현재: 카이스트 본원 <-> 대전역) - if (isMajor) { - const taxiFare = await taxiFareModel - .findOne( - { - from: from._id, - to: to._id, - time: sTime, - }, - (err, docs) => { - if (err) - logger.error( - "Error occured while finding Taxi Fare documents: " + - err.message - ); - } - ) - .lean(); + + const fare = await taxiFareModel + .findOne({ from: from._id, to: to._id, time: sTime }) + .lean(); + // 해당 sTime 대로 값이 존재하는 경우 (현재: 카이스트 본원 <-> 대전역) + if (fare) { //만일 초기화 되지 않은 시간대의 정보를 필요로하는 비상시의 경우 대비 - if (!taxiFare || taxiFare.fare <= 0) { + if (fare.fare <= 0) { await callTaxiFare(from, to) .then((fare) => { res.status(200).json({ fare: fare }); @@ -88,27 +63,19 @@ const getTaxiFareHandler = async (req, res) => { logger.error(err.message); }); } else { - res.status(200).json({ fare: taxiFare.fare }); + res.status(200).json({ fare: fare.fare }); } } else { - const taxiFare = await taxiFareModel - .findOne( - { - from: from._id, - to: to._id, - time: 0, - }, - (err, docs) => { - if (err) - logger.error( - "Error occured while finding Taxi Fare documents: " + - err.message - ); - } - ) + const minorTaxiFare = await taxiFareModel + .findOne({ + from: from._id, + to: to._id, + time: 48 * new Date(req.query.time).getDay() + 0, + }) .lean(); + //만일 초기화 되지 않은 시간대의 정보를 필요로하는 비상시의 경우 대비 - if (!taxiFare || taxiFare.fare <= 0) { + if (!minorTaxiFare || minorTaxiFare.fare <= 0) { await callTaxiFare(from, to) .then((fare) => { res.status(200).json({ fare: fare }); @@ -117,7 +84,7 @@ const getTaxiFareHandler = async (req, res) => { logger.error(err.message); }); } else { - res.status(200).json({ fare: taxiFare.fare }); + res.status(200).json({ fare: minorTaxiFare.fare }); } } } catch (err) { diff --git a/src/services/rooms.js b/src/services/rooms.js index e9beb5fb..d2626d72 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -611,12 +611,7 @@ const commitSettlementHandler = async (req, res) => { }); // 이벤트 코드입니다. - await contracts?.completePayingQuest( - req.userOid, - req.timestamp, - roomObject - ); - await contracts?.completePayingAndSendingQuest( + await contracts?.completeFareSettlementQuest( req.userOid, req.timestamp, roomObject @@ -689,12 +684,7 @@ const commitPaymentHandler = async (req, res) => { }); // 이벤트 코드입니다. - await contracts?.completeSendingQuest( - req.userOid, - req.timestamp, - roomObject - ); - await contracts?.completePayingAndSendingQuest( + await contracts?.completeFarePaymentQuest( req.userOid, req.timestamp, roomObject From 56bd968a9aeae504175290acecc2d0bcad3da07c Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Mon, 2 Sep 2024 23:22:47 +0900 Subject: [PATCH 07/25] Add: modulize validation of service ban --- src/modules/ban.js | 43 +++++++++++++++++++++++++++++++++++++------ src/services/rooms.js | 22 +++++++--------------- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/modules/ban.js b/src/modules/ban.js index 290e8f86..9be3aeb2 100644 --- a/src/modules/ban.js +++ b/src/modules/ban.js @@ -1,7 +1,23 @@ const logger = require("./logger"); const { banModel } = require("./stores/mongo"); -const getMaxValidServiceBanRecord = async (req) => { +/** + * + * @param {*} req + * @param {String} service + */ +const validateServiceBanRecord = async (req, service) => { + switch (service) { + case "Rooms/create": + case "Rooms/join": + var _serviceName = "service"; + break; + default: + logger.error( + "Error occured while validateServiceBanRecord: given service is not defined." + ); + return; + } try { // 현재 시각이 expireAt 보다 작고, 본인인 경우(ban의 userId가 userId랑 같은 경우) 중 serviceName이 "service"인 record를 모두 가져옴 const bans = await banModel.find({ @@ -9,25 +25,40 @@ const getMaxValidServiceBanRecord = async (req) => { expireAt: { $gte: req.timestamp, }, - serviceName: "service", + serviceName: _serviceName, }); + var banRecord = undefined; if (bans.length > 0) { // 가장 expireAt이 큰 정지 기록만 반환함. - const latestBan = bans.reduce( + var banRecord = bans.reduce( (max, ban) => (ban.expireAt > max.expireAt ? ban : max), bans[0] ); - return latestBan; } - return; } catch (err) { logger.error( "Error occured while getValidServiceBanRecord: " + err.message ); return; } + if (banRecord != undefined) { + const formattedExpireAt = banRecord.expireAt + .toISOString() + .replace("T", " ") + .split(".")[0]; + switch (service) { + case "Rooms/create": + var banErrorMessage = `Rooms/create : user ${req.userId} (${req.session.loginInfo.sid}) is temporarily restricted from creating rooms until ${formattedExpireAt}.`; + break; + case "Rooms/join": + var banErrorMessage = `Rooms/join : user ${req.userId} (${req.session.loginInfo.sid}) is temporarily restricted from joining rooms until ${formattedExpireAt}.`; + break; + } + return banErrorMessage; + } + return; }; module.exports = { - getMaxValidServiceBanRecord, + validateServiceBanRecord, }; diff --git a/src/services/rooms.js b/src/services/rooms.js index d2626d72..495a7dc9 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -21,21 +21,17 @@ const eventPeriod = eventConfig && { endAt: new Date(eventConfig.period.endAt), }; const { contracts } = require("../lottery"); -const { getMaxValidServiceBanRecord } = require("../modules/ban"); +const { validateServiceBanRecord } = require("../modules/ban"); const createHandler = async (req, res) => { const { name, from, to, time, maxPartLength } = req.body; try { // 사용자가 방 생성 기능에 대하여 이용 정지 상태인지 확인합니다. - const banRecord = await getMaxValidServiceBanRecord(req); - if (banRecord != undefined) { - const formattedExpireAt = banRecord.expireAt - .toISOString() - .replace("T", " ") - .split(".")[0]; + const banErrorMessage = await validateServiceBanRecord(req, "Rooms/create"); + if (banErrorMessage != undefined) { return res.status(400).json({ - error: `Rooms/create : user ${req.userId} is temporarily restricted from creating rooms until ${formattedExpireAt}.`, + error: banErrorMessage, }); } @@ -251,14 +247,10 @@ const joinHandler = async (req, res) => { .populate("ongoingRoom"); // 사용자가 방 참여 기능에 대하여 이용 정지 상태인지 확인합니다. - const banRecord = await getMaxValidServiceBanRecord(req); - if (banRecord != undefined) { - const formattedExpireAt = banRecord.expireAt - .toISOString() - .replace("T", " ") - .split(".")[0]; + const banErrorMessage = await validateServiceBanRecord(req, "Rooms/join"); + if (banErrorMessage != undefined) { return res.status(400).json({ - error: `Rooms/join : user ${req.userId} is temporarily restricted from joining rooms until ${formattedExpireAt}.`, + error: banErrorMessage, }); } From 7eceae0e9582385dc031560d353fd57e041cc639 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 3 Sep 2024 21:19:48 +0900 Subject: [PATCH 08/25] Add: resolve comments --- src/modules/ban.js | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/modules/ban.js b/src/modules/ban.js index 9be3aeb2..d86c2a72 100644 --- a/src/modules/ban.js +++ b/src/modules/ban.js @@ -7,16 +7,19 @@ const { banModel } = require("./stores/mongo"); * @param {String} service */ const validateServiceBanRecord = async (req, service) => { + let _serviceName = undefined; + let banRecord = undefined; switch (service) { case "Rooms/create": case "Rooms/join": - var _serviceName = "service"; + _serviceName = "service"; break; - default: - logger.error( - "Error occured while validateServiceBanRecord: given service is not defined." - ); - return; + } + if (_serviceName === undefined) { + logger.error( + "Error occured while validateServiceBanRecord: given service is not defined." + ); + return; } try { // 현재 시각이 expireAt 보다 작고, 본인인 경우(ban의 userId가 userId랑 같은 경우) 중 serviceName이 "service"인 record를 모두 가져옴 @@ -27,10 +30,9 @@ const validateServiceBanRecord = async (req, service) => { }, serviceName: _serviceName, }); - var banRecord = undefined; if (bans.length > 0) { // 가장 expireAt이 큰 정지 기록만 반환함. - var banRecord = bans.reduce( + banRecord = bans.reduce( (max, ban) => (ban.expireAt > max.expireAt ? ban : max), bans[0] ); @@ -41,19 +43,12 @@ const validateServiceBanRecord = async (req, service) => { ); return; } - if (banRecord != undefined) { + if (banRecord !== undefined) { const formattedExpireAt = banRecord.expireAt .toISOString() .replace("T", " ") .split(".")[0]; - switch (service) { - case "Rooms/create": - var banErrorMessage = `Rooms/create : user ${req.userId} (${req.session.loginInfo.sid}) is temporarily restricted from creating rooms until ${formattedExpireAt}.`; - break; - case "Rooms/join": - var banErrorMessage = `Rooms/join : user ${req.userId} (${req.session.loginInfo.sid}) is temporarily restricted from joining rooms until ${formattedExpireAt}.`; - break; - } + const banErrorMessage = `${service} : user ${req.userId} (${req.session.loginInfo.sid}) is temporarily restricted from service until ${formattedExpireAt}.`; return banErrorMessage; } return; From eecc5f67b66150119f5d9b689d509f84f4811020 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 3 Sep 2024 22:55:43 +0900 Subject: [PATCH 09/25] Add: resolve comments --- src/modules/ban.js | 21 ++++++++++----------- src/services/rooms.js | 4 ++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/modules/ban.js b/src/modules/ban.js index d86c2a72..939b7d9f 100644 --- a/src/modules/ban.js +++ b/src/modules/ban.js @@ -23,19 +23,18 @@ const validateServiceBanRecord = async (req, service) => { } try { // 현재 시각이 expireAt 보다 작고, 본인인 경우(ban의 userId가 userId랑 같은 경우) 중 serviceName이 "service"인 record를 모두 가져옴 - const bans = await banModel.find({ - userSid: req.session.loginInfo.sid, - expireAt: { - $gte: req.timestamp, - }, - serviceName: _serviceName, - }); + const bans = await banModel + .find({ + userSid: req.session.loginInfo.sid, + expireAt: { + $gte: req.timestamp, + }, + serviceName: _serviceName, + }) + .sort({ expireAt: -1 }); if (bans.length > 0) { // 가장 expireAt이 큰 정지 기록만 반환함. - banRecord = bans.reduce( - (max, ban) => (ban.expireAt > max.expireAt ? ban : max), - bans[0] - ); + banRecord = bans[0]; } } catch (err) { logger.error( diff --git a/src/services/rooms.js b/src/services/rooms.js index 495a7dc9..0de9064f 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -29,7 +29,7 @@ const createHandler = async (req, res) => { try { // 사용자가 방 생성 기능에 대하여 이용 정지 상태인지 확인합니다. const banErrorMessage = await validateServiceBanRecord(req, "Rooms/create"); - if (banErrorMessage != undefined) { + if (banErrorMessage !== undefined) { return res.status(400).json({ error: banErrorMessage, }); @@ -248,7 +248,7 @@ const joinHandler = async (req, res) => { // 사용자가 방 참여 기능에 대하여 이용 정지 상태인지 확인합니다. const banErrorMessage = await validateServiceBanRecord(req, "Rooms/join"); - if (banErrorMessage != undefined) { + if (banErrorMessage !== undefined) { return res.status(400).json({ error: banErrorMessage, }); From 91516934dff7b1c88da344c15e8c45dc43eb83df Mon Sep 17 00:00:00 2001 From: hwmin414 Date: Tue, 3 Sep 2024 23:11:29 +0900 Subject: [PATCH 10/25] feat: add item handler --- src/lottery/routes/docs/schemas/itemsSchema.js | 3 +++ src/lottery/routes/items.js | 5 +++++ src/lottery/services/items.js | 14 ++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/src/lottery/routes/docs/schemas/itemsSchema.js b/src/lottery/routes/docs/schemas/itemsSchema.js index 7e570b5a..d224ba70 100644 --- a/src/lottery/routes/docs/schemas/itemsSchema.js +++ b/src/lottery/routes/docs/schemas/itemsSchema.js @@ -3,6 +3,9 @@ const { zodToSchemaObject } = require("../../../../routes/docs/utils"); const { objectId } = require("../../../../modules/patterns"); const itemsZod = { + getItemHandler: z.object({ + itemId: z.string().regex(objectId), + }), getItemLeaderboardHandler: z.object({ itemId: z.string().regex(objectId), }), diff --git a/src/lottery/routes/items.js b/src/lottery/routes/items.js index 135617b8..0de8be18 100644 --- a/src/lottery/routes/items.js +++ b/src/lottery/routes/items.js @@ -6,6 +6,11 @@ const { itemsZod } = require("./docs/schemas/itemsSchema"); const itemsHandlers = require("../services/items"); router.get("/", itemsHandlers.getItemsHandler); +router.get( + "/:itemId", + validateParams(itemsZod.getItemHandler), + itemsHandlers.getItemHandler +); router.get( "/leaderboard/:itemId", validateParams(itemsZod.getItemLeaderboardHandler), diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 9196ad53..94cc5e86 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -25,6 +25,19 @@ const getItemsHandler = async (req, res) => { } }; +const getItemHandler = async (req, res) => { + try { + const { itemId } = req.params; + const item = await itemModel.findById(itemId).lean(); + if (!item) return res.status(400).json({ error: "Items/ : invalid item" }); + + res.json({ item }); + } catch (err) { + logger.error(err); + res.status(500).json({ error: "Items/ : internal server error" }); + } +}; + // 유도 과정은 services/publicNotice.js 파일에 정의된 calculateProbabilityV2 함수의 주석 참조 const calculateWinProbability = (realStock, users, amount, totalAmount) => { if (users.length <= realStock) return 1; @@ -363,6 +376,7 @@ const purchaseItemHandler = async (req, res) => { module.exports = { getItemsHandler, + getItemHandler, getItemLeaderboardHandler, purchaseItemHandler, }; From ab696255998f5cae7da0bf4ce4d6e520dfd41fa0 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 3 Sep 2024 23:37:54 +0900 Subject: [PATCH 11/25] Add: resolve comments --- src/routes/users.js | 2 +- src/services/users.js | 28 +++++++++++++++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/routes/users.js b/src/routes/users.js index d5366155..faba91e1 100755 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -57,7 +57,7 @@ router.get("/editProfileImg/done", userHandlers.editProfileImgDoneHandler); router.get("/resetProfileImg", userHandlers.resetProfileImgHandler); // 유저의 현재 유효한 서비스 정지 기록들만 반환합니다. -router.get("/isBanned", userHandlers.isBannedHandler); +router.get("/getValidBanRecord", userHandlers.getValidBanRecordHandler); // 유저의 서비스 정지 기록들을 모두 반환합니다. router.get("/getBanRecord", userHandlers.getBanRecordHandler); diff --git a/src/services/users.js b/src/services/users.js index 5d8ee632..ab73c8e1 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -200,15 +200,17 @@ const resetProfileImgHandler = async (req, res) => { } }; -const isBannedHandler = async (req, res) => { +const getValidBanRecordHandler = async (req, res) => { try { - // 현재 시각이 expireAt 보다 작고 본인인 경우(ban의 userId가 userOid랑 같은 경우)의 record를 모두 가져옴 - const result = await banModel.find({ - userId: req.userOid, - expireAt: { - $gte: req.timestamp, - }, - }); + // 현재 시각이 expireAt 보다 작고 본인인 경우(ban의 userId가 userSid랑 같은 경우)의 record를 모두 가져옴 + const result = await banModel + .find({ + userSid: req.session.loginInfo.sid, + expireAt: { + $gte: req.timestamp, + }, + }) + .sort({ expireAt: -1 }); if (!result) return res.status(500).send("Users/isBanned : internal server error"); res.status(200).json(result); @@ -219,8 +221,12 @@ const isBannedHandler = async (req, res) => { const getBanRecordHandler = async (req, res) => { try { - // 본인인 경우(ban의 userId가 userOid랑 같은 경우)의 record를 모두 가져옴 - const result = await banModel.find({ userId: req.userOid }); + // 본인인 경우(ban의 userId가 userSid랑 같은 경우)의 record를 모두 가져옴 + const result = await banModel + .find({ + userSid: req.session.loginInfo.sid, + }) + .sort({ expireAt: -1 }); if (!result) return res.status(500).send("Users/getBanRecord : internal server error"); res.status(200).json(result); @@ -238,6 +244,6 @@ module.exports = { editProfileImgDoneHandler, resetNicknameHandler, resetProfileImgHandler, - isBannedHandler, + getValidBanRecordHandler, getBanRecordHandler, }; From 9d47b87a00badfabb8366ac07c9e23691f1ff551 Mon Sep 17 00:00:00 2001 From: hwmin414 Date: Tue, 3 Sep 2024 23:54:15 +0900 Subject: [PATCH 12/25] feat: unused field remove && update doc --- src/lottery/routes/docs/items.js | 82 ++++++++++++++++++++++++++++++++ src/lottery/services/items.js | 8 +++- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/lottery/routes/docs/items.js b/src/lottery/routes/docs/items.js index 06a6c118..71b866ea 100644 --- a/src/lottery/routes/docs/items.js +++ b/src/lottery/routes/docs/items.js @@ -82,6 +82,83 @@ itemsDocs[`${apiPrefix}/`] = { }, }, }; +itemsDocs[`${apiPrefix}/:itemId`] = { + get: { + tags: [`${apiPrefix}`], + summary: "상점에서 판매하는 특정 상품의 정보 반환", + description: "상점에서 판매하는 특정 상품의 정보를 가져옵니다.", + parameters: [ + { + in: "path", + name: "itemId", + required: true, + description: "상품 정보를 조회할 ObjectId", + example: "ITEM ID", + }, + ], + responses: { + 200: { + content: { + "application/json": { + schema: { + type: "object", + required: ["items"], + properties: { + item: { + type: "object", + description: "상품의 정보", + properties: { + _id: { + type: "string", + description: "상품의 ObjectId", + example: "ITEM ID", + }, + name: { + type: "string", + description: "상품의 이름", + example: "진짜 송편", + }, + description: { + type: "string", + description: "상품의 설명", + example: "먹을 수 있는 송편입니다.", + }, + imageUrl: { + type: "string", + description: "상품의 썸네일 이미지 URL", + example: "THUMBNAIL URL", + }, + instagramStoryStickerImageUrl: { + type: "string", + description: "인스타그램 스토리 스티커 이미지 URL", + example: "STICKER URL", + }, + price: { + type: "number", + description: "상품의 가격. 0 이상의 정수입니다.", + example: 400, + }, + isDisabled: { + type: "boolean", + description: "상품의 판매 중지 여부", + example: false, + }, + itemType: { + type: "number", + description: + "상품의 유형. 0: 일반 상품, 1: 일반 티켓, 2: 고급 티켓, 3: 랜덤박스입니다.", + example: 0, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}; itemsDocs[`${apiPrefix}/leaderboard/{itemId}`] = { get: { tags: [`${apiPrefix}`], @@ -136,6 +213,11 @@ itemsDocs[`${apiPrefix}/leaderboard/{itemId}`] = { description: "유저가 상품에 당첨될 확률", example: 0.1, }, + rank: { + type: "number", + description: "순위", + example: 1, + }, }, }, }, diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 94cc5e86..eb00535d 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -28,7 +28,12 @@ const getItemsHandler = async (req, res) => { const getItemHandler = async (req, res) => { try { const { itemId } = req.params; - const item = await itemModel.findById(itemId).lean(); + const item = await itemModel + .findById( + itemId, + "_id name description imageUrl instagramStoryStickerImageUrl price isDisabled itemType" + ) + .lean(); if (!item) return res.status(400).json({ error: "Items/ : invalid item" }); res.json({ item }); @@ -126,6 +131,7 @@ const getItemLeaderboardHandler = async (req, res) => { profileImageUrl: userInfo.profileImageUrl, amount: user.amount, probability: user.probability, + rank: user.rank, }; }) ); From dde8965a7b49ea007fc2189ef61b1e97b623a4dc Mon Sep 17 00:00:00 2001 From: hwmin414 Date: Wed, 4 Sep 2024 00:09:40 +0900 Subject: [PATCH 13/25] fix: minor doc update --- src/lottery/routes/docs/items.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lottery/routes/docs/items.js b/src/lottery/routes/docs/items.js index 71b866ea..7f473d15 100644 --- a/src/lottery/routes/docs/items.js +++ b/src/lottery/routes/docs/items.js @@ -82,7 +82,7 @@ itemsDocs[`${apiPrefix}/`] = { }, }, }; -itemsDocs[`${apiPrefix}/:itemId`] = { +itemsDocs[`${apiPrefix}/{itemId}`] = { get: { tags: [`${apiPrefix}`], summary: "상점에서 판매하는 특정 상품의 정보 반환", @@ -102,7 +102,7 @@ itemsDocs[`${apiPrefix}/:itemId`] = { "application/json": { schema: { type: "object", - required: ["items"], + required: ["item"], properties: { item: { type: "object", From 4d487c6f16210e1ee6206b87476c2a04cdda817b Mon Sep 17 00:00:00 2001 From: hwmin414 Date: Wed, 4 Sep 2024 00:14:46 +0900 Subject: [PATCH 14/25] fix: minor doc fix --- src/lottery/routes/docs/items.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lottery/routes/docs/items.js b/src/lottery/routes/docs/items.js index 7f473d15..063e5f45 100644 --- a/src/lottery/routes/docs/items.js +++ b/src/lottery/routes/docs/items.js @@ -106,6 +106,15 @@ itemsDocs[`${apiPrefix}/{itemId}`] = { properties: { item: { type: "object", + required: [ + "_id", + "name", + "description", + "imageUrl", + "price", + "isDisabled", + "itemType", + ], description: "상품의 정보", properties: { _id: { @@ -191,6 +200,7 @@ itemsDocs[`${apiPrefix}/leaderboard/{itemId}`] = { "profileImageUrl", "amount", "probability", + "rank", ], properties: { nickname: { From b341a3395f72dc392d1edc86a9c4b3dba36e3452 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 4 Sep 2024 00:26:41 +0900 Subject: [PATCH 15/25] Add: ban middleware --- src/middlewares/ban.js | 21 +++++++++++++++++++++ src/modules/ban.js | 18 +++--------------- src/routes/rooms.js | 3 +++ src/services/rooms.js | 16 ---------------- 4 files changed, 27 insertions(+), 31 deletions(-) create mode 100644 src/middlewares/ban.js diff --git a/src/middlewares/ban.js b/src/middlewares/ban.js new file mode 100644 index 00000000..ee354ee8 --- /dev/null +++ b/src/middlewares/ban.js @@ -0,0 +1,21 @@ +const { validateServiceBanRecord } = require("../modules/ban"); + +const banMiddleware = async (req, res, next) => { + console.log(`req.originalUrl: ${req.originalUrl}`); + const serviceMapper = { + "/rooms/create": "service", + "/rooms/join": "service", + }; + const banErrorMessage = await validateServiceBanRecord( + req, + serviceMapper[req.originalUrl] + ); + if (banErrorMessage !== undefined) { + console.log("banned user"); + return res.status(400).json({ error: banErrorMessage }); + } + console.log("next()"); + next(); +}; + +module.exports = banMiddleware; diff --git a/src/modules/ban.js b/src/modules/ban.js index 939b7d9f..859a4a33 100644 --- a/src/modules/ban.js +++ b/src/modules/ban.js @@ -7,20 +7,8 @@ const { banModel } = require("./stores/mongo"); * @param {String} service */ const validateServiceBanRecord = async (req, service) => { - let _serviceName = undefined; let banRecord = undefined; - switch (service) { - case "Rooms/create": - case "Rooms/join": - _serviceName = "service"; - break; - } - if (_serviceName === undefined) { - logger.error( - "Error occured while validateServiceBanRecord: given service is not defined." - ); - return; - } + try { // 현재 시각이 expireAt 보다 작고, 본인인 경우(ban의 userId가 userId랑 같은 경우) 중 serviceName이 "service"인 record를 모두 가져옴 const bans = await banModel @@ -29,7 +17,7 @@ const validateServiceBanRecord = async (req, service) => { expireAt: { $gte: req.timestamp, }, - serviceName: _serviceName, + serviceName: service, }) .sort({ expireAt: -1 }); if (bans.length > 0) { @@ -47,7 +35,7 @@ const validateServiceBanRecord = async (req, service) => { .toISOString() .replace("T", " ") .split(".")[0]; - const banErrorMessage = `${service} : user ${req.userId} (${req.session.loginInfo.sid}) is temporarily restricted from service until ${formattedExpireAt}.`; + const banErrorMessage = `${req.originalUrl} : user ${req.userId} (${req.session.loginInfo.sid}) is temporarily restricted from service until ${formattedExpireAt}.`; return banErrorMessage; } return; diff --git a/src/routes/rooms.js b/src/routes/rooms.js index 6345fa6f..1076e39b 100644 --- a/src/routes/rooms.js +++ b/src/routes/rooms.js @@ -43,6 +43,9 @@ router.get( roomHandlers.infoHandler ); +// 방 생성/참여전 ban 여부 확인 +router.use(require("../middlewares/ban")); + // JSON으로 받은 정보로 방을 생성한다. router.post( "/create", diff --git a/src/services/rooms.js b/src/services/rooms.js index 0de9064f..8b15f72d 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -27,14 +27,6 @@ const createHandler = async (req, res) => { const { name, from, to, time, maxPartLength } = req.body; try { - // 사용자가 방 생성 기능에 대하여 이용 정지 상태인지 확인합니다. - const banErrorMessage = await validateServiceBanRecord(req, "Rooms/create"); - if (banErrorMessage !== undefined) { - return res.status(400).json({ - error: banErrorMessage, - }); - } - if (from === to) { return res.status(400).json({ error: "Rooms/create : locations are same", @@ -246,14 +238,6 @@ const joinHandler = async (req, res) => { .findOne({ id: req.userId }) .populate("ongoingRoom"); - // 사용자가 방 참여 기능에 대하여 이용 정지 상태인지 확인합니다. - const banErrorMessage = await validateServiceBanRecord(req, "Rooms/join"); - if (banErrorMessage !== undefined) { - return res.status(400).json({ - error: banErrorMessage, - }); - } - // 사용자의 참여중인 진행중인 방이 5개 이상이면 오류를 반환합니다. if (user.ongoingRoom.length >= 5) { return res.status(400).json({ From 7dd207396f58af50332626a76b06025475f27a1a Mon Sep 17 00:00:00 2001 From: static Date: Wed, 4 Sep 2024 11:26:22 +0900 Subject: [PATCH 16/25] Add: realStock field in item info endpoint --- src/lottery/routes/docs/items.js | 24 +++++++----------------- src/lottery/services/items.js | 7 ++----- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/lottery/routes/docs/items.js b/src/lottery/routes/docs/items.js index 063e5f45..8ca28359 100644 --- a/src/lottery/routes/docs/items.js +++ b/src/lottery/routes/docs/items.js @@ -23,7 +23,6 @@ itemsDocs[`${apiPrefix}/`] = { required: [ "_id", "name", - "description", "imageUrl", "price", "isDisabled", @@ -40,21 +39,11 @@ itemsDocs[`${apiPrefix}/`] = { description: "상품의 이름", example: "진짜 송편", }, - description: { - type: "string", - description: "상품의 설명", - example: "먹을 수 있는 송편입니다.", - }, imageUrl: { type: "string", description: "상품의 썸네일 이미지 URL", example: "THUMBNAIL URL", }, - instagramStoryStickerImageUrl: { - type: "string", - description: "인스타그램 스토리 스티커 이미지 URL", - example: "STICKER URL", - }, price: { type: "number", description: "상품의 가격. 0 이상의 정수입니다.", @@ -92,7 +81,7 @@ itemsDocs[`${apiPrefix}/{itemId}`] = { in: "path", name: "itemId", required: true, - description: "상품 정보를 조회할 ObjectId", + description: "정보를 조회할 상품의 ObjectId", example: "ITEM ID", }, ], @@ -114,6 +103,7 @@ itemsDocs[`${apiPrefix}/{itemId}`] = { "price", "isDisabled", "itemType", + "realStock", ], description: "상품의 정보", properties: { @@ -137,11 +127,6 @@ itemsDocs[`${apiPrefix}/{itemId}`] = { description: "상품의 썸네일 이미지 URL", example: "THUMBNAIL URL", }, - instagramStoryStickerImageUrl: { - type: "string", - description: "인스타그램 스토리 스티커 이미지 URL", - example: "STICKER URL", - }, price: { type: "number", description: "상품의 가격. 0 이상의 정수입니다.", @@ -158,6 +143,11 @@ itemsDocs[`${apiPrefix}/{itemId}`] = { "상품의 유형. 0: 일반 상품, 1: 일반 티켓, 2: 고급 티켓, 3: 랜덤박스입니다.", example: 0, }, + realStock: { + type: "number", + description: "상품의 실제 재고", + example: 30, + }, }, }, }, diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index eb00535d..33c95c58 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -13,10 +13,7 @@ const contracts = require("../modules/contracts"); const getItemsHandler = async (req, res) => { try { const items = await itemModel - .find( - {}, - "_id name description imageUrl instagramStoryStickerImageUrl price isDisabled itemType" - ) + .find({}, "_id name imageUrl price isDisabled itemType") .lean(); res.json({ items }); } catch (err) { @@ -31,7 +28,7 @@ const getItemHandler = async (req, res) => { const item = await itemModel .findById( itemId, - "_id name description imageUrl instagramStoryStickerImageUrl price isDisabled itemType" + "_id name description imageUrl price isDisabled itemType realStock" ) .lean(); if (!item) return res.status(400).json({ error: "Items/ : invalid item" }); From 12f9bcf65c1ddba14276c67517a554f9f3591a4d Mon Sep 17 00:00:00 2001 From: static Date: Wed, 4 Sep 2024 11:35:50 +0900 Subject: [PATCH 17/25] Fix: quest can be achieved infinitely --- src/lottery/modules/quests.js | 2 +- src/lottery/routes/docs/globalState.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index 0d79ac81..6bf162fb 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -85,7 +85,7 @@ const completeQuest = async (userId, timestamp, quest) => { // 3단계: 유저의 퀘스트 완료 횟수를 확인합니다. // maxCount가 0인 경우, 무제한으로 퀘스트를 완료할 수 있습니다. const questCount = eventStatus.completedQuests.filter( - (completedQuestId) => completedQuestId === quest.id + ({ questId }) => questId === quest.id ).length; if (quest.maxCount > 0 && questCount >= quest.maxCount) { logger.info( diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 1bbf23f4..44b62384 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -103,9 +103,9 @@ globalStateDocs[`${apiPrefix}/`] = { "유저가 완료한 퀘스트의 배열. 여러 번 완료한 퀘스트의 경우 배열 내에 같은 퀘스트가 여러 번 포함됩니다.", items: { type: "object", - required: ["id", "completedAt"], + required: ["questId", "completedAt"], properties: { - id: { + questId: { type: "string", description: "퀘스트의 Id", example: "QUEST ID", From 074d3572a33f309e1890026457ac2eb1666e066a Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 4 Sep 2024 15:16:55 +0900 Subject: [PATCH 18/25] Add: resolve comments --- src/middlewares/ban.js | 13 +++++-------- src/routes/rooms.js | 6 +++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/middlewares/ban.js b/src/middlewares/ban.js index ee354ee8..bd4fd39f 100644 --- a/src/middlewares/ban.js +++ b/src/middlewares/ban.js @@ -1,20 +1,17 @@ const { validateServiceBanRecord } = require("../modules/ban"); +const serviceMapper = new Map([ + ["/rooms/create", "service"], + ["/rooms/join", "service"], +]); const banMiddleware = async (req, res, next) => { - console.log(`req.originalUrl: ${req.originalUrl}`); - const serviceMapper = { - "/rooms/create": "service", - "/rooms/join": "service", - }; const banErrorMessage = await validateServiceBanRecord( req, - serviceMapper[req.originalUrl] + serviceMapper.get(req.originalUrl) ); if (banErrorMessage !== undefined) { - console.log("banned user"); return res.status(400).json({ error: banErrorMessage }); } - console.log("next()"); next(); }; diff --git a/src/routes/rooms.js b/src/routes/rooms.js index 1076e39b..b8bad634 100644 --- a/src/routes/rooms.js +++ b/src/routes/rooms.js @@ -35,6 +35,9 @@ router.get( // 이후 API 접근 시 로그인 필요 router.use(require("../middlewares/auth")); +// 방 생성/참여전 ban 여부 확인 +router.use(require("../middlewares/ban")); + // 특정 id 방 세부사항 보기 router.get( "/info", @@ -43,9 +46,6 @@ router.get( roomHandlers.infoHandler ); -// 방 생성/참여전 ban 여부 확인 -router.use(require("../middlewares/ban")); - // JSON으로 받은 정보로 방을 생성한다. router.post( "/create", From 8da0c4aed63967226a21a509bea2f21b5ffc6f8a Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 4 Sep 2024 15:51:51 +0900 Subject: [PATCH 19/25] Add: resolve comments --- src/middlewares/ban.js | 1 + src/modules/ban.js | 3 +- src/routes/docs/users.js | 67 ++-------------------------------------- src/routes/users.js | 3 -- src/services/users.js | 20 ------------ 5 files changed, 4 insertions(+), 90 deletions(-) diff --git a/src/middlewares/ban.js b/src/middlewares/ban.js index bd4fd39f..f70b9550 100644 --- a/src/middlewares/ban.js +++ b/src/middlewares/ban.js @@ -1,4 +1,5 @@ const { validateServiceBanRecord } = require("../modules/ban"); + const serviceMapper = new Map([ ["/rooms/create", "service"], ["/rooms/join", "service"], diff --git a/src/modules/ban.js b/src/modules/ban.js index 859a4a33..4068db78 100644 --- a/src/modules/ban.js +++ b/src/modules/ban.js @@ -2,7 +2,6 @@ const logger = require("./logger"); const { banModel } = require("./stores/mongo"); /** - * * @param {*} req * @param {String} service */ @@ -26,7 +25,7 @@ const validateServiceBanRecord = async (req, service) => { } } catch (err) { logger.error( - "Error occured while getValidServiceBanRecord: " + err.message + "Error occured while validateServiceBanRecord: " + err.message ); return; } diff --git a/src/routes/docs/users.js b/src/routes/docs/users.js index ec4532a5..0f19ecde 100644 --- a/src/routes/docs/users.js +++ b/src/routes/docs/users.js @@ -330,7 +330,7 @@ usersDocs[`${apiPrefix}/resetProfileImg`] = { }, }; -usersDocs[`${apiPrefix}/isBanned`] = { +usersDocs[`${apiPrefix}/getBanRecord`] = { get: { tags: [tag], summary: "본인의 현재 정지 기록을 가져움", @@ -344,7 +344,7 @@ usersDocs[`${apiPrefix}/isBanned`] = { type: "array", items: { properties: { - userId: { + userSid: { type: "string", description: "사용자의 SSO ID", pattern: "monday-sid", @@ -375,69 +375,6 @@ usersDocs[`${apiPrefix}/isBanned`] = { }, }, }, - 400: { - content: { - "text/html": { - example: "Users/isBanned : there is no ban record", - }, - }, - }, - 500: { - content: { - "text/html": { - example: "Users/isBanned : internal server error", - }, - }, - }, - }, - }, -}; - -usersDocs[`${apiPrefix}/getBanRecord`] = { - get: { - tags: [tag], - summary: "본인의 모든 정지 기록을 가져움", - description: - "정지 기록들 중 본인인 경우에 해당하는 정지 기록을 모두 가져옴", - responses: { - 200: { - content: { - "application/json": { - schema: { - type: "array", - items: { - properties: { - userId: { - type: "string", - description: "사용자의 SSO ID", - example: "monday-sid", - }, - reason: { - type: "string", - description: "정지 사유", - example: "미정산", - }, - bannedAt: { - type: "date", - description: "정지 당한 시각", - example: "2024-05-20 12:00", - }, - expireAt: { - type: "date", - description: "정지 만료 시각", - example: "2024-05-21 12:00", - }, - serviceName: { - type: "string", - description: "정지를 당한 서비스 또는 이벤트 이름", - example: "2023-fall-event", - }, - }, - }, - }, - }, - }, - }, 400: { content: { "text/html": { diff --git a/src/routes/users.js b/src/routes/users.js index faba91e1..de3c0b1d 100755 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -56,9 +56,6 @@ router.get("/editProfileImg/done", userHandlers.editProfileImgDoneHandler); // 프로필 이미지를 기본값으로 재설정합니다. router.get("/resetProfileImg", userHandlers.resetProfileImgHandler); -// 유저의 현재 유효한 서비스 정지 기록들만 반환합니다. -router.get("/getValidBanRecord", userHandlers.getValidBanRecordHandler); - // 유저의 서비스 정지 기록들을 모두 반환합니다. router.get("/getBanRecord", userHandlers.getBanRecordHandler); diff --git a/src/services/users.js b/src/services/users.js index ab73c8e1..8c51c736 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -200,25 +200,6 @@ const resetProfileImgHandler = async (req, res) => { } }; -const getValidBanRecordHandler = async (req, res) => { - try { - // 현재 시각이 expireAt 보다 작고 본인인 경우(ban의 userId가 userSid랑 같은 경우)의 record를 모두 가져옴 - const result = await banModel - .find({ - userSid: req.session.loginInfo.sid, - expireAt: { - $gte: req.timestamp, - }, - }) - .sort({ expireAt: -1 }); - if (!result) - return res.status(500).send("Users/isBanned : internal server error"); - res.status(200).json(result); - } catch (err) { - res.status(500).send("Users/isBanned : internal server error"); - } -}; - const getBanRecordHandler = async (req, res) => { try { // 본인인 경우(ban의 userId가 userSid랑 같은 경우)의 record를 모두 가져옴 @@ -244,6 +225,5 @@ module.exports = { editProfileImgDoneHandler, resetNicknameHandler, resetProfileImgHandler, - getValidBanRecordHandler, getBanRecordHandler, }; From 0729f66180fa7572812ef2288aadb1593774faf8 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 4 Sep 2024 15:58:09 +0900 Subject: [PATCH 20/25] Remove: unnecessary require --- src/services/rooms.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/rooms.js b/src/services/rooms.js index 8b15f72d..0ef798fe 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -21,7 +21,6 @@ const eventPeriod = eventConfig && { endAt: new Date(eventConfig.period.endAt), }; const { contracts } = require("../lottery"); -const { validateServiceBanRecord } = require("../modules/ban"); const createHandler = async (req, res) => { const { name, from, to, time, maxPartLength } = req.body; From eb051110bf71552be49e170baa838ddf88092538 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 5 Sep 2024 01:10:42 +0900 Subject: [PATCH 21/25] Fix: validation error when random box result is jjokbak --- 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 33c95c58..cffd28f7 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -334,7 +334,7 @@ const purchaseItemHandler = async (req, res) => { } else { const transaction = new transactionModel({ type: "use", - amount: creditDelta, + amount: -creditDelta, userId: req.userOid, itemId: item._id, itemAmount: amount, From ec52dff6c01ae8a4a8aa2615825d738aede9ba00 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 5 Sep 2024 01:22:38 +0900 Subject: [PATCH 22/25] Refactor: use same response format in two querying item info endpoints --- src/lottery/routes/docs/items.js | 12 ++++++++++++ src/lottery/services/items.js | 5 ++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/lottery/routes/docs/items.js b/src/lottery/routes/docs/items.js index 8ca28359..28ecd53d 100644 --- a/src/lottery/routes/docs/items.js +++ b/src/lottery/routes/docs/items.js @@ -23,10 +23,12 @@ itemsDocs[`${apiPrefix}/`] = { required: [ "_id", "name", + "description", "imageUrl", "price", "isDisabled", "itemType", + "realStock", ], properties: { _id: { @@ -39,6 +41,11 @@ itemsDocs[`${apiPrefix}/`] = { description: "상품의 이름", example: "진짜 송편", }, + description: { + type: "string", + description: "상품의 설명", + example: "먹을 수 있는 송편입니다.", + }, imageUrl: { type: "string", description: "상품의 썸네일 이미지 URL", @@ -60,6 +67,11 @@ itemsDocs[`${apiPrefix}/`] = { "상품의 유형. 0: 일반 상품, 1: 일반 티켓, 2: 고급 티켓, 3: 랜덤박스입니다.", example: 0, }, + realStock: { + type: "number", + description: "상품의 실제 재고", + example: 30, + }, }, }, }, diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index cffd28f7..07730574 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -13,7 +13,10 @@ const contracts = require("../modules/contracts"); const getItemsHandler = async (req, res) => { try { const items = await itemModel - .find({}, "_id name imageUrl price isDisabled itemType") + .find( + {}, + "_id name description imageUrl price isDisabled itemType realStock" + ) .lean(); res.json({ items }); } catch (err) { From 69565147a776ec2eae227ff76c0a9f6afaeb0e0b Mon Sep 17 00:00:00 2001 From: static Date: Sat, 7 Sep 2024 04:55:22 +0900 Subject: [PATCH 23/25] Refactor: update quest information --- src/lottery/modules/contracts.js | 46 +++++++++++++++++--------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/lottery/modules/contracts.js b/src/lottery/modules/contracts.js index a415b0af..6281bec7 100644 --- a/src/lottery/modules/contracts.js +++ b/src/lottery/modules/contracts.js @@ -15,15 +15,15 @@ const quests = buildQuests({ description: "이벤트 참여만 해도 송편코인을 얻을 수 있다고?? 이벤트 참여에 동의하고 송편코인을 받아 보세요.", imageUrl: - "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_firstLogin.png", + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024fall/quest_firstLogin.png", reward: 200, }, firstRoomCreation: { name: "첫 방 개설", description: - "원하는 택시팟을 찾을 수 없다면? 원하는 조건으로 방 개설 페이지에서 방을 직접 개설해보세요.", + "원하는 택시팟을 찾을 수 없다면? 원하는 조건으로 방 개설 페이지에서 방을 직접 개설해 보세요.", imageUrl: - "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_firstRoomCreation.png", + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024fall/quest_firstRoomCreation.png", reward: 500, }, roomSharing: { @@ -31,7 +31,7 @@ const quests = buildQuests({ description: "방을 공유해 친구들을 택시팟에 초대해 보세요. 채팅창 상단의 햄버거(☰) 버튼을 누르면 공유하기 버튼을 찾을 수 있어요.", imageUrl: - "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_roomSharing.png", + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024fall/quest_roomSharing.png", reward: 500, isApiRequired: true, }, @@ -40,64 +40,68 @@ const quests = buildQuests({ description: "2명 이상과 함께 택시를 타고 택시비를 결제한 후 정산을 요청해 보세요. 정산하기 버튼은 채팅 페이지 좌측 하단의 + 버튼을 눌러 찾을 수 있어요.", imageUrl: - "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_paying.png", + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024fall/quest_fareSettlement.png", reward: 2000, maxCount: 0, }, farePayment: { name: "송금 완료면 I am 신뢰에요", description: - "2명 이상과 함께 택시를 타고 택시비를 결제한 분께 송금해주세요. 송금하기 버튼은 채팅 페이지 좌측 하단의 +버튼을 눌러 확인할 수 있어요.", + "2명 이상과 함께 택시를 타고 택시비를 결제한 분께 송금해 주세요. 송금하기 버튼은 채팅 페이지 좌측 하단의 + 버튼을 눌러 찾을 수 있어요.", imageUrl: - "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_sending.png", + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024fall/quest_farePayment.png", reward: 2000, maxCount: 0, }, nicknameChanging: { name: "닉네임 폼 미쳤다", description: - "닉네임을 변경하여 자신을 표현하세요. 마이페이지수정하기 버튼을 눌러 닉네임을 수정할 수 있어요.", + "닉네임을 변경하여 자신을 표현하세요. 마이 페이지수정하기 버튼을 눌러 닉네임을 수정할 수 있어요.", imageUrl: - "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_nicknameChanging.png", + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024fall/quest_nicknameChanging.png", reward: 500, }, accountChanging: { name: "계좌 등록을 해야 능률이 올라갑니다", description: - "정산하기 기능을 더욱 빠르고 이용할 수 있다고? 계좌번호를 등록하면 정산하기를 할 때 계좌가 자동으로 입력돼요. 마이페이지수정하기 버튼을 눌러 계좌번호를 등록 또는 수정할 수 있어요.", + "정산하기 기능을 더욱 빠르게 이용할 수 있다고? 계좌 번호를 등록하면 정산하기를 할 때 계좌가 자동으로 입력돼요. 마이 페이지수정하기 버튼을 눌러 계좌 번호를 등록 또는 수정할 수 있어요.", imageUrl: - "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_accountChanging.png", + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024fall/quest_accountChanging.png", reward: 500, }, adPushAgreement: { name: "Taxi의 소울메이트", description: - "Taxi 서비스를 잊지 않도록 가끔 찾아갈게요! 광고성 푸시 알림 수신 동의를 해주시면 방이 많이 모이는 시즌, 주변에 택시앱 사용자가 있을 때 알려드릴 수 있어요.", + "Taxi 서비스를 잊지 않도록 가끔 찾아갈게요! 광고성 푸시 알림 수신 동의를 해주시면 방이 많이 모이는 시즌, 주변에 Taxi 앱 사용자가 있을 때 알려드릴 수 있어요.", imageUrl: - "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_adPushAgreement.png", + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024fall/quest_adPushAgreement.png", reward: 500, }, eventSharing: { name: "Taxi를 아십니까", - description: "내가 초대한 사람이 이벤트에 참여하면 송편코인을 드려요.", + description: + "내가 초대한 사람이 이벤트에 참여하면 송편코인을 드려요. 다른 사람의 초대를 받아 이벤트에 참여한 경우에도 이 퀘스트가 달성돼요.", imageUrl: - "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024spring/quest_eventSharing.png", + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024fall/quest_eventSharing.png", reward: 700, maxCount: 0, }, dailyAttendance: { - name: "하루 한 번 Taxi!", + name: "매일매일 출석 췤!", description: - "매일 Taxi에 접속하여 출석 체크를 하면 송편코인을 드려요! 하루에 한 번, 택시팟도 둘러보고 송편코인도 받아 가세요. 송편코인을 얻으려면 출석 체크 페이지에서 출석 버튼을 눌러야 해요.", - imageUrl: "", + "매일 Taxi에 접속하면 하루 한 번 송편코인을 드려요! 하루에 한 번, 택시팟도 둘러보고 송편코인도 받아 가세요.", + imageUrl: + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024fall/quest_dailyAttendance.png", reward: 700, maxCount: 17, isApiRequired: true, }, itemPurchase: { - name: "itemPurchase", - description: "itemPurchase", - imageUrl: "", + name: "Taxi에서 산 응모권", + description: + "응모권 교환소에서 아무 경품 응모권이나 구매해 보세요. Taxi에서 판매하는 응모권은 모두 정품이니 안심해도 좋아요.", + imageUrl: + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2024fall/quest_itemPurchase.png", reward: 500, }, }); From 20d5a642254d3e5156ea8f81448797a7880be87f Mon Sep 17 00:00:00 2001 From: static Date: Wed, 25 Sep 2024 00:57:55 +0900 Subject: [PATCH 24/25] Fix: deprecated function and invalid default icon url --- package.json | 2 +- pnpm-lock.yaml | 1525 +++++++++++++++++++++++--------------------- src/modules/fcm.js | 7 +- 3 files changed, 808 insertions(+), 726 deletions(-) diff --git a/package.json b/package.json index 4ea2a648..47d8047c 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "express-rate-limit": "^7.1.0", "express-session": "^1.17.3", "express-validator": "^6.14.0", - "firebase-admin": "^11.4.1", + "firebase-admin": "^11.11.1", "jsonwebtoken": "^9.0.2", "mongoose": "^6.12.0", "node-cron": "3.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db4e122d..25c9f218 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,7 +7,7 @@ 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.2) + version: 5.1.0(adminjs@6.8.7)(express-formidable@1.2.0)(express-session@1.17.3)(express@4.18.2)(tslib@2.7.0) '@adminjs/mongoose': specifier: ^3.0.3 version: 3.0.3(adminjs@6.8.7)(mongoose@6.12.0) @@ -60,14 +60,14 @@ dependencies: specifier: ^6.14.0 version: 6.15.0 firebase-admin: - specifier: ^11.4.1 - version: 11.10.1 + specifier: ^11.11.1 + version: 11.11.1 jsonwebtoken: specifier: ^9.0.2 version: 9.0.2 mongoose: specifier: ^6.12.0 - version: 6.12.0 + version: 6.12.0(@aws-sdk/client-sso-oidc@3.654.0) node-cron: specifier: 3.0.2 version: 3.0.2 @@ -123,7 +123,7 @@ devDependencies: version: 10.2.0 mongodb: specifier: ^4.1.0 - version: 4.17.1 + version: 4.17.1(@aws-sdk/client-sso-oidc@3.654.0) nodemon: specifier: ^3.0.1 version: 3.0.1 @@ -184,7 +184,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.2): + /@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.7.0): resolution: {integrity: sha512-+mrtDmoAYA9R+/FTYWOLL48g005yrgcAWC2phdwqGzznIxGKSp2YERcfzdTI7Svtnlaal72/QW8Q3OhzJjVLzQ==} peerDependencies: adminjs: '>=6.0.0' @@ -198,7 +198,7 @@ packages: express-formidable: 1.2.0 express-session: 1.17.3 path-to-regexp: 6.2.1 - tslib: 2.6.2 + tslib: 2.7.0 dev: false /@adminjs/mongoose@3.0.3(adminjs@6.8.7)(mongoose@6.12.0): @@ -210,7 +210,7 @@ packages: adminjs: 6.8.7 escape-regexp: 0.0.1 lodash: 4.17.21 - mongoose: 6.12.0 + mongoose: 6.12.0(@aws-sdk/client-sso-oidc@3.654.0) dev: false /@ampproject/remapping@2.2.1: @@ -221,504 +221,527 @@ packages: '@jridgewell/trace-mapping': 0.3.18 dev: false - /@aws-crypto/crc32@3.0.0: - resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==} + /@aws-crypto/sha256-browser@5.2.0: + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} requiresBuild: true dependencies: - '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.425.0 - tslib: 1.14.1 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.654.0 + '@aws-sdk/util-locate-window': 3.568.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 optional: true - /@aws-crypto/ie11-detection@3.0.0: - resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} + /@aws-crypto/sha256-js@5.2.0: + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - tslib: 1.14.1 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.654.0 + tslib: 2.6.2 optional: true - /@aws-crypto/sha256-browser@3.0.0: - resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==} + /@aws-crypto/supports-web-crypto@5.2.0: + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} requiresBuild: 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.425.0 - '@aws-sdk/util-locate-window': 3.310.0 - '@aws-sdk/util-utf8-browser': 3.259.0 - tslib: 1.14.1 + tslib: 2.6.2 optional: true - /@aws-crypto/sha256-js@3.0.0: - resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} + /@aws-crypto/util@5.2.0: + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} requiresBuild: true dependencies: - '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.425.0 - tslib: 1.14.1 + '@aws-sdk/types': 3.654.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 optional: true - /@aws-crypto/supports-web-crypto@3.0.0: - resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==} - requiresBuild: true - dependencies: - tslib: 1.14.1 + /@aws-sdk/client-cognito-identity@3.654.0: + resolution: {integrity: sha512-3K806KJVivVP011R7Wf4ujGKP8R6d7KFlo9t0Swr9YFnStCdSdjmRX1yW8RpzSzRC4xyuUw+bo8wPf+tE/YxnA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sso-oidc': 3.654.0(@aws-sdk/client-sts@3.654.0) + '@aws-sdk/client-sts': 3.654.0 + '@aws-sdk/core': 3.654.0 + '@aws-sdk/credential-provider-node': 3.654.0(@aws-sdk/client-sso-oidc@3.654.0)(@aws-sdk/client-sts@3.654.0) + '@aws-sdk/middleware-host-header': 3.654.0 + '@aws-sdk/middleware-logger': 3.654.0 + '@aws-sdk/middleware-recursion-detection': 3.654.0 + '@aws-sdk/middleware-user-agent': 3.654.0 + '@aws-sdk/region-config-resolver': 3.654.0 + '@aws-sdk/types': 3.654.0 + '@aws-sdk/util-endpoints': 3.654.0 + '@aws-sdk/util-user-agent-browser': 3.654.0 + '@aws-sdk/util-user-agent-node': 3.654.0 + '@smithy/config-resolver': 3.0.8 + '@smithy/core': 2.4.5 + '@smithy/fetch-http-handler': 3.2.8 + '@smithy/hash-node': 3.0.6 + '@smithy/invalid-dependency': 3.0.6 + '@smithy/middleware-content-length': 3.0.8 + '@smithy/middleware-endpoint': 3.1.3 + '@smithy/middleware-retry': 3.0.20 + '@smithy/middleware-serde': 3.0.6 + '@smithy/middleware-stack': 3.0.6 + '@smithy/node-config-provider': 3.1.7 + '@smithy/node-http-handler': 3.2.3 + '@smithy/protocol-http': 4.1.3 + '@smithy/smithy-client': 3.3.4 + '@smithy/types': 3.4.2 + '@smithy/url-parser': 3.0.6 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.20 + '@smithy/util-defaults-mode-node': 3.0.20 + '@smithy/util-endpoints': 2.1.2 + '@smithy/util-middleware': 3.0.6 + '@smithy/util-retry': 3.0.6 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt optional: true - /@aws-crypto/util@3.0.0: - resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} - requiresBuild: true - dependencies: - '@aws-sdk/types': 3.425.0 - '@aws-sdk/util-utf8-browser': 3.259.0 - tslib: 1.14.1 + /@aws-sdk/client-sso-oidc@3.654.0(@aws-sdk/client-sts@3.654.0): + resolution: {integrity: sha512-gbHrKsEnaAtmkNCVQzLyiqMzpDaThV/bWl/ODEklI+t6stW3Pe3oDMstEHLfJ6JU5g8sYnx4VLuxlnJMtUkvPw==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.654.0 + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sts': 3.654.0 + '@aws-sdk/core': 3.654.0 + '@aws-sdk/credential-provider-node': 3.654.0(@aws-sdk/client-sso-oidc@3.654.0)(@aws-sdk/client-sts@3.654.0) + '@aws-sdk/middleware-host-header': 3.654.0 + '@aws-sdk/middleware-logger': 3.654.0 + '@aws-sdk/middleware-recursion-detection': 3.654.0 + '@aws-sdk/middleware-user-agent': 3.654.0 + '@aws-sdk/region-config-resolver': 3.654.0 + '@aws-sdk/types': 3.654.0 + '@aws-sdk/util-endpoints': 3.654.0 + '@aws-sdk/util-user-agent-browser': 3.654.0 + '@aws-sdk/util-user-agent-node': 3.654.0 + '@smithy/config-resolver': 3.0.8 + '@smithy/core': 2.4.5 + '@smithy/fetch-http-handler': 3.2.8 + '@smithy/hash-node': 3.0.6 + '@smithy/invalid-dependency': 3.0.6 + '@smithy/middleware-content-length': 3.0.8 + '@smithy/middleware-endpoint': 3.1.3 + '@smithy/middleware-retry': 3.0.20 + '@smithy/middleware-serde': 3.0.6 + '@smithy/middleware-stack': 3.0.6 + '@smithy/node-config-provider': 3.1.7 + '@smithy/node-http-handler': 3.2.3 + '@smithy/protocol-http': 4.1.3 + '@smithy/smithy-client': 3.3.4 + '@smithy/types': 3.4.2 + '@smithy/url-parser': 3.0.6 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.20 + '@smithy/util-defaults-mode-node': 3.0.20 + '@smithy/util-endpoints': 2.1.2 + '@smithy/util-middleware': 3.0.6 + '@smithy/util-retry': 3.0.6 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt optional: true - /@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.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.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 + /@aws-sdk/client-sso@3.654.0: + resolution: {integrity: sha512-4kBxs2IzCDtj6a6lRXa/lXK5wWpMGzwKtb+HMXf/rJYVM6x7wYRzc1hYrOd3DYkFQ/sR3dUFj+0mTP0os3aAbA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.654.0 + '@aws-sdk/middleware-host-header': 3.654.0 + '@aws-sdk/middleware-logger': 3.654.0 + '@aws-sdk/middleware-recursion-detection': 3.654.0 + '@aws-sdk/middleware-user-agent': 3.654.0 + '@aws-sdk/region-config-resolver': 3.654.0 + '@aws-sdk/types': 3.654.0 + '@aws-sdk/util-endpoints': 3.654.0 + '@aws-sdk/util-user-agent-browser': 3.654.0 + '@aws-sdk/util-user-agent-node': 3.654.0 + '@smithy/config-resolver': 3.0.8 + '@smithy/core': 2.4.5 + '@smithy/fetch-http-handler': 3.2.8 + '@smithy/hash-node': 3.0.6 + '@smithy/invalid-dependency': 3.0.6 + '@smithy/middleware-content-length': 3.0.8 + '@smithy/middleware-endpoint': 3.1.3 + '@smithy/middleware-retry': 3.0.20 + '@smithy/middleware-serde': 3.0.6 + '@smithy/middleware-stack': 3.0.6 + '@smithy/node-config-provider': 3.1.7 + '@smithy/node-http-handler': 3.2.3 + '@smithy/protocol-http': 4.1.3 + '@smithy/smithy-client': 3.3.4 + '@smithy/types': 3.4.2 + '@smithy/url-parser': 3.0.6 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.20 + '@smithy/util-defaults-mode-node': 3.0.20 + '@smithy/util-endpoints': 2.1.2 + '@smithy/util-middleware': 3.0.6 + '@smithy/util-retry': 3.0.6 + '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 transitivePeerDependencies: - aws-crt optional: true - /@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.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.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 + /@aws-sdk/client-sts@3.654.0: + resolution: {integrity: sha512-tyHa8jsBy+/NQZFHm6Q2Q09Vi9p3EH4yPy6PU8yPewpi2klreObtrUd0anJa6nzjS9SSuqnlZWsRic3cQ4QwCg==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sso-oidc': 3.654.0(@aws-sdk/client-sts@3.654.0) + '@aws-sdk/core': 3.654.0 + '@aws-sdk/credential-provider-node': 3.654.0(@aws-sdk/client-sso-oidc@3.654.0)(@aws-sdk/client-sts@3.654.0) + '@aws-sdk/middleware-host-header': 3.654.0 + '@aws-sdk/middleware-logger': 3.654.0 + '@aws-sdk/middleware-recursion-detection': 3.654.0 + '@aws-sdk/middleware-user-agent': 3.654.0 + '@aws-sdk/region-config-resolver': 3.654.0 + '@aws-sdk/types': 3.654.0 + '@aws-sdk/util-endpoints': 3.654.0 + '@aws-sdk/util-user-agent-browser': 3.654.0 + '@aws-sdk/util-user-agent-node': 3.654.0 + '@smithy/config-resolver': 3.0.8 + '@smithy/core': 2.4.5 + '@smithy/fetch-http-handler': 3.2.8 + '@smithy/hash-node': 3.0.6 + '@smithy/invalid-dependency': 3.0.6 + '@smithy/middleware-content-length': 3.0.8 + '@smithy/middleware-endpoint': 3.1.3 + '@smithy/middleware-retry': 3.0.20 + '@smithy/middleware-serde': 3.0.6 + '@smithy/middleware-stack': 3.0.6 + '@smithy/node-config-provider': 3.1.7 + '@smithy/node-http-handler': 3.2.3 + '@smithy/protocol-http': 4.1.3 + '@smithy/smithy-client': 3.3.4 + '@smithy/types': 3.4.2 + '@smithy/url-parser': 3.0.6 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.20 + '@smithy/util-defaults-mode-node': 3.0.20 + '@smithy/util-endpoints': 2.1.2 + '@smithy/util-middleware': 3.0.6 + '@smithy/util-retry': 3.0.6 + '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 transitivePeerDependencies: - aws-crt optional: true - /@aws-sdk/client-sts@3.427.0: - resolution: {integrity: sha512-le2wLJKILyWuRfPz2HbyaNtu5kEki+ojUkTqCU6FPDRrqUvEkaaCBH9Awo/2AtrCfRkiobop8RuTTj6cAnpiJg==} - engines: {node: '>=14.0.0'} + /@aws-sdk/core@3.654.0: + resolution: {integrity: sha512-4Rwx7BVaNaFqmXBDmnOkMbyuIFFbpZ+ru4lr660p45zY1QoNNSalechfoRffcokLFOZO+VWEJkdcorPUUU993w==} + engines: {node: '>=16.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.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.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 + '@smithy/core': 2.4.5 + '@smithy/node-config-provider': 3.1.7 + '@smithy/property-provider': 3.1.6 + '@smithy/protocol-http': 4.1.3 + '@smithy/signature-v4': 4.1.4 + '@smithy/smithy-client': 3.3.4 + '@smithy/types': 3.4.2 + '@smithy/util-middleware': 3.0.6 + fast-xml-parser: 4.4.1 tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt optional: true - /@aws-sdk/credential-provider-cognito-identity@3.427.0: - resolution: {integrity: sha512-BQNzNrMJlBAfXhYNdAUqaVASpT9Aho5swj7glZKxx4Uds1w5Pih2e14JWgnl8XgUWAZ36pchTrV1aA4JT7N8vw==} - engines: {node: '>=14.0.0'} + /@aws-sdk/credential-provider-cognito-identity@3.654.0: + resolution: {integrity: sha512-0aq4Ri9VYjixS7AZKNmuJc/5MlQdfrkgtzHV1TBisoroi/ed1WWnZmQvUFi3ZqRkt1Cvi7oZi6J1gZEfzq8p8g==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@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 + '@aws-sdk/client-cognito-identity': 3.654.0 + '@aws-sdk/types': 3.654.0 + '@smithy/property-provider': 3.1.6 + '@smithy/types': 3.4.2 tslib: 2.6.2 transitivePeerDependencies: - aws-crt optional: true - /@aws-sdk/credential-provider-env@3.425.0: - resolution: {integrity: sha512-J20etnLvMKXRVi5FK4F8yOCNm2RTaQn5psQTGdDEPWJNGxohcSpzzls8U2KcMyUJ+vItlrThr4qwgpHG3i/N0w==} - engines: {node: '>=14.0.0'} + /@aws-sdk/credential-provider-env@3.654.0: + resolution: {integrity: sha512-kogsx3Ql81JouHS7DkheCDU9MYAvK0AokxjcshDveGmf7BbgbWCA8Fnb9wjQyNDaOXNvkZu8Z8rgkX91z324/w==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.425.0 - '@smithy/property-provider': 2.0.12 - '@smithy/types': 2.3.5 + '@aws-sdk/types': 3.654.0 + '@smithy/property-provider': 3.1.6 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@aws-sdk/credential-provider-http@3.425.0: - resolution: {integrity: sha512-aP9nkoVWf+OlNMecrUqe4+RuQrX13nucVbty0HTvuwfwJJj0T6ByWZzle+fo1D+5OxvJmtzTflBWt6jUERdHWA==} - engines: {node: '>=14.0.0'} + /@aws-sdk/credential-provider-http@3.654.0: + resolution: {integrity: sha512-tgmAH4MBi/aDR882lfw48+tDV95ZH3GWc1Eoe6DpNLiM3GN2VfU/cZwuHmi6aq+vAbdIlswBHJ/+va0fOvlyjw==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@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 + '@aws-sdk/types': 3.654.0 + '@smithy/fetch-http-handler': 3.2.8 + '@smithy/node-http-handler': 3.2.3 + '@smithy/property-provider': 3.1.6 + '@smithy/protocol-http': 4.1.3 + '@smithy/smithy-client': 3.3.4 + '@smithy/types': 3.4.2 + '@smithy/util-stream': 3.1.8 tslib: 2.6.2 optional: true - /@aws-sdk/credential-provider-ini@3.427.0: - resolution: {integrity: sha512-NmH1cO/w98CKMltYec3IrJIIco19wRjATFNiw83c+FGXZ+InJwReqBnruxIOmKTx2KDzd6fwU1HOewS7UjaaaQ==} - engines: {node: '>=14.0.0'} + /@aws-sdk/credential-provider-ini@3.654.0(@aws-sdk/client-sso-oidc@3.654.0)(@aws-sdk/client-sts@3.654.0): + resolution: {integrity: sha512-DKSdaNu2hwdmuvnm9KnA0NLqMWxxmxSOLWjSUSoFIm++wGXUjPrRMFYKvMktaXnPuyf5my8gF/yGbwzPZ8wlTg==} + engines: {node: '>=16.0.0'} requiresBuild: true + peerDependencies: + '@aws-sdk/client-sts': ^3.654.0 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 + '@aws-sdk/client-sts': 3.654.0 + '@aws-sdk/credential-provider-env': 3.654.0 + '@aws-sdk/credential-provider-http': 3.654.0 + '@aws-sdk/credential-provider-process': 3.654.0 + '@aws-sdk/credential-provider-sso': 3.654.0(@aws-sdk/client-sso-oidc@3.654.0) + '@aws-sdk/credential-provider-web-identity': 3.654.0(@aws-sdk/client-sts@3.654.0) + '@aws-sdk/types': 3.654.0 + '@smithy/credential-provider-imds': 3.2.3 + '@smithy/property-provider': 3.1.6 + '@smithy/shared-ini-file-loader': 3.1.7 + '@smithy/types': 3.4.2 tslib: 2.6.2 transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' - aws-crt optional: true - /@aws-sdk/credential-provider-node@3.427.0: - resolution: {integrity: sha512-wYYbQ57nKL8OfgRbl8k6uXcdnYml+p3LSSfDUAuUEp1HKlQ8lOXFJ3BdLr5qrk7LhpyppSRnWBmh2c3kWa7ANQ==} - engines: {node: '>=14.0.0'} + /@aws-sdk/credential-provider-node@3.654.0(@aws-sdk/client-sso-oidc@3.654.0)(@aws-sdk/client-sts@3.654.0): + resolution: {integrity: sha512-wPV7CNYaXDEc+SS+3R0v8SZwkHRUE1z2k2j1d49tH5QBDT4tb/k2V/biXWkwSk3hbR+IMWXmuhJDv/5lybhIvg==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@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 + '@aws-sdk/credential-provider-env': 3.654.0 + '@aws-sdk/credential-provider-http': 3.654.0 + '@aws-sdk/credential-provider-ini': 3.654.0(@aws-sdk/client-sso-oidc@3.654.0)(@aws-sdk/client-sts@3.654.0) + '@aws-sdk/credential-provider-process': 3.654.0 + '@aws-sdk/credential-provider-sso': 3.654.0(@aws-sdk/client-sso-oidc@3.654.0) + '@aws-sdk/credential-provider-web-identity': 3.654.0(@aws-sdk/client-sts@3.654.0) + '@aws-sdk/types': 3.654.0 + '@smithy/credential-provider-imds': 3.2.3 + '@smithy/property-provider': 3.1.6 + '@smithy/shared-ini-file-loader': 3.1.7 + '@smithy/types': 3.4.2 tslib: 2.6.2 transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' - aws-crt optional: true - /@aws-sdk/credential-provider-process@3.425.0: - resolution: {integrity: sha512-YY6tkLdvtb1Fgofp3b1UWO+5vwS14LJ/smGmuGpSba0V7gFJRdcrJ9bcb9vVgAGuMdjzRJ+bUKlLLtqXkaykEw==} - engines: {node: '>=14.0.0'} + /@aws-sdk/credential-provider-process@3.654.0: + resolution: {integrity: sha512-PmQoo8sZ9Q2Ow8OMzK++Z9lI7MsRUG7sNq3E72DVA215dhtTICTDQwGlXH2AAmIp7n+G9LLRds+4wo2ehG4mkg==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@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 + '@aws-sdk/types': 3.654.0 + '@smithy/property-provider': 3.1.6 + '@smithy/shared-ini-file-loader': 3.1.7 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@aws-sdk/credential-provider-sso@3.427.0: - resolution: {integrity: sha512-c+tXyS/i49erHs4bAp6vKNYeYlyQ0VNMBgoco0LCn1rL0REtHbfhWMnqDLF6c2n3yIWDOTrQu0D73Idnpy16eA==} - engines: {node: '>=14.0.0'} + /@aws-sdk/credential-provider-sso@3.654.0(@aws-sdk/client-sso-oidc@3.654.0): + resolution: {integrity: sha512-7GFme6fWEdA/XYKzZPOAdj/jS6fMBy1NdSIZsDXikS0v9jU+ZzHrAaWt13YLzHyjgxB9Sg9id9ncdY1IiubQXQ==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@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 + '@aws-sdk/client-sso': 3.654.0 + '@aws-sdk/token-providers': 3.654.0(@aws-sdk/client-sso-oidc@3.654.0) + '@aws-sdk/types': 3.654.0 + '@smithy/property-provider': 3.1.6 + '@smithy/shared-ini-file-loader': 3.1.7 + '@smithy/types': 3.4.2 tslib: 2.6.2 transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' - aws-crt optional: true - /@aws-sdk/credential-provider-web-identity@3.425.0: - resolution: {integrity: sha512-/0R65TgRzL01JU3SzloivWNwdkbIhr06uY/F5pBHf/DynQqaspKNfdHn6AiozgSVDfwRHFjKBTUy6wvf3QFkuA==} - engines: {node: '>=14.0.0'} + /@aws-sdk/credential-provider-web-identity@3.654.0(@aws-sdk/client-sts@3.654.0): + resolution: {integrity: sha512-6a2g9gMtZToqSu+CusjNK5zvbLJahQ9di7buO3iXgbizXpLXU1rnawCpWxwslMpT5fLgMSKDnKDrr6wdEk7jSw==} + engines: {node: '>=16.0.0'} requiresBuild: true + peerDependencies: + '@aws-sdk/client-sts': ^3.654.0 dependencies: - '@aws-sdk/types': 3.425.0 - '@smithy/property-provider': 2.0.12 - '@smithy/types': 2.3.5 + '@aws-sdk/client-sts': 3.654.0 + '@aws-sdk/types': 3.654.0 + '@smithy/property-provider': 3.1.6 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@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.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 + /@aws-sdk/credential-providers@3.654.0(@aws-sdk/client-sso-oidc@3.654.0): + resolution: {integrity: sha512-e9ZDKnmXOMOQW9e3RQyaLUcerZFzHCickRSPoSxAsGKnrhH/ltIm9Od3uyVILl1TGJoOCxVDMBE9nPfl+vNRzQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/client-cognito-identity': 3.654.0 + '@aws-sdk/client-sso': 3.654.0 + '@aws-sdk/client-sts': 3.654.0 + '@aws-sdk/credential-provider-cognito-identity': 3.654.0 + '@aws-sdk/credential-provider-env': 3.654.0 + '@aws-sdk/credential-provider-http': 3.654.0 + '@aws-sdk/credential-provider-ini': 3.654.0(@aws-sdk/client-sso-oidc@3.654.0)(@aws-sdk/client-sts@3.654.0) + '@aws-sdk/credential-provider-node': 3.654.0(@aws-sdk/client-sso-oidc@3.654.0)(@aws-sdk/client-sts@3.654.0) + '@aws-sdk/credential-provider-process': 3.654.0 + '@aws-sdk/credential-provider-sso': 3.654.0(@aws-sdk/client-sso-oidc@3.654.0) + '@aws-sdk/credential-provider-web-identity': 3.654.0(@aws-sdk/client-sts@3.654.0) + '@aws-sdk/types': 3.654.0 + '@smithy/credential-provider-imds': 3.2.3 + '@smithy/property-provider': 3.1.6 + '@smithy/types': 3.4.2 tslib: 2.6.2 transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' - aws-crt optional: true - /@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.425.0 - '@smithy/protocol-http': 3.0.7 - '@smithy/types': 2.3.5 - tslib: 2.6.2 - optional: true - - /@aws-sdk/middleware-logger@3.425.0: - resolution: {integrity: sha512-INE9XWRXx2f4a/r2vOU0tAmgctVp7nEaEasemNtVBYhqbKLZvr9ndLBSgKGgJ8LIcXAoISipaMuFiqIGkFsm7A==} - engines: {node: '>=14.0.0'} + /@aws-sdk/middleware-host-header@3.654.0: + resolution: {integrity: sha512-rxGgVHWKp8U2ubMv+t+vlIk7QYUaRCHaVpmUlJv0Wv6Q0KeO9a42T9FxHphjOTlCGQOLcjCreL9CF8Qhtb4mdQ==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.425.0 - '@smithy/types': 2.3.5 + '@aws-sdk/types': 3.654.0 + '@smithy/protocol-http': 4.1.3 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@aws-sdk/middleware-recursion-detection@3.425.0: - resolution: {integrity: sha512-77gnzJ5b91bgD75L/ugpOyerx6lR3oyS4080X1YI58EzdyBMkDrHM4FbMcY2RynETi3lwXCFzLRyZjWXY1mRlw==} - engines: {node: '>=14.0.0'} + /@aws-sdk/middleware-logger@3.654.0: + resolution: {integrity: sha512-OQYb+nWlmASyXfRb989pwkJ9EVUMP1CrKn2eyTk3usl20JZmKo2Vjis6I0tLUkMSxMhnBJJlQKyWkRpD/u1FVg==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.425.0 - '@smithy/protocol-http': 3.0.7 - '@smithy/types': 2.3.5 + '@aws-sdk/types': 3.654.0 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@aws-sdk/middleware-sdk-sts@3.425.0: - resolution: {integrity: sha512-JFojrg76oKAoBknnr9EL5N2aJ1mRCtBqXoZYST58GSx8uYdFQ89qS65VNQ8JviBXzsrCNAn4vDhZ5Ch5E6TxGQ==} - engines: {node: '>=14.0.0'} + /@aws-sdk/middleware-recursion-detection@3.654.0: + resolution: {integrity: sha512-gKSomgltKVmsT8sC6W7CrADZ4GHwX9epk3GcH6QhebVO3LA9LRbkL3TwOPUXakxxOLLUTYdOZLIOtFf7iH00lg==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@aws-sdk/middleware-signing': 3.425.0 - '@aws-sdk/types': 3.425.0 - '@smithy/types': 2.3.5 + '@aws-sdk/types': 3.654.0 + '@smithy/protocol-http': 4.1.3 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@aws-sdk/middleware-signing@3.425.0: - resolution: {integrity: sha512-ZpOfgJHk7ovQ0sSwg3tU4NxFOnz53lJlkJRf7S+wxQALHM0P2MJ6LYBrZaFMVsKiJxNIdZBXD6jclgHg72ZW6Q==} - engines: {node: '>=14.0.0'} + /@aws-sdk/middleware-user-agent@3.654.0: + resolution: {integrity: sha512-liCcqPAyRsr53cy2tYu4qeH4MMN0eh9g6k56XzI5xd4SghXH5YWh4qOYAlQ8T66ZV4nPMtD8GLtLXGzsH8moFg==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@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 + '@aws-sdk/types': 3.654.0 + '@aws-sdk/util-endpoints': 3.654.0 + '@smithy/protocol-http': 4.1.3 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@aws-sdk/middleware-user-agent@3.427.0: - resolution: {integrity: sha512-y9HxYsNvnA3KqDl8w1jHeCwz4P9CuBEtu/G+KYffLeAMBsMZmh4SIkFFCO9wE/dyYg6+yo07rYcnnIfy7WA0bw==} - engines: {node: '>=14.0.0'} + /@aws-sdk/region-config-resolver@3.654.0: + resolution: {integrity: sha512-ydGOrXJxj3x0sJhsXyTmvJVLAE0xxuTWFJihTl67RtaO7VRNtd82I3P3bwoMMaDn5WpmV5mPo8fEUDRlBm3fPg==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.425.0 - '@aws-sdk/util-endpoints': 3.427.0 - '@smithy/protocol-http': 3.0.7 - '@smithy/types': 2.3.5 + '@aws-sdk/types': 3.654.0 + '@smithy/node-config-provider': 3.1.7 + '@smithy/types': 3.4.2 + '@smithy/util-config-provider': 3.0.0 + '@smithy/util-middleware': 3.0.6 tslib: 2.6.2 optional: true - /@aws-sdk/region-config-resolver@3.425.0: - resolution: {integrity: sha512-u7uv/iUOapIJdRgRkO3wnpYsUgV6ponsZJQgVg/8L+n+Vo5PQL5gAcIuAOwcYSKQPFaeK+KbmByI4SyOK203Vw==} - engines: {node: '>=14.0.0'} + /@aws-sdk/token-providers@3.654.0(@aws-sdk/client-sso-oidc@3.654.0): + resolution: {integrity: sha512-D8GeJYmvbfWkQDtTB4owmIobSMexZel0fOoetwvgCQ/7L8VPph3Q2bn1TRRIXvH7wdt6DcDxA3tKMHPBkT3GlA==} + engines: {node: '>=16.0.0'} requiresBuild: true + peerDependencies: + '@aws-sdk/client-sso-oidc': ^3.654.0 dependencies: - '@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 + '@aws-sdk/client-sso-oidc': 3.654.0(@aws-sdk/client-sts@3.654.0) + '@aws-sdk/types': 3.654.0 + '@smithy/property-provider': 3.1.6 + '@smithy/shared-ini-file-loader': 3.1.7 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@aws-sdk/token-providers@3.427.0: - resolution: {integrity: sha512-4E5E+4p8lJ69PBY400dJXF06LUHYx5lkKzBEsYqWWhoZcoftrvi24ltIhUDoGVLkrLcTHZIWSdFAWSos4hXqeg==} - engines: {node: '>=14.0.0'} + /@aws-sdk/types@3.654.0: + resolution: {integrity: sha512-VWvbED3SV+10QJIcmU/PKjsKilsTV16d1I7/on4bvD/jo1qGeMXqLDBSen3ks/tuvXZF/mFc7ZW/W2DiLVtO7A==} + engines: {node: '>=16.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.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 + '@smithy/types': 3.4.2 tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt optional: true - /@aws-sdk/types@3.425.0: - resolution: {integrity: sha512-6lqbmorwerN4v+J5dqbHPAsjynI0mkEF+blf+69QTaKKGaxBBVaXgqoqul9RXYcK5MMrrYRbQIMd0zYOoy90kA==} - engines: {node: '>=14.0.0'} + /@aws-sdk/util-endpoints@3.654.0: + resolution: {integrity: sha512-i902fcBknHs0Irgdpi62+QMvzxE+bczvILXigYrlHL4+PiEnlMVpni5L5W1qCkNZXf8AaMrSBuR1NZAGp6UOUw==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.3.5 + '@aws-sdk/types': 3.654.0 + '@smithy/types': 3.4.2 + '@smithy/util-endpoints': 2.1.2 tslib: 2.6.2 optional: true - /@aws-sdk/util-endpoints@3.427.0: - resolution: {integrity: sha512-rSyiAIFF/EVvity/+LWUqoTMJ0a25RAc9iqx0WZ4tf1UjuEXRRXxZEb+jEZg1bk+pY84gdLdx9z5E+MSJCZxNQ==} - engines: {node: '>=14.0.0'} + /@aws-sdk/util-locate-window@3.568.0: + resolution: {integrity: sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@aws-sdk/types': 3.425.0 - '@smithy/node-config-provider': 2.1.1 tslib: 2.6.2 optional: true - /@aws-sdk/util-locate-window@3.310.0: - resolution: {integrity: sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==} - engines: {node: '>=14.0.0'} + /@aws-sdk/util-user-agent-browser@3.654.0: + resolution: {integrity: sha512-ykYAJqvnxLt7wfrqya28wuH3/7NdrwzfiFd7NqEVQf7dXVxL5RPEpD7DxjcyQo3DsHvvdUvGZVaQhozycn1pzA==} requiresBuild: true dependencies: - tslib: 2.6.2 - optional: true - - /@aws-sdk/util-user-agent-browser@3.425.0: - resolution: {integrity: sha512-22Y9iMtjGcFjGILR6/xdp1qRezlHVLyXtnpEsbuPTiernRCPk6zfAnK/ATH77r02MUjU057tdxVkd5umUBTn9Q==} - requiresBuild: true - dependencies: - '@aws-sdk/types': 3.425.0 - '@smithy/types': 2.3.5 + '@aws-sdk/types': 3.654.0 + '@smithy/types': 3.4.2 bowser: 2.11.0 tslib: 2.6.2 optional: true - /@aws-sdk/util-user-agent-node@3.425.0: - resolution: {integrity: sha512-SIR4F5uQeeVAi8lv4OgRirtdtNi5zeyogTuQgGi9su8F/WP1N6JqxofcwpUY5f8/oJ2UlXr/tx1f09UHfJJzvA==} - engines: {node: '>=14.0.0'} + /@aws-sdk/util-user-agent-node@3.654.0: + resolution: {integrity: sha512-a0ojjdBN6pqv6gB4H/QPPSfhs7mFtlVwnmKCM/QrTaFzN0U810PJ1BST3lBx5sa23I5jWHGaoFY+5q65C3clLQ==} + engines: {node: '>=16.0.0'} requiresBuild: true peerDependencies: aws-crt: '>=1.0.0' @@ -726,16 +749,9 @@ packages: aws-crt: optional: true dependencies: - '@aws-sdk/types': 3.425.0 - '@smithy/node-config-provider': 2.1.1 - '@smithy/types': 2.3.5 - tslib: 2.6.2 - optional: true - - /@aws-sdk/util-utf8-browser@3.259.0: - resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} - requiresBuild: true - dependencies: + '@aws-sdk/types': 3.654.0 + '@smithy/node-config-provider': 3.1.7 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true @@ -2244,7 +2260,7 @@ packages: resolution: {integrity: sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==} dependencies: '@firebase/util': 1.9.3 - tslib: 2.6.2 + tslib: 2.7.0 dev: false /@firebase/database-compat@0.3.4: @@ -2255,7 +2271,7 @@ packages: '@firebase/database-types': 0.10.4 '@firebase/logger': 0.4.0 '@firebase/util': 1.9.3 - tslib: 2.6.2 + tslib: 2.7.0 dev: false /@firebase/database-types@0.10.4: @@ -2273,19 +2289,19 @@ packages: '@firebase/logger': 0.4.0 '@firebase/util': 1.9.3 faye-websocket: 0.11.4 - tslib: 2.6.2 + tslib: 2.7.0 dev: false /@firebase/logger@0.4.0: resolution: {integrity: sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==} dependencies: - tslib: 2.6.2 + tslib: 2.7.0 dev: false /@firebase/util@1.9.3: resolution: {integrity: sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==} dependencies: - tslib: 2.6.2 + tslib: 2.7.0 dev: false /@floating-ui/core@1.4.1: @@ -2313,7 +2329,7 @@ packages: fast-deep-equal: 3.1.3 functional-red-black-tree: 1.0.1 google-gax: 3.6.1 - protobufjs: 7.2.5 + protobufjs: 7.4.0 transitivePeerDependencies: - encoding - supports-color @@ -2355,10 +2371,10 @@ packages: abort-controller: 3.0.0 async-retry: 1.3.3 compressible: 2.0.18 - duplexify: 4.1.2 - ent: 2.2.0 + duplexify: 4.1.3 + ent: 2.2.1 extend: 3.0.2 - fast-xml-parser: 4.2.7 + fast-xml-parser: 4.4.1 gaxios: 5.1.3 google-auth-library: 8.9.0 mime: 3.0.0 @@ -2373,26 +2389,25 @@ packages: dev: false optional: true - /@grpc/grpc-js@1.8.21: - resolution: {integrity: sha512-KeyQeZpxeEBSqFVTi3q2K7PiPXmgBfECc4updA1ejCLjYmoAlvvM3ZMp5ztTDUCUQmoY3CpDxvchjO1+rFkoHg==} + /@grpc/grpc-js@1.8.22: + resolution: {integrity: sha512-oAjDdN7fzbUi+4hZjKG96MR6KTEubAeMpQEb+77qy+3r0Ua5xTFuie6JOLr4ZZgl5g+W5/uRTS2M1V8mVAFPuA==} engines: {node: ^8.13.0 || >=10.10.0} requiresBuild: true dependencies: - '@grpc/proto-loader': 0.7.8 - '@types/node': 20.4.7 + '@grpc/proto-loader': 0.7.13 + '@types/node': 22.6.1 dev: false optional: true - /@grpc/proto-loader@0.7.8: - resolution: {integrity: sha512-GU12e2c8dmdXb7XUlOgYWZ2o2i+z9/VeACkxTA/zzAe2IjclC5PnVL0lpgjhrqfpDYHzM8B1TF6pqWegMYAzlA==} + /@grpc/proto-loader@0.7.13: + resolution: {integrity: sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==} engines: {node: '>=6'} hasBin: true requiresBuild: true dependencies: - '@types/long': 4.0.2 lodash.camelcase: 4.3.0 - long: 4.0.0 - protobufjs: 7.2.5 + long: 5.2.3 + protobufjs: 7.4.0 yargs: 17.7.2 dev: false optional: true @@ -2487,8 +2502,8 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: false - /@jsdoc/salty@0.2.5: - resolution: {integrity: sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==} + /@jsdoc/salty@0.2.8: + resolution: {integrity: sha512-5e+SFVavj1ORKlKaKr2BmTOekmXbelU7dC0cDkQLqag7xfuTPuGMUFx7KWJuv4bYZrTsoL2Z18VVCOKYxzoHcg==} engines: {node: '>=v12.0.0'} requiresBuild: true dependencies: @@ -2496,8 +2511,8 @@ packages: dev: false optional: true - /@mongodb-js/saslprep@1.1.0: - resolution: {integrity: sha512-Xfijy7HvfzzqiOAhAepF4SGN5e9leLkMvg/OPOF97XemjfVCYN/oWa75wnkc6mltMSTwY+XlbhWgUOJmkFspSw==} + /@mongodb-js/saslprep@1.1.9: + resolution: {integrity: sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==} requiresBuild: true dependencies: sparse-bitfield: 3.0.3 @@ -2749,385 +2764,434 @@ packages: rollup: 2.79.1 dev: false - /@smithy/abort-controller@2.0.11: - resolution: {integrity: sha512-MSzE1qR2JNyb7ot3blIOT3O3H0Jn06iNDEgHRaqZUwBgx5EG+VIx24Y21tlKofzYryIOcWpIohLrIIyocD6LMA==} - engines: {node: '>=14.0.0'} + /@smithy/abort-controller@3.1.4: + resolution: {integrity: sha512-VupaALAQlXViW3/enTf/f5l5JZYSAxoJL7f0nanhNNKnww6DGCg1oYIuNP78KDugnkwthBO6iEcym16HhWV8RQ==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.3.5 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/config-resolver@2.0.14: - resolution: {integrity: sha512-K1K+FuWQoy8j/G7lAmK85o03O89s2Vvh6kMFmzEmiHUoQCRH1rzbDtMnGNiaMHeSeYJ6y79IyTusdRG+LuWwtg==} - engines: {node: '>=14.0.0'} + /@smithy/config-resolver@3.0.8: + resolution: {integrity: sha512-Tv1obAC18XOd2OnDAjSWmmthzx6Pdeh63FbLin8MlPiuJ2ATpKkq0NcNOJFr0dO+JmZXnwu8FQxKJ3TKJ3Hulw==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@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 + '@smithy/node-config-provider': 3.1.7 + '@smithy/types': 3.4.2 + '@smithy/util-config-provider': 3.0.0 + '@smithy/util-middleware': 3.0.6 tslib: 2.6.2 optional: true - /@smithy/credential-provider-imds@2.0.16: - resolution: {integrity: sha512-tKa2xF+69TvGxJT+lnJpGrKxUuAZDLYXFhqnPEgnHz+psTpkpcB4QRjHj63+uj83KaeFJdTfW201eLZeRn6FfA==} - engines: {node: '>=14.0.0'} + /@smithy/core@2.4.5: + resolution: {integrity: sha512-Z0qlPXgZ0pouYgnu/cZTEYeRAvniiKZmVl4wIbZHX/nEMHkMDV9ao6KFArsU9KndE0TuhL149xcRx45wfw1YCA==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/node-config-provider': 2.1.1 - '@smithy/property-provider': 2.0.12 - '@smithy/types': 2.3.5 - '@smithy/url-parser': 2.0.11 + '@smithy/middleware-endpoint': 3.1.3 + '@smithy/middleware-retry': 3.0.20 + '@smithy/middleware-serde': 3.0.6 + '@smithy/protocol-http': 4.1.3 + '@smithy/smithy-client': 3.3.4 + '@smithy/types': 3.4.2 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-middleware': 3.0.6 + '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 optional: true - /@smithy/eventstream-codec@2.0.11: - resolution: {integrity: sha512-BQCTjxhCYRZIfXapa2LmZSaH8QUBGwMZw7XRN83hrdixbLjIcj+o549zjkedFS07Ve2TlvWUI6BTzP+nv7snBA==} + /@smithy/credential-provider-imds@3.2.3: + resolution: {integrity: sha512-VoxMzSzdvkkjMJNE38yQgx4CfnmT+Z+5EUXkg4x7yag93eQkVQgZvN3XBSHC/ylfBbLbAtdu7flTCChX9I+mVg==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@aws-crypto/crc32': 3.0.0 - '@smithy/types': 2.3.5 - '@smithy/util-hex-encoding': 2.0.0 + '@smithy/node-config-provider': 3.1.7 + '@smithy/property-provider': 3.1.6 + '@smithy/types': 3.4.2 + '@smithy/url-parser': 3.0.6 tslib: 2.6.2 optional: true - /@smithy/fetch-http-handler@2.2.2: - resolution: {integrity: sha512-K7aRtRuaBjzlk+jWWeyfDTLAmRRvmA4fU8eHUXtjsuEDgi3f356ZE32VD2ssxIH13RCLVZbXMt5h7wHzYiSuVA==} + /@smithy/fetch-http-handler@3.2.8: + resolution: {integrity: sha512-Lqe0B8F5RM7zkw//6avq1SJ8AfaRd3ubFUS1eVp5WszV7p6Ne5hQ4dSuMHDpNRPhgTvj4va9Kd/pcVigHEHRow==} requiresBuild: true dependencies: - '@smithy/protocol-http': 3.0.7 - '@smithy/querystring-builder': 2.0.11 - '@smithy/types': 2.3.5 - '@smithy/util-base64': 2.0.0 + '@smithy/protocol-http': 4.1.3 + '@smithy/querystring-builder': 3.0.6 + '@smithy/types': 3.4.2 + '@smithy/util-base64': 3.0.0 tslib: 2.6.2 optional: true - /@smithy/hash-node@2.0.11: - resolution: {integrity: sha512-PbleVugN2tbhl1ZoNWVrZ1oTFFas/Hq+s6zGO8B9bv4w/StTriTKA9W+xZJACOj9X7zwfoTLbscM+avCB1KqOQ==} - engines: {node: '>=14.0.0'} + /@smithy/hash-node@3.0.6: + resolution: {integrity: sha512-c/FHEdKK/7DU2z6ZE91L36ahyXWayR3B+FzELjnYq7wH5YqIseM24V+pWCS9kFn1Ln8OFGTf+pyYPiHZuX0s/Q==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.3.5 - '@smithy/util-buffer-from': 2.0.0 - '@smithy/util-utf8': 2.0.0 + '@smithy/types': 3.4.2 + '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 optional: true - /@smithy/invalid-dependency@2.0.11: - resolution: {integrity: sha512-zazq99ujxYv/NOf9zh7xXbNgzoVLsqE0wle8P/1zU/XdhPi/0zohTPKWUzIxjGdqb5hkkwfBkNkl5H+LE0mvgw==} + /@smithy/invalid-dependency@3.0.6: + resolution: {integrity: sha512-czM7Ioq3s8pIXht7oD+vmgy4Wfb4XavU/k/irO8NdXFFOx7YAlsCCcKOh/lJD1mJSYQqiR7NmpZ9JviryD/7AQ==} requiresBuild: true dependencies: - '@smithy/types': 2.3.5 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/is-array-buffer@2.0.0: - resolution: {integrity: sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==} + /@smithy/is-array-buffer@2.2.0: + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: tslib: 2.6.2 optional: true - /@smithy/middleware-content-length@2.0.13: - resolution: {integrity: sha512-Md2kxWpaec3bXp1oERFPQPBhOXCkGSAF7uc1E+4rkwjgw3/tqAXRtbjbggu67HJdwaif76As8AV6XxbD1HzqTQ==} - engines: {node: '>=14.0.0'} + /@smithy/is-array-buffer@3.0.0: + resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/protocol-http': 3.0.7 - '@smithy/types': 2.3.5 tslib: 2.6.2 optional: true - /@smithy/middleware-endpoint@2.0.11: - resolution: {integrity: sha512-mCugsvB15up6fqpzUEpMT4CuJmFkEI+KcozA7QMzYguXCaIilyMKsyxgamwmr+o7lo3QdjN0//XLQ9bWFL129g==} - engines: {node: '>=14.0.0'} + /@smithy/middleware-content-length@3.0.8: + resolution: {integrity: sha512-VuyszlSO49WKh3H9/kIO2kf07VUwGV80QRiaDxUfP8P8UKlokz381ETJvwLhwuypBYhLymCYyNhB3fLAGBX2og==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/middleware-serde': 2.0.11 - '@smithy/types': 2.3.5 - '@smithy/url-parser': 2.0.11 - '@smithy/util-middleware': 2.0.4 + '@smithy/protocol-http': 4.1.3 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/middleware-retry@2.0.16: - resolution: {integrity: sha512-Br5+0yoiMS0ugiOAfJxregzMMGIRCbX4PYo1kDHtLgvkA/d++aHbnHB819m5zOIAMPvPE7AThZgcsoK+WOsUTA==} - engines: {node: '>=14.0.0'} + /@smithy/middleware-endpoint@3.1.3: + resolution: {integrity: sha512-KeM/OrK8MVFUsoJsmCN0MZMVPjKKLudn13xpgwIMpGTYpA8QZB2Xq5tJ+RE6iu3A6NhOI4VajDTwBsm8pwwrhg==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@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 + '@smithy/middleware-serde': 3.0.6 + '@smithy/node-config-provider': 3.1.7 + '@smithy/shared-ini-file-loader': 3.1.7 + '@smithy/types': 3.4.2 + '@smithy/url-parser': 3.0.6 + '@smithy/util-middleware': 3.0.6 tslib: 2.6.2 - uuid: 8.3.2 optional: true - /@smithy/middleware-serde@2.0.11: - resolution: {integrity: sha512-NuxnjMyf4zQqhwwdh0OTj5RqpnuT6HcH5Xg5GrPijPcKzc2REXVEVK4Yyk8ckj8ez1XSj/bCmJ+oNjmqB02GWA==} - engines: {node: '>=14.0.0'} + /@smithy/middleware-retry@3.0.20: + resolution: {integrity: sha512-HELCOVwYw5hFDBm69d+LmmGjBCjWnwp/t7SJiHmp+c4u9vgfIaCjdSeIdnlOsLrr5ic5jGTJXvJFUQnd987b/g==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.3.5 + '@smithy/node-config-provider': 3.1.7 + '@smithy/protocol-http': 4.1.3 + '@smithy/service-error-classification': 3.0.6 + '@smithy/smithy-client': 3.3.4 + '@smithy/types': 3.4.2 + '@smithy/util-middleware': 3.0.6 + '@smithy/util-retry': 3.0.6 tslib: 2.6.2 + uuid: 9.0.1 optional: true - /@smithy/middleware-stack@2.0.5: - resolution: {integrity: sha512-bVQU/rZzBY7CbSxIrDTGZYnBWKtIw+PL/cRc9B7etZk1IKSOe0NvKMJyWllfhfhrTeMF6eleCzOihIQympAvPw==} - engines: {node: '>=14.0.0'} + /@smithy/middleware-serde@3.0.6: + resolution: {integrity: sha512-KKTUSl1MzOM0MAjGbudeaVNtIDo+PpekTBkCNwvfZlKndodrnvRo+00USatiyLOc0ujjO9UydMRu3O9dYML7ag==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.3.5 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/node-config-provider@2.1.1: - resolution: {integrity: sha512-1lF6s1YWBi1LBu2O30tD3jyTgMtuvk/Z1twzXM4GPYe4dmZix4nNREPJIPOcfFikNU2o0eTYP80+izx5F2jIJA==} - engines: {node: '>=14.0.0'} + /@smithy/middleware-stack@3.0.6: + resolution: {integrity: sha512-2c0eSYhTQ8xQqHMcRxLMpadFbTXg6Zla5l0mwNftFCZMQmuhI7EbAJMx6R5eqfuV3YbJ3QGyS3d5uSmrHV8Khg==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/property-provider': 2.0.12 - '@smithy/shared-ini-file-loader': 2.2.0 - '@smithy/types': 2.3.5 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/node-http-handler@2.1.7: - resolution: {integrity: sha512-PQIKZXlp3awCDn/xNlCSTFE7aYG/5Tx33M05NfQmWYeB5yV1GZZOSz4dXpwiNJYTXb9jPqjl+ueXXkwtEluFFA==} - engines: {node: '>=14.0.0'} + /@smithy/node-config-provider@3.1.7: + resolution: {integrity: sha512-g3mfnC3Oo8pOI0dYuPXLtdW1WGVb3bR2tkV21GNkm0ZvQjLTtamXAwCWt/FCb0HGvKt3gHHmF1XerG0ICfalOg==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/abort-controller': 2.0.11 - '@smithy/protocol-http': 3.0.7 - '@smithy/querystring-builder': 2.0.11 - '@smithy/types': 2.3.5 + '@smithy/property-provider': 3.1.6 + '@smithy/shared-ini-file-loader': 3.1.7 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/property-provider@2.0.12: - resolution: {integrity: sha512-Un/OvvuQ1Kg8WYtoMCicfsFFuHb/TKL3pCA6ZIo/WvNTJTR94RtoRnL7mY4XkkUAoFMyf6KjcQJ76y1FX7S5rw==} - engines: {node: '>=14.0.0'} + /@smithy/node-http-handler@3.2.3: + resolution: {integrity: sha512-/gcm5DJ3k1b1zEInzBGAZC8ntJ+jwrz1NcSIu+9dSXd1FfG0G6QgkDI40tt8/WYUbHtLyo8fEqtm2v29koWo/w==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.3.5 + '@smithy/abort-controller': 3.1.4 + '@smithy/protocol-http': 4.1.3 + '@smithy/querystring-builder': 3.0.6 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/protocol-http@3.0.7: - resolution: {integrity: sha512-HnZW8y+r66ntYueCDbLqKwWcMNWW8o3eVpSrHNluwtBJ/EUWfQHRKSiu6vZZtc6PGfPQWgVfucoCE/C3QufMAA==} - engines: {node: '>=14.0.0'} + /@smithy/property-provider@3.1.6: + resolution: {integrity: sha512-NK3y/T7Q/Bw+Z8vsVs9MYIQ5v7gOX7clyrXcwhhIBQhbPgRl6JDrZbusO9qWDhcEus75Tg+VCxtIRfo3H76fpw==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.3.5 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/querystring-builder@2.0.11: - resolution: {integrity: sha512-b4kEbVMxpmfv2VWUITn2otckTi7GlMteZQxi+jlwedoATOGEyrCJPfRcYQJjbCi3fZ2QTfh3PcORvB27+j38Yg==} - engines: {node: '>=14.0.0'} + /@smithy/protocol-http@4.1.3: + resolution: {integrity: sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.3.5 - '@smithy/util-uri-escape': 2.0.0 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/querystring-parser@2.0.11: - resolution: {integrity: sha512-YXe7jhi7s3dQ0Fu9dLoY/gLu6NCyy8tBWJL/v2c9i7/RLpHgKT+uT96/OqZkHizCJ4kr0ZD46tzMjql/o60KLg==} - engines: {node: '>=14.0.0'} + /@smithy/querystring-builder@3.0.6: + resolution: {integrity: sha512-sQe08RunoObe+Usujn9+R2zrLuQERi3CWvRO3BvnoWSYUaIrLKuAIeY7cMeDax6xGyfIP3x/yFWbEKSXvOnvVg==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.3.5 + '@smithy/types': 3.4.2 + '@smithy/util-uri-escape': 3.0.0 tslib: 2.6.2 optional: true - /@smithy/service-error-classification@2.0.4: - resolution: {integrity: sha512-77506l12I5gxTZqBkx3Wb0RqMG81bMYLaVQ+EqIWFwQDJRs5UFeXogKxSKojCmz1wLUziHZQXm03MBzPQiumQw==} - engines: {node: '>=14.0.0'} + /@smithy/querystring-parser@3.0.6: + resolution: {integrity: sha512-UJKw4LlEkytzz2Wq+uIdHf6qOtFfee/o7ruH0jF5I6UAuU+19r9QV7nU3P/uI0l6+oElRHmG/5cBBcGJrD7Ozg==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.3.5 + '@smithy/types': 3.4.2 + tslib: 2.6.2 optional: true - /@smithy/shared-ini-file-loader@2.2.0: - resolution: {integrity: sha512-xFXqs4vAb5BdkzHSRrTapFoaqS4/3m/CGZzdw46fBjYZ0paYuLAoMY60ICCn1FfGirG+PiJ3eWcqJNe4/SkfyA==} - engines: {node: '>=14.0.0'} + /@smithy/service-error-classification@3.0.6: + resolution: {integrity: sha512-53SpchU3+DUZrN7J6sBx9tBiCVGzsib2e4sc512Q7K9fpC5zkJKs6Z9s+qbMxSYrkEkle6hnMtrts7XNkMJJMg==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/types': 3.4.2 + optional: true + + /@smithy/shared-ini-file-loader@3.1.7: + resolution: {integrity: sha512-IA4K2qTJYXkF5OfVN4vsY1hfnUZjaslEE8Fsr/gGFza4TAC2A9NfnZuSY2srQIbt9bwtjHiAayrRVgKse4Q7fA==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.3.5 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/signature-v4@2.0.11: - resolution: {integrity: sha512-EFVU1dT+2s8xi227l1A9O27edT/GNKvyAK6lZnIZ0zhIHq/jSLznvkk15aonGAM1kmhmZBVGpI7Tt0odueZK9A==} - engines: {node: '>=14.0.0'} + /@smithy/signature-v4@4.1.4: + resolution: {integrity: sha512-72MiK7xYukNsnLJI9NqvUHqTu0ziEsfMsYNlWpiJfuGQnCTFKpckThlEatirvcA/LmT1h7rRO+pJD06PYsPu9Q==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/eventstream-codec': 2.0.11 - '@smithy/is-array-buffer': 2.0.0 - '@smithy/types': 2.3.5 - '@smithy/util-hex-encoding': 2.0.0 - '@smithy/util-middleware': 2.0.4 - '@smithy/util-uri-escape': 2.0.0 - '@smithy/util-utf8': 2.0.0 + '@smithy/is-array-buffer': 3.0.0 + '@smithy/protocol-http': 4.1.3 + '@smithy/types': 3.4.2 + '@smithy/util-hex-encoding': 3.0.0 + '@smithy/util-middleware': 3.0.6 + '@smithy/util-uri-escape': 3.0.0 + '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 optional: true - /@smithy/smithy-client@2.1.10: - resolution: {integrity: sha512-2OEmZDiW1Z196QHuQZ5M6cBE8FCSG0H2HADP1G+DY8P3agsvb0YJyfhyKuJbxIQy15tr3eDAK6FOrlbxgKOOew==} - engines: {node: '>=14.0.0'} + /@smithy/smithy-client@3.3.4: + resolution: {integrity: sha512-NKw/2XxOW/Rg3rzB90HxsmGok5oS6vRzJgMh/JN4BHaOQQ4q5OuX999GmOGxEp730wbpIXIowfKZmIMXkG4v0Q==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/middleware-stack': 2.0.5 - '@smithy/types': 2.3.5 - '@smithy/util-stream': 2.0.15 + '@smithy/middleware-endpoint': 3.1.3 + '@smithy/middleware-stack': 3.0.6 + '@smithy/protocol-http': 4.1.3 + '@smithy/types': 3.4.2 + '@smithy/util-stream': 3.1.8 tslib: 2.6.2 optional: true - /@smithy/types@2.3.5: - resolution: {integrity: sha512-ehyDt8M9hehyxrLQGoA1BGPou8Js1Ocoh5M0ngDhJMqbFmNK5N6Xhr9/ZExWkyIW8XcGkiMPq3ZUEE0ScrhbuQ==} - engines: {node: '>=14.0.0'} + /@smithy/types@3.4.2: + resolution: {integrity: sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: tslib: 2.6.2 optional: true - /@smithy/url-parser@2.0.11: - resolution: {integrity: sha512-h89yXMCCF+S5k9XIoKltMIWTYj+FcEkU/IIFZ6RtE222fskOTL4Iak6ZRG+ehSvZDt8yKEcxqheTDq7JvvtK3g==} + /@smithy/url-parser@3.0.6: + resolution: {integrity: sha512-47Op/NU8Opt49KyGpHtVdnmmJMsp2hEwBdyjuFB9M2V5QVOwA7pBhhxKN5z6ztKGrMw76gd8MlbPuzzvaAncuQ==} requiresBuild: true dependencies: - '@smithy/querystring-parser': 2.0.11 - '@smithy/types': 2.3.5 + '@smithy/querystring-parser': 3.0.6 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/util-base64@2.0.0: - resolution: {integrity: sha512-Zb1E4xx+m5Lud8bbeYi5FkcMJMnn+1WUnJF3qD7rAdXpaL7UjkFQLdmW5fHadoKbdHpwH9vSR8EyTJFHJs++tA==} - engines: {node: '>=14.0.0'} + /@smithy/util-base64@3.0.0: + resolution: {integrity: sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/util-buffer-from': 2.0.0 + '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 optional: true - /@smithy/util-body-length-browser@2.0.0: - resolution: {integrity: sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==} + /@smithy/util-body-length-browser@3.0.0: + resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} requiresBuild: true dependencies: tslib: 2.6.2 optional: true - /@smithy/util-body-length-node@2.1.0: - resolution: {integrity: sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==} - engines: {node: '>=14.0.0'} + /@smithy/util-body-length-node@3.0.0: + resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: tslib: 2.6.2 optional: true - /@smithy/util-buffer-from@2.0.0: - resolution: {integrity: sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==} + /@smithy/util-buffer-from@2.2.0: + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/is-array-buffer': 2.0.0 + '@smithy/is-array-buffer': 2.2.0 tslib: 2.6.2 optional: true - /@smithy/util-config-provider@2.0.0: - resolution: {integrity: sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==} - engines: {node: '>=14.0.0'} + /@smithy/util-buffer-from@3.0.0: + resolution: {integrity: sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: + '@smithy/is-array-buffer': 3.0.0 tslib: 2.6.2 optional: true - /@smithy/util-defaults-mode-browser@2.0.14: - resolution: {integrity: sha512-NupG7SWUucm3vJrvlpt9jG1XeoPJphjcivgcUUXhDJbUPy4F04LhlTiAhWSzwlCNcF8OJsMvZ/DWbpYD3pselw==} + /@smithy/util-config-provider@3.0.0: + resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + tslib: 2.6.2 + optional: true + + /@smithy/util-defaults-mode-browser@3.0.20: + resolution: {integrity: sha512-HpYmCpEThQJpCKzwzrGrklhdegRfuXI9keHRrHidbyEMliCdgic6t38MikJeZEkdIcEMhO1g95HIYMzjUzB+xg==} engines: {node: '>= 10.0.0'} requiresBuild: true dependencies: - '@smithy/property-provider': 2.0.12 - '@smithy/smithy-client': 2.1.10 - '@smithy/types': 2.3.5 + '@smithy/property-provider': 3.1.6 + '@smithy/smithy-client': 3.3.4 + '@smithy/types': 3.4.2 bowser: 2.11.0 tslib: 2.6.2 optional: true - /@smithy/util-defaults-mode-node@2.0.18: - resolution: {integrity: sha512-+3jMom/b/Cdp21tDnY4vKu249Al+G/P0HbRbct7/aSZDlROzv1tksaYukon6UUv7uoHn+/McqnsvqZHLlqvQ0g==} + /@smithy/util-defaults-mode-node@3.0.20: + resolution: {integrity: sha512-atdsHNtAX0rwTvRRGsrONU0C0XzapH6tI8T1y/OReOvWN7uBwXqqWRft6m8egU2DgeReU0xqT3PHdGCe5VRaaQ==} engines: {node: '>= 10.0.0'} requiresBuild: true dependencies: - '@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 + '@smithy/config-resolver': 3.0.8 + '@smithy/credential-provider-imds': 3.2.3 + '@smithy/node-config-provider': 3.1.7 + '@smithy/property-provider': 3.1.6 + '@smithy/smithy-client': 3.3.4 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/util-hex-encoding@2.0.0: - resolution: {integrity: sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==} - engines: {node: '>=14.0.0'} + /@smithy/util-endpoints@2.1.2: + resolution: {integrity: sha512-FEISzffb4H8DLzGq1g4MuDpcv6CIG15fXoQzDH9SjpRJv6h7J++1STFWWinilG0tQh9H1v2UKWG19Jjr2B16zQ==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: + '@smithy/node-config-provider': 3.1.7 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/util-middleware@2.0.4: - resolution: {integrity: sha512-Pbu6P4MBwRcjrLgdTR1O4Y3c0sTZn2JdOiJNcgL7EcIStcQodj+6ZTXtbyU/WTEU3MV2NMA10LxFc3AWHZ3+4A==} - engines: {node: '>=14.0.0'} + /@smithy/util-hex-encoding@3.0.0: + resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/types': 2.3.5 tslib: 2.6.2 optional: true - /@smithy/util-retry@2.0.4: - resolution: {integrity: sha512-b+n1jBBKc77C1E/zfBe1Zo7S9OXGBiGn55N0apfhZHxPUP/fMH5AhFUUcWaJh7NAnah284M5lGkBKuhnr3yK5w==} - engines: {node: '>= 14.0.0'} + /@smithy/util-middleware@3.0.6: + resolution: {integrity: sha512-BxbX4aBhI1O9p87/xM+zWy0GzT3CEVcXFPBRDoHAM+pV0eSW156pR+PSYEz0DQHDMYDsYAflC2bQNz2uaDBUZQ==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@smithy/service-error-classification': 2.0.4 - '@smithy/types': 2.3.5 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/util-stream@2.0.15: - resolution: {integrity: sha512-A/hkYJPH2N5MCWYvky4tTpQihpYAEzqnUfxDyG3L/yMndy/2sLvxnyQal9Opuj1e9FiKSTeMyjnU9xxZGs0mRw==} - engines: {node: '>=14.0.0'} + /@smithy/util-retry@3.0.6: + resolution: {integrity: sha512-BRZiuF7IwDntAbevqMco67an0Sr9oLQJqqRCsSPZZHYRnehS0LHDAkJk/pSmI7Z8c/1Vet294H7fY2fWUgB+Rg==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: - '@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 + '@smithy/service-error-classification': 3.0.6 + '@smithy/types': 3.4.2 tslib: 2.6.2 optional: true - /@smithy/util-uri-escape@2.0.0: - resolution: {integrity: sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==} - engines: {node: '>=14.0.0'} + /@smithy/util-stream@3.1.8: + resolution: {integrity: sha512-hoKOqSmb8FD3WLObuB5hwbM7bNIWgcnvkThokTvVq7J5PKjlLUK5qQQcB9zWLHIoSaIlf3VIv2OxZY2wtQjcRQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/fetch-http-handler': 3.2.8 + '@smithy/node-http-handler': 3.2.3 + '@smithy/types': 3.4.2 + '@smithy/util-base64': 3.0.0 + '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-hex-encoding': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/util-uri-escape@3.0.0: + resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==} + engines: {node: '>=16.0.0'} requiresBuild: true dependencies: tslib: 2.6.2 optional: true - /@smithy/util-utf8@2.0.0: - resolution: {integrity: sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ==} + /@smithy/util-utf8@2.3.0: + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} engines: {node: '>=14.0.0'} requiresBuild: true dependencies: - '@smithy/util-buffer-from': 2.0.0 + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.6.2 + optional: true + + /@smithy/util-utf8@3.0.0: + resolution: {integrity: sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/util-buffer-from': 3.0.0 tslib: 2.6.2 optional: true @@ -3634,13 +3698,13 @@ packages: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 - '@types/node': 20.4.7 + '@types/node': 22.6.1 dev: false /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 20.4.7 + '@types/node': 22.6.1 dev: false /@types/cookie@0.4.1: @@ -3660,7 +3724,7 @@ packages: /@types/express-serve-static-core@4.17.35: resolution: {integrity: sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==} dependencies: - '@types/node': 20.4.7 + '@types/node': 22.6.1 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 '@types/send': 0.17.1 @@ -3680,7 +3744,7 @@ packages: requiresBuild: true dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.4.7 + '@types/node': 22.6.1 dev: false optional: true @@ -3698,11 +3762,11 @@ packages: /@types/jsonwebtoken@9.0.2: resolution: {integrity: sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==} dependencies: - '@types/node': 20.4.7 + '@types/node': 22.6.1 dev: false - /@types/linkify-it@3.0.2: - resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==} + /@types/linkify-it@5.0.0: + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} requiresBuild: true dev: false optional: true @@ -3713,17 +3777,17 @@ packages: dev: false optional: true - /@types/markdown-it@12.2.3: - resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==} + /@types/markdown-it@14.1.2: + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} requiresBuild: true dependencies: - '@types/linkify-it': 3.0.2 - '@types/mdurl': 1.0.2 + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 dev: false optional: true - /@types/mdurl@1.0.2: - resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} + /@types/mdurl@2.0.0: + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} requiresBuild: true dev: false optional: true @@ -3741,6 +3805,12 @@ packages: /@types/node@20.4.7: resolution: {integrity: sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g==} + /@types/node@22.6.1: + resolution: {integrity: sha512-V48tCfcKb/e6cVUigLAaJDAILdMP0fUW6BidkPK4GpGjXcfbnoHasCZDwz3N3yVt5we2RHm4XTQCpv0KJz9zqw==} + dependencies: + undici-types: 6.19.8 + dev: false + /@types/object.omit@3.0.0: resolution: {integrity: sha512-I27IoPpH250TUzc9FzXd0P1BV/BMJuzqD3jOz98ehf9dQqGkxlq+hO1bIqZGWqCg5bVOy0g4AUVJtnxe0klDmw==} dev: false @@ -3790,7 +3860,7 @@ packages: requiresBuild: true dependencies: '@types/glob': 8.1.0 - '@types/node': 20.4.7 + '@types/node': 22.6.1 dev: false optional: true @@ -3802,7 +3872,7 @@ packages: resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} dependencies: '@types/mime': 1.3.2 - '@types/node': 20.4.7 + '@types/node': 22.6.1 dev: false /@types/serve-static@1.15.2: @@ -3810,7 +3880,7 @@ packages: dependencies: '@types/http-errors': 2.0.1 '@types/mime': 1.3.2 - '@types/node': 20.4.7 + '@types/node': 22.6.1 dev: false /@types/throttle-debounce@2.1.0: @@ -4475,7 +4545,7 @@ packages: debug: 4.3.4 express-session: 1.17.3 kruptein: 3.0.6 - mongodb: 4.17.1 + mongodb: 4.17.1(@aws-sdk/client-sso-oidc@3.654.0) transitivePeerDependencies: - supports-color dev: false @@ -4767,14 +4837,14 @@ packages: engines: {node: '>=12'} dev: false - /duplexify@4.1.2: - resolution: {integrity: sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==} + /duplexify@4.1.3: + resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} requiresBuild: true dependencies: end-of-stream: 1.4.4 inherits: 2.0.4 readable-stream: 3.6.2 - stream-shift: 1.0.1 + stream-shift: 1.0.3 dev: false optional: true @@ -4837,15 +4907,12 @@ packages: - utf-8-validate dev: false - /ent@2.2.0: - resolution: {integrity: sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==} - requiresBuild: true - dev: false - optional: true - - /entities@2.1.0: - resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==} + /ent@2.2.1: + resolution: {integrity: sha512-QHuXVeZx9d+tIQAz/XztU0ZwZf2Agg9CcXcgE1rurqvdBeDBrpSwjl8/6XUqMg7tw2Y7uAdKb2sRv+bSEFqQ5A==} + engines: {node: '>= 0.4'} requiresBuild: true + dependencies: + punycode: 1.4.1 dev: false optional: true @@ -4854,6 +4921,13 @@ packages: engines: {node: '>=0.12'} dev: false + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + requiresBuild: true + dev: false + optional: true + /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -5176,23 +5250,14 @@ packages: dev: false optional: true - /fast-xml-parser@4.2.5: - resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} + /fast-xml-parser@4.4.1: + resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} hasBin: true requiresBuild: true dependencies: strnum: 1.0.5 optional: true - /fast-xml-parser@4.2.7: - resolution: {integrity: sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig==} - hasBin: true - requiresBuild: true - dependencies: - strnum: 1.0.5 - dev: false - optional: true - /fastq@1.15.0: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: @@ -5269,18 +5334,18 @@ packages: locate-path: 6.0.0 path-exists: 4.0.0 - /firebase-admin@11.10.1: - resolution: {integrity: sha512-atv1E6GbuvcvWaD3eHwrjeP5dAVs+EaHEJhu9CThMzPY6In8QYDiUR6tq5SwGl4SdA/GcAU0nhwWc/FSJsAzfQ==} + /firebase-admin@11.11.1: + resolution: {integrity: sha512-UyEbq+3u6jWzCYbUntv/HuJiTixwh36G1R9j0v71mSvGAx/YZEWEW7uSGLYxBYE6ckVRQoKMr40PYUEzrm/4dg==} engines: {node: '>=14'} dependencies: '@fastify/busboy': 1.2.1 '@firebase/database-compat': 0.3.4 '@firebase/database-types': 0.10.4 - '@types/node': 20.4.7 + '@types/node': 22.6.1 jsonwebtoken: 9.0.2 - jwks-rsa: 3.0.1 + jwks-rsa: 3.1.0 node-forge: 1.3.1 - uuid: 9.0.0 + uuid: 9.0.1 optionalDependencies: '@google-cloud/firestore': 6.8.0 '@google-cloud/storage': 6.12.0 @@ -5379,7 +5444,7 @@ packages: extend: 3.0.2 https-proxy-agent: 5.0.1 is-stream: 2.0.1 - node-fetch: 2.6.12 + node-fetch: 2.7.0 transitivePeerDependencies: - encoding - supports-color @@ -5461,12 +5526,13 @@ packages: /glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported requiresBuild: true dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 5.1.6 + minimatch: 5.0.1 once: 1.4.0 dev: false optional: true @@ -5519,16 +5585,16 @@ packages: hasBin: true requiresBuild: true dependencies: - '@grpc/grpc-js': 1.8.21 - '@grpc/proto-loader': 0.7.8 + '@grpc/grpc-js': 1.8.22 + '@grpc/proto-loader': 0.7.13 '@types/long': 4.0.2 '@types/rimraf': 3.0.2 abort-controller: 3.0.0 - duplexify: 4.1.2 + duplexify: 4.1.3 fast-text-encoding: 1.0.6 google-auth-library: 8.9.0 is-stream-ended: 0.1.4 - node-fetch: 2.6.12 + node-fetch: 2.7.0 object-hash: 3.0.0 proto3-json-serializer: 1.1.1 protobufjs: 7.2.4 @@ -5543,6 +5609,7 @@ packages: /google-p12-pem@4.0.1: resolution: {integrity: sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==} engines: {node: '>=12.0.0'} + deprecated: Package is no longer maintained hasBin: true requiresBuild: true dependencies: @@ -5874,8 +5941,8 @@ packages: engines: {node: '>= 0.6.0'} dev: false - /jose@4.14.4: - resolution: {integrity: sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==} + /jose@4.15.9: + resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} dev: false /js-tokens@4.0.0: @@ -5896,27 +5963,27 @@ packages: dev: false optional: true - /jsdoc@4.0.2: - resolution: {integrity: sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==} + /jsdoc@4.0.3: + resolution: {integrity: sha512-Nu7Sf35kXJ1MWDZIMAuATRQTg1iIPdzh7tqJ6jjvaU/GfDf+qi5UV8zJR3Mo+/pYFvm8mzay4+6O5EWigaQBQw==} engines: {node: '>=12.0.0'} hasBin: true requiresBuild: true dependencies: '@babel/parser': 7.22.7 - '@jsdoc/salty': 0.2.5 - '@types/markdown-it': 12.2.3 + '@jsdoc/salty': 0.2.8 + '@types/markdown-it': 14.1.2 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.6.7(@types/markdown-it@12.2.3)(markdown-it@12.3.2) + markdown-it: 14.1.0 + markdown-it-anchor: 8.6.7(@types/markdown-it@14.1.2)(markdown-it@14.1.0) marked: 4.3.0 mkdirp: 1.0.4 requizzle: 0.2.4 strip-json-comments: 3.1.1 - underscore: 1.13.6 + underscore: 1.13.7 dev: false optional: true @@ -5993,14 +6060,14 @@ packages: dev: false optional: true - /jwks-rsa@3.0.1: - resolution: {integrity: sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==} + /jwks-rsa@3.1.0: + resolution: {integrity: sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==} engines: {node: '>=14'} dependencies: '@types/express': 4.17.21 '@types/jsonwebtoken': 9.0.2 debug: 4.3.4 - jose: 4.14.4 + jose: 4.15.9 limiter: 1.1.5 lru-memoizer: 2.2.0 transitivePeerDependencies: @@ -6077,19 +6144,19 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: false - /linkify-it@3.0.3: - resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==} - requiresBuild: true + /linkify-it@4.0.1: + resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==} dependencies: uc.micro: 1.0.6 dev: false - optional: true - /linkify-it@4.0.1: - resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==} + /linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + requiresBuild: true dependencies: - uc.micro: 1.0.6 + uc.micro: 2.1.0 dev: false + optional: true /linkifyjs@3.0.5: resolution: {integrity: sha512-1Y9XQH65eQKA9p2xtk+zxvnTeQBG7rdAXSkUG97DmuI/Xhji9uaUzaWxRj6rf9YC0v8KKHkxav7tnLX82Sz5Fg==} @@ -6188,12 +6255,6 @@ packages: triple-beam: 1.4.1 dev: false - /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 @@ -6257,41 +6318,42 @@ packages: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: false - /markdown-it-anchor@8.6.7(@types/markdown-it@12.2.3)(markdown-it@12.3.2): + /markdown-it-anchor@8.6.7(@types/markdown-it@14.1.2)(markdown-it@14.1.0): resolution: {integrity: sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==} requiresBuild: true peerDependencies: '@types/markdown-it': '*' markdown-it: '*' dependencies: - '@types/markdown-it': 12.2.3 - markdown-it: 12.3.2 + '@types/markdown-it': 14.1.2 + markdown-it: 14.1.0 dev: false optional: true - /markdown-it@12.3.2: - resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==} + /markdown-it@13.0.1: + resolution: {integrity: sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==} hasBin: true - requiresBuild: true dependencies: argparse: 2.0.1 - entities: 2.1.0 - linkify-it: 3.0.3 + entities: 3.0.1 + linkify-it: 4.0.1 mdurl: 1.0.1 uc.micro: 1.0.6 dev: false - optional: true - /markdown-it@13.0.1: - resolution: {integrity: sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==} + /markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} hasBin: true + requiresBuild: true dependencies: argparse: 2.0.1 - entities: 3.0.1 - linkify-it: 4.0.1 - mdurl: 1.0.1 - uc.micro: 1.0.6 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 dev: false + optional: true /marked@4.3.0: resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} @@ -6305,6 +6367,12 @@ packages: resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} dev: false + /mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + requiresBuild: true + dev: false + optional: true + /media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -6391,16 +6459,6 @@ packages: engines: {node: '>=10'} dependencies: brace-expansion: 2.0.1 - dev: true - - /minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} - requiresBuild: true - dependencies: - brace-expansion: 2.0.1 - dev: false - optional: true /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -6454,7 +6512,7 @@ packages: '@types/whatwg-url': 8.2.2 whatwg-url: 11.0.0 - /mongodb@4.17.1: + /mongodb@4.17.1(@aws-sdk/client-sso-oidc@3.654.0): resolution: {integrity: sha512-MBuyYiPUPRTqfH2dV0ya4dcr2E5N52ocBuZ8Sgg/M030nGF78v855B3Z27mZJnp8PxjnUquEnAtjOsphgMZOlQ==} engines: {node: '>=12.9.0'} dependencies: @@ -6462,23 +6520,25 @@ packages: mongodb-connection-string-url: 2.6.0 socks: 2.7.1 optionalDependencies: - '@aws-sdk/credential-providers': 3.427.0 - '@mongodb-js/saslprep': 1.1.0 + '@aws-sdk/credential-providers': 3.654.0(@aws-sdk/client-sso-oidc@3.654.0) + '@mongodb-js/saslprep': 1.1.9 transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' - aws-crt - /mongoose@6.12.0: + /mongoose@6.12.0(@aws-sdk/client-sso-oidc@3.654.0): resolution: {integrity: sha512-sd/q83C6TBRPBrrD2A/POSbA/exbCFM2WOuY7Lf2JuIJFlHFG39zYSDTTAEiYlzIfahNOLmXPxBGFxdAch41Mw==} engines: {node: '>=12.0.0'} dependencies: bson: 4.7.2 kareem: 2.5.1 - mongodb: 4.17.1 + mongodb: 4.17.1(@aws-sdk/client-sso-oidc@3.654.0) mpath: 0.9.0 mquery: 4.0.3 ms: 2.1.3 sift: 16.0.1 transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' - aws-crt - supports-color dev: false @@ -6528,8 +6588,8 @@ packages: uuid: 8.3.2 dev: false - /node-fetch@2.6.12: - resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} requiresBuild: true peerDependencies: @@ -7001,7 +7061,7 @@ packages: engines: {node: '>=12.0.0'} requiresBuild: true dependencies: - protobufjs: 7.2.5 + protobufjs: 7.4.0 dev: false optional: true @@ -7018,12 +7078,12 @@ packages: espree: 9.6.1 estraverse: 5.3.0 glob: 8.1.0 - jsdoc: 4.0.2 + jsdoc: 4.0.3 minimist: 1.2.8 protobufjs: 7.2.4 semver: 7.5.4 - tmp: 0.2.1 - uglify-js: 3.17.4 + tmp: 0.2.3 + uglify-js: 3.19.3 dev: false optional: true @@ -7042,13 +7102,13 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.4.7 + '@types/node': 22.6.1 long: 5.2.3 dev: false optional: true - /protobufjs@7.2.5: - resolution: {integrity: sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==} + /protobufjs@7.4.0: + resolution: {integrity: sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==} engines: {node: '>=12.0.0'} requiresBuild: true dependencies: @@ -7062,7 +7122,7 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.4.7 + '@types/node': 22.6.1 long: 5.2.3 dev: false optional: true @@ -7083,10 +7143,23 @@ packages: resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} dev: true + /punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + requiresBuild: true + dev: false + optional: true + /punycode@1.3.2: resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==} dev: false + /punycode@1.4.1: + resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} + requiresBuild: true + dev: false + optional: true + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} @@ -7769,8 +7842,8 @@ packages: dev: false optional: true - /stream-shift@1.0.1: - resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==} + /stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} requiresBuild: true dev: false optional: true @@ -7928,9 +8001,9 @@ packages: dependencies: http-proxy-agent: 5.0.0 https-proxy-agent: 5.0.1 - node-fetch: 2.6.12 + node-fetch: 2.7.0 stream-events: 1.0.5 - uuid: 9.0.0 + uuid: 9.0.1 transitivePeerDependencies: - encoding - supports-color @@ -7978,12 +8051,10 @@ packages: '@popperjs/core': 2.11.8 dev: false - /tmp@0.2.1: - resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} - engines: {node: '>=8.17.0'} + /tmp@0.2.3: + resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + engines: {node: '>=14.14'} requiresBuild: true - dependencies: - rimraf: 3.0.2 dev: false optional: true @@ -8027,13 +8098,14 @@ packages: engines: {node: '>= 14.0.0'} dev: false - /tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} requiresBuild: true optional: true - /tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + /tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + dev: false /type-check@0.3.2: resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} @@ -8076,8 +8148,14 @@ packages: resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} dev: false - /uglify-js@3.17.4: - resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} + /uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + requiresBuild: true + dev: false + optional: true + + /uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} hasBin: true requiresBuild: true @@ -8095,12 +8173,16 @@ packages: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: true - /underscore@1.13.6: - resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} + /underscore@1.13.7: + resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} requiresBuild: true dev: false optional: true + /undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + dev: false + /unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} @@ -8208,11 +8290,12 @@ packages: /uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true + dev: false - /uuid@9.0.0: - resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true - dev: false + requiresBuild: true /v8-compile-cache@2.3.0: resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} diff --git a/src/modules/fcm.js b/src/modules/fcm.js index 1fc53946..25ca9e72 100644 --- a/src/modules/fcm.js +++ b/src/modules/fcm.js @@ -196,7 +196,7 @@ const sendMessageByTokens = async (tokens, type, title, body, icon, link) => { title, body, url: link || "/", - icon: icon || "/icons-512.png", + icon: icon || "https://taxi.sparcs.org/icons-512.png", click_action: "FLUTTER_NOTIFICATION_CLICK", }, apns: { payload: { aps: { alert: { title, body } } } }, @@ -204,9 +204,8 @@ const sendMessageByTokens = async (tokens, type, title, body, icon, link) => { ttl: 0, }, }; - const { responses, failureCount } = await getMessaging().sendMulticast( - message - ); + const { responses, failureCount } = + await getMessaging().sendEachForMulticast(message); // 메시지 전송에 실패한 기기가 존재할 경우, 해당 기기의 deviceToken을 DB에서 삭제합니다. if (failureCount) { From e8d26d61124441d8caab5d7e86c0efdfdd233cc6 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 25 Sep 2024 01:42:20 +0900 Subject: [PATCH 25/25] Refactor: use high priority fcm for android devices --- src/modules/fcm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/fcm.js b/src/modules/fcm.js index 25ca9e72..fa37f64e 100644 --- a/src/modules/fcm.js +++ b/src/modules/fcm.js @@ -201,7 +201,7 @@ const sendMessageByTokens = async (tokens, type, title, body, icon, link) => { }, apns: { payload: { aps: { alert: { title, body } } } }, android: { - ttl: 0, + priority: "high", }, }; const { responses, failureCount } =