diff --git a/src/routes/docs/rooms.js b/src/routes/docs/rooms.js index 87488b11..3ac66e6b 100644 --- a/src/routes/docs/rooms.js +++ b/src/routes/docs/rooms.js @@ -98,6 +98,11 @@ roomsDocs[`${apiPrefix}/create`] = { error: "Rooms/create : participating in too many rooms", }, }, + "사용자가 아직 송금하지 않은 방이 존재": { + value: { + error: "Rooms/create : user has send-required rooms", + }, + }, }, }, }, @@ -309,6 +314,11 @@ roomsDocs[`${apiPrefix}/join`] = { error: "Rooms/join : participating in too many rooms", }, }, + "사용자가 아직 송금하지 않은 방이 존재": { + value: { + error: "Rooms/join : user has send-required rooms", + }, + }, "입력한 시간의 방이 이미 출발함": { value: { error: "Rooms/join : The room has already departed", diff --git a/src/services/rooms.js b/src/services/rooms.js index a1a61913..218a23d5 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -72,6 +72,14 @@ const createHandler = async (req, res) => { }); } + // 사용자가 참여한 진행중인 방 중 송금을 아직 완료하지 않은 방이 있다면 오류를 반환합니다. + const isSendRequired = checkIsSendRequired(user); + if (isSendRequired) { + return res.status(400).json({ + error: "Rooms/create : user has send-required rooms", + }); + } + const part = [{ user: user._id }]; // settlementStatus는 기본적으로 "not-departed"로 설정됨 let room = new roomModel({ @@ -114,58 +122,6 @@ const createHandler = async (req, res) => { } }; -const checkIsAbusing = ( - { from, to, time, maxPartLength }, - countRecentlyMadeRooms, - candidateRooms -) => { - /** - * 방을 생성하였을 때, 다음 조건 중 하나라도 만족하게 되면 어뷰징 가능성이 있다고 판단합니다. - * 1. 참여한 방 중, 생성하려는 방의 출발 시간 앞 뒤 12시간 내에 출발하는 방이 3개 이상인 경우 - * 2. 참여한 방 중, 생성하려는 방의 출발 시간 앞 뒤 12시간 내에 출발하는 방이 2개이고, 다음 조건 중 하나 이상을 만족하는 경우 - * a. 두 방의 출발 시간 간격이 1시간 이하인 경우 - * b. 두 방의 출발 시간 간격이 1시간 초과이고, 다음 조건 중 하나 이상을 만족하는 경우 - * i. 두 방의 출발지가 같은 경우 - * ii. 두 방의 목적지가 같은 경우 - * iii. 먼저 출발하는 방의 목적지와 나중에 출발하는 방의 출발지가 다른 경우 - * iv. 두 방의 최대 탑승 가능 인원이 모두 2명인 경우 - * 3. 최근 24시간 내에 생성한 방이 4개 이상인 경우 - */ - - if (countRecentlyMadeRooms + 1 >= 4) return true; // 조건 3 - - if (candidateRooms.length + 1 >= 3) return true; // 조건 1 - if (candidateRooms.length + 1 < 2) return false; // 조건 2의 여집합 - - let firstRoom = { - from: candidateRooms[0].from.toString(), - to: candidateRooms[0].to.toString(), - time: candidateRooms[0].time, - maxPartLength: candidateRooms[0].maxPartLength, - }; - let secondRoom = { - from, - to, - time: new Date(time), - maxPartLength, - }; - if (secondRoom.time < firstRoom.time) { - [firstRoom, secondRoom] = [secondRoom, firstRoom]; - } - - if (secondRoom.time - firstRoom.time <= 3600000) return true; // 조건 2-a - if ( - firstRoom.from === secondRoom.from || - firstRoom.to === secondRoom.to || - firstRoom.to !== secondRoom.from - ) - return true; // 조건 2-b-i, 2-b-ii, 2-b-iii - if (firstRoom.maxPartLength === 2 && secondRoom.maxPartLength === 2) - return true; // 조건 2-b-iv - - return false; -}; - const createTestHandler = async (req, res) => { // 이 Handler에서는 Parameter에 대해 추가적인 Validation을 하지 않습니다. const { time } = req.body; @@ -288,6 +244,14 @@ const joinHandler = async (req, res) => { }); } + // 사용자가 참여한 진행중인 방 중 송금을 아직 완료하지 않은 방이 있다면 오류를 반환합니다. + const isSendRequired = checkIsSendRequired(user); + if (isSendRequired) { + return res.status(400).json({ + error: "Rooms/join : user has send-required rooms", + }); + } + const room = await roomModel.findById(req.body.roomId); if (!room) { res.status(404).json({ @@ -721,6 +685,80 @@ const commitPaymentHandler = async (req, res) => { } }; +const checkIsAbusing = ( + { from, to, time, maxPartLength }, + countRecentlyMadeRooms, + candidateRooms +) => { + /** + * 방을 생성하였을 때, 다음 조건 중 하나라도 만족하게 되면 어뷰징 가능성이 있다고 판단합니다. + * 1. 참여한 방 중, 생성하려는 방의 출발 시간 앞 뒤 12시간 내에 출발하는 방이 3개 이상인 경우 + * 2. 참여한 방 중, 생성하려는 방의 출발 시간 앞 뒤 12시간 내에 출발하는 방이 2개이고, 다음 조건 중 하나 이상을 만족하는 경우 + * a. 두 방의 출발 시간 간격이 1시간 이하인 경우 + * b. 두 방의 출발 시간 간격이 1시간 초과이고, 다음 조건 중 하나 이상을 만족하는 경우 + * i. 두 방의 출발지가 같은 경우 + * ii. 두 방의 목적지가 같은 경우 + * iii. 먼저 출발하는 방의 목적지와 나중에 출발하는 방의 출발지가 다른 경우 + * iv. 두 방의 최대 탑승 가능 인원이 모두 2명인 경우 + * 3. 최근 24시간 내에 생성한 방이 4개 이상인 경우 + */ + + if (countRecentlyMadeRooms + 1 >= 4) return true; // 조건 3 + + if (candidateRooms.length + 1 >= 3) return true; // 조건 1 + if (candidateRooms.length + 1 < 2) return false; // 조건 2의 여집합 + + let firstRoom = { + from: candidateRooms[0].from.toString(), + to: candidateRooms[0].to.toString(), + time: candidateRooms[0].time, + maxPartLength: candidateRooms[0].maxPartLength, + }; + let secondRoom = { + from, + to, + time: new Date(time), + maxPartLength, + }; + if (secondRoom.time < firstRoom.time) { + [firstRoom, secondRoom] = [secondRoom, firstRoom]; + } + + if (secondRoom.time - firstRoom.time <= 3600000) return true; // 조건 2-a + if ( + firstRoom.from === secondRoom.from || + firstRoom.to === secondRoom.to || + firstRoom.to !== secondRoom.from + ) + return true; // 조건 2-b-i, 2-b-ii, 2-b-iii + if (firstRoom.maxPartLength === 2 && secondRoom.maxPartLength === 2) + return true; // 조건 2-b-iv + + return false; +}; + +/** + * User Object가 주어졌을 때, 해당 유저가 참여한 방 중 아직 유저가 송금하지 않은 방이 있는지 확인합니다. + * @param {Object} userObject - userObject입니다. ongoingRoom 정보를 포함한 형태의 object여야 합니다. + * @return {Boolean} 송금해야 하는 방이 있는지 여부를 반환합니다. + */ +const checkIsSendRequired = (userObject) => { + // user의 참여중인 방의 part 정보만 가져오기 + const ongoingRoomParts = userObject.ongoingRoom.map((room) => room.part); + // part에서 자신의 id에 해당하는 part만 가져오기 + const userParts = ongoingRoomParts + .map((partList) => + partList.filter((part) => part.user.equals(userObject._id)) + ) + .filter((partList) => partList.length > 0); + // 해당 part object들 중 settlementStatus가 "send-required"인 part 찾기 + const sendRequired = userParts + .map((partList) => partList[0].settlementStatus) + .filter((status) => status === "send-required"); + + return sendRequired.length > 0; +}; + /** * @todo Unused -> Maybe used in the future? */