diff --git a/infra/mysql/initdb.d/init.sql b/infra/mysql/initdb.d/init.sql index 9a64249e..aa8bdafa 100644 --- a/infra/mysql/initdb.d/init.sql +++ b/infra/mysql/initdb.d/init.sql @@ -212,6 +212,7 @@ create table room announcement varchar(100), room_image varchar(500), manager_nickname varchar(30), + deleted_at datetime(6), created_at datetime(6) not null, updated_at datetime(6), primary key (id) diff --git a/src/main/java/com/moabam/api/application/room/CertificationService.java b/src/main/java/com/moabam/api/application/room/CertificationService.java index a6676f1a..c4e86434 100644 --- a/src/main/java/com/moabam/api/application/room/CertificationService.java +++ b/src/main/java/com/moabam/api/application/room/CertificationService.java @@ -115,14 +115,6 @@ public Certification findCertification(Long certificationId) { .orElseThrow(() -> new NotFoundException(CERTIFICATION_NOT_FOUND)); } - public List findCertifications(List routines) { - return certificationsSearchRepository.findCertificationsByRoutines(routines); - } - - public void deleteCertifications(List certifications) { - certificationRepository.deleteAll(certifications); - } - private void validateCertifyTime(LocalDateTime now, int certifyTime) { LocalTime targetTime = LocalTime.of(certifyTime, 0); LocalDateTime targetDateTime = LocalDateTime.of(now.toLocalDate(), targetTime); diff --git a/src/main/java/com/moabam/api/application/room/RoomService.java b/src/main/java/com/moabam/api/application/room/RoomService.java index 2a8fae16..5ad81780 100644 --- a/src/main/java/com/moabam/api/application/room/RoomService.java +++ b/src/main/java/com/moabam/api/application/room/RoomService.java @@ -1,12 +1,30 @@ package com.moabam.api.application.room; +import static com.moabam.api.domain.room.RoomType.*; +import static com.moabam.global.error.model.ErrorMessage.*; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import com.moabam.api.application.member.MemberService; import com.moabam.api.application.room.mapper.ParticipantMapper; import com.moabam.api.application.room.mapper.RoomMapper; import com.moabam.api.application.room.mapper.RoutineMapper; import com.moabam.api.domain.member.Member; -import com.moabam.api.domain.room.*; -import com.moabam.api.domain.room.repository.*; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.RoomType; +import com.moabam.api.domain.room.Routine; +import com.moabam.api.domain.room.repository.DailyMemberCertificationRepository; +import com.moabam.api.domain.room.repository.ParticipantRepository; +import com.moabam.api.domain.room.repository.ParticipantSearchRepository; +import com.moabam.api.domain.room.repository.RoomRepository; +import com.moabam.api.domain.room.repository.RoutineRepository; import com.moabam.api.dto.room.CreateRoomRequest; import com.moabam.api.dto.room.EnterRoomRequest; import com.moabam.api.dto.room.ModifyRoomRequest; @@ -14,19 +32,9 @@ import com.moabam.global.error.exception.BadRequestException; import com.moabam.global.error.exception.ForbiddenException; import com.moabam.global.error.exception.NotFoundException; + import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.List; - -import static com.moabam.api.domain.room.RoomType.MORNING; -import static com.moabam.api.domain.room.RoomType.NIGHT; -import static com.moabam.global.error.model.ErrorMessage.*; @Service @RequiredArgsConstructor @@ -34,204 +42,199 @@ @Transactional(readOnly = true) public class RoomService { - private final RoomRepository roomRepository; - private final RoutineRepository routineRepository; - private final ParticipantRepository participantRepository; - private final ParticipantSearchRepository participantSearchRepository; - private final DailyMemberCertificationRepository dailyMemberCertificationRepository; - private final CertificationService certificationService; - private final MemberService memberService; - private final ClockHolder clockHolder; - - @Transactional - public Long createRoom(Long memberId, CreateRoomRequest createRoomRequest) { - Room room = RoomMapper.toRoomEntity(createRoomRequest); - List routines = RoutineMapper.toRoutineEntities(room, createRoomRequest.routines()); - Participant participant = ParticipantMapper.toParticipant(room, memberId); - - validateEnteredRoomCount(memberId, room.getRoomType()); - - Member member = memberService.findMember(memberId); - member.enterRoom(room.getRoomType()); - participant.enableManager(); - room.changeManagerNickname(member.getNickname()); - - Room savedRoom = roomRepository.save(room); - routineRepository.saveAll(routines); - participantRepository.save(participant); - - return savedRoom.getId(); - } - - @Transactional - public void modifyRoom(Long memberId, Long roomId, ModifyRoomRequest modifyRoomRequest) { - Participant participant = getParticipant(memberId, roomId); - validateManagerAuthorization(participant); - - Room room = participant.getRoom(); - room.changeTitle(modifyRoomRequest.title()); - room.changeAnnouncement(modifyRoomRequest.announcement()); - room.changePassword(modifyRoomRequest.password()); - room.changeMaxCount(modifyRoomRequest.maxUserCount()); - - if (room.getCertifyTime() != modifyRoomRequest.certifyTime()) { - validateChangeCertifyTime(roomId); - } - room.changeCertifyTime(modifyRoomRequest.certifyTime()); - } - - @Transactional - public void enterRoom(Long memberId, Long roomId, EnterRoomRequest enterRoomRequest) { - Room room = roomRepository.findWithPessimisticLockById(roomId).orElseThrow( - () -> new NotFoundException(ROOM_NOT_FOUND)); - validateRoomEnter(memberId, enterRoomRequest.password(), room); - - Member member = memberService.findMember(memberId); - member.enterRoom(room.getRoomType()); - room.increaseCurrentUserCount(); - - Participant participant = ParticipantMapper.toParticipant(room, memberId); - participantRepository.save(participant); - } - - @Transactional - public void exitRoom(Long memberId, Long roomId) { - Participant participant = getParticipant(memberId, roomId); - Room room = participant.getRoom(); - - validateRoomExit(participant, room); - - Member member = memberService.findMember(memberId); - member.exitRoom(room.getRoomType()); - - participant.removeRoom(); - participantRepository.flush(); - participantRepository.delete(participant); - - if (!participant.isManager()) { - room.decreaseCurrentUserCount(); - return; - } - - List routines = routineRepository.findAllByRoomId(roomId); - List certifications = certificationService.findCertifications(routines); - - certificationService.deleteCertifications(certifications); - routineRepository.deleteAll(routines); - roomRepository.delete(room); - } - - @Transactional - public void mandateManager(Long managerId, Long roomId, Long memberId) { - Participant managerParticipant = getParticipant(managerId, roomId); - Participant memberParticipant = getParticipant(memberId, roomId); - validateManagerAuthorization(managerParticipant); - - Room room = managerParticipant.getRoom(); - Member member = memberService.findMember(memberParticipant.getMemberId()); - room.changeManagerNickname(member.getNickname()); - - managerParticipant.disableManager(); - memberParticipant.enableManager(); - } - - @Transactional - public void deportParticipant(Long managerId, Long roomId, Long memberId) { - validateDeportParticipant(managerId, memberId); - Participant managerParticipant = getParticipant(managerId, roomId); - Participant memberParticipant = getParticipant(memberId, roomId); - validateManagerAuthorization(managerParticipant); - - Room room = managerParticipant.getRoom(); - memberParticipant.removeRoom(); - participantRepository.flush(); - participantRepository.delete(memberParticipant); - room.decreaseCurrentUserCount(); - - Member member = memberService.findMember(memberId); - member.exitRoom(room.getRoomType()); - } - - public boolean checkIfParticipant(Long memberId, Long roomId) { - try { - getParticipant(memberId, roomId); - return true; - } catch (NotFoundException e) { - return false; - } - } - - public Room findRoom(Long roomId) { - return roomRepository.findById(roomId) - .orElseThrow(() -> new NotFoundException(ROOM_NOT_FOUND)); - } - - private void validateChangeCertifyTime(Long roomId) { - if (certificationService.existsAnyMemberCertification(roomId, clockHolder.date())) { - throw new BadRequestException(UNAVAILABLE_TO_CHANGE_CERTIFY_TIME); - } - } - - private Participant getParticipant(Long memberId, Long roomId) { - return participantSearchRepository.findOne(memberId, roomId) - .orElseThrow(() -> new NotFoundException(PARTICIPANT_NOT_FOUND)); - } - - private void validateDeportParticipant(Long managerId, Long memberId) { - if (managerId.equals(memberId)) { - throw new BadRequestException(PARTICIPANT_DEPORT_ERROR); - } - } - - private void validateManagerAuthorization(Participant participant) { - if (!participant.isManager()) { - throw new ForbiddenException(ROOM_MODIFY_UNAUTHORIZED_REQUEST); - } - } - - private void validateRoomEnter(Long memberId, String requestPassword, Room room) { - validateEnteredRoomCount(memberId, room.getRoomType()); - validateCertifyTime(room); - - if (!StringUtils.isEmpty(requestPassword) && !room.getPassword().equals(requestPassword)) { - throw new BadRequestException(WRONG_ROOM_PASSWORD); - } - if (room.getCurrentUserCount() == room.getMaxUserCount()) { - throw new BadRequestException(ROOM_MAX_USER_REACHED); - } - } - - private void validateEnteredRoomCount(Long memberId, RoomType roomType) { - Member member = memberService.findMember(memberId); - - if (roomType.equals(MORNING) && member.getCurrentMorningCount() >= 3) { - throw new BadRequestException(MEMBER_ROOM_EXCEED); - } - if (roomType.equals(NIGHT) && member.getCurrentNightCount() >= 3) { - throw new BadRequestException(MEMBER_ROOM_EXCEED); - } - } - - private void validateCertifyTime(Room room) { - LocalDateTime now = clockHolder.times(); - LocalTime targetTime = LocalTime.of(room.getCertifyTime(), 0); - LocalDateTime targetDateTime = LocalDateTime.of(now.toLocalDate(), targetTime); - - LocalDateTime plusTenMinutes = targetDateTime.plusMinutes(10); - - if (now.isAfter(targetDateTime) && now.isBefore(plusTenMinutes)) { - throw new BadRequestException(ROOM_ENTER_FAILED); - } - } - - private void validateRoomExit(Participant participant, Room room) { - if (participant.isManager() && room.getCurrentUserCount() != 1) { - throw new BadRequestException(ROOM_EXIT_MANAGER_FAIL); - } - - if (dailyMemberCertificationRepository.existsByMemberIdAndRoomIdAndCreatedAtBetween(participant.getMemberId(), - room.getId(), clockHolder.date().atStartOfDay(), clockHolder.date().atTime(LocalTime.MAX))) { - throw new BadRequestException(CERTIFIED_ROOM_EXIT_FAILED); - } - } + private final RoomRepository roomRepository; + private final RoutineRepository routineRepository; + private final ParticipantRepository participantRepository; + private final ParticipantSearchRepository participantSearchRepository; + private final DailyMemberCertificationRepository dailyMemberCertificationRepository; + private final CertificationService certificationService; + private final MemberService memberService; + private final ClockHolder clockHolder; + + @Transactional + public Long createRoom(Long memberId, CreateRoomRequest createRoomRequest) { + Room room = RoomMapper.toRoomEntity(createRoomRequest); + List routines = RoutineMapper.toRoutineEntities(room, createRoomRequest.routines()); + Participant participant = ParticipantMapper.toParticipant(room, memberId); + + validateEnteredRoomCount(memberId, room.getRoomType()); + + Member member = memberService.findMember(memberId); + member.enterRoom(room.getRoomType()); + participant.enableManager(); + room.changeManagerNickname(member.getNickname()); + + Room savedRoom = roomRepository.save(room); + routineRepository.saveAll(routines); + participantRepository.save(participant); + + return savedRoom.getId(); + } + + @Transactional + public void modifyRoom(Long memberId, Long roomId, ModifyRoomRequest modifyRoomRequest) { + Participant participant = getParticipant(memberId, roomId); + validateManagerAuthorization(participant); + + Room room = participant.getRoom(); + room.changeTitle(modifyRoomRequest.title()); + room.changeAnnouncement(modifyRoomRequest.announcement()); + room.changePassword(modifyRoomRequest.password()); + room.changeMaxCount(modifyRoomRequest.maxUserCount()); + + if (room.getCertifyTime() != modifyRoomRequest.certifyTime()) { + validateChangeCertifyTime(roomId); + } + room.changeCertifyTime(modifyRoomRequest.certifyTime()); + } + + @Transactional + public void enterRoom(Long memberId, Long roomId, EnterRoomRequest enterRoomRequest) { + Room room = roomRepository.findWithPessimisticLockById(roomId).orElseThrow( + () -> new NotFoundException(ROOM_NOT_FOUND)); + validateRoomEnter(memberId, enterRoomRequest.password(), room); + + Member member = memberService.findMember(memberId); + member.enterRoom(room.getRoomType()); + room.increaseCurrentUserCount(); + + Participant participant = ParticipantMapper.toParticipant(room, memberId); + participantRepository.save(participant); + } + + @Transactional + public void exitRoom(Long memberId, Long roomId) { + Participant participant = getParticipant(memberId, roomId); + Room room = participant.getRoom(); + + validateRoomExit(participant, room); + + Member member = memberService.findMember(memberId); + member.exitRoom(room.getRoomType()); + + participant.removeRoom(); + participantRepository.flush(); + participantRepository.delete(participant); + + if (!participant.isManager()) { + room.decreaseCurrentUserCount(); + return; + } + + roomRepository.delete(room); + } + + @Transactional + public void mandateManager(Long managerId, Long roomId, Long memberId) { + Participant managerParticipant = getParticipant(managerId, roomId); + Participant memberParticipant = getParticipant(memberId, roomId); + validateManagerAuthorization(managerParticipant); + + Room room = managerParticipant.getRoom(); + Member member = memberService.findMember(memberParticipant.getMemberId()); + room.changeManagerNickname(member.getNickname()); + + managerParticipant.disableManager(); + memberParticipant.enableManager(); + } + + @Transactional + public void deportParticipant(Long managerId, Long roomId, Long memberId) { + validateDeportParticipant(managerId, memberId); + Participant managerParticipant = getParticipant(managerId, roomId); + Participant memberParticipant = getParticipant(memberId, roomId); + validateManagerAuthorization(managerParticipant); + + Room room = managerParticipant.getRoom(); + memberParticipant.removeRoom(); + participantRepository.flush(); + participantRepository.delete(memberParticipant); + room.decreaseCurrentUserCount(); + + Member member = memberService.findMember(memberId); + member.exitRoom(room.getRoomType()); + } + + public boolean checkIfParticipant(Long memberId, Long roomId) { + try { + getParticipant(memberId, roomId); + return true; + } catch (NotFoundException e) { + return false; + } + } + + public Room findRoom(Long roomId) { + return roomRepository.findById(roomId) + .orElseThrow(() -> new NotFoundException(ROOM_NOT_FOUND)); + } + + private void validateChangeCertifyTime(Long roomId) { + if (certificationService.existsAnyMemberCertification(roomId, clockHolder.date())) { + throw new BadRequestException(UNAVAILABLE_TO_CHANGE_CERTIFY_TIME); + } + } + + private Participant getParticipant(Long memberId, Long roomId) { + return participantSearchRepository.findOne(memberId, roomId) + .orElseThrow(() -> new NotFoundException(PARTICIPANT_NOT_FOUND)); + } + + private void validateDeportParticipant(Long managerId, Long memberId) { + if (managerId.equals(memberId)) { + throw new BadRequestException(PARTICIPANT_DEPORT_ERROR); + } + } + + private void validateManagerAuthorization(Participant participant) { + if (!participant.isManager()) { + throw new ForbiddenException(ROOM_MODIFY_UNAUTHORIZED_REQUEST); + } + } + + private void validateRoomEnter(Long memberId, String requestPassword, Room room) { + validateEnteredRoomCount(memberId, room.getRoomType()); + validateCertifyTime(room); + + if (!StringUtils.isEmpty(requestPassword) && !room.getPassword().equals(requestPassword)) { + throw new BadRequestException(WRONG_ROOM_PASSWORD); + } + if (room.getCurrentUserCount() == room.getMaxUserCount()) { + throw new BadRequestException(ROOM_MAX_USER_REACHED); + } + } + + private void validateEnteredRoomCount(Long memberId, RoomType roomType) { + Member member = memberService.findMember(memberId); + + if (roomType.equals(MORNING) && member.getCurrentMorningCount() >= 3) { + throw new BadRequestException(MEMBER_ROOM_EXCEED); + } + if (roomType.equals(NIGHT) && member.getCurrentNightCount() >= 3) { + throw new BadRequestException(MEMBER_ROOM_EXCEED); + } + } + + private void validateCertifyTime(Room room) { + LocalDateTime now = clockHolder.times(); + LocalTime targetTime = LocalTime.of(room.getCertifyTime(), 0); + LocalDateTime targetDateTime = LocalDateTime.of(now.toLocalDate(), targetTime); + + LocalDateTime plusTenMinutes = targetDateTime.plusMinutes(10); + + if (now.isAfter(targetDateTime) && now.isBefore(plusTenMinutes)) { + throw new BadRequestException(ROOM_ENTER_FAILED); + } + } + + private void validateRoomExit(Participant participant, Room room) { + if (participant.isManager() && room.getCurrentUserCount() != 1) { + throw new BadRequestException(ROOM_EXIT_MANAGER_FAIL); + } + + if (dailyMemberCertificationRepository.existsByMemberIdAndRoomIdAndCreatedAtBetween(participant.getMemberId(), + room.getId(), clockHolder.date().atStartOfDay(), clockHolder.date().atTime(LocalTime.MAX))) { + throw new BadRequestException(CERTIFIED_ROOM_EXIT_FAILED); + } + } } diff --git a/src/main/java/com/moabam/api/application/room/SearchService.java b/src/main/java/com/moabam/api/application/room/SearchService.java index aafb5bb9..fc5dce63 100644 --- a/src/main/java/com/moabam/api/application/room/SearchService.java +++ b/src/main/java/com/moabam/api/application/room/SearchService.java @@ -263,6 +263,7 @@ private List getTodayCertificateRankResponses(Long List participants = participantSearchRepository.findAllParticipantsByRoomId(roomId); List members = memberService.getRoomMembers(participants.stream() .map(Participant::getMemberId) + .distinct() .toList()); List knocks = notificationService.getMyKnockStatusInRoom(memberId, roomId, participants); @@ -324,6 +325,7 @@ private List uncompletedMembers( List allMemberIds = participants.stream() .map(Participant::getMemberId) + .distinct() .collect(Collectors.toList()); List certifiedMemberIds = dailyMemberCertifications.stream() @@ -372,6 +374,7 @@ private CertificationImagesResponse getCertificationImages(Long memberId, List participants, LocalDate date) { Participant participant = participants.stream() .filter(p -> p.getMemberId().equals(memberId)) + .filter(p -> p.getDeletedAt() == null) .findAny() .orElseThrow(() -> new NotFoundException(ROOM_DETAILS_ERROR)); diff --git a/src/main/java/com/moabam/api/domain/room/Participant.java b/src/main/java/com/moabam/api/domain/room/Participant.java index d6274a4d..0a0d3d02 100644 --- a/src/main/java/com/moabam/api/domain/room/Participant.java +++ b/src/main/java/com/moabam/api/domain/room/Participant.java @@ -76,6 +76,5 @@ public void updateCertifyCount() { public void removeRoom() { this.deletedRoomTitle = this.room.getTitle(); - this.room = null; } } diff --git a/src/main/java/com/moabam/api/domain/room/Room.java b/src/main/java/com/moabam/api/domain/room/Room.java index 00a7ac42..5505b3d7 100644 --- a/src/main/java/com/moabam/api/domain/room/Room.java +++ b/src/main/java/com/moabam/api/domain/room/Room.java @@ -4,7 +4,10 @@ import static com.moabam.global.error.model.ErrorMessage.*; import static java.util.Objects.*; +import java.time.LocalDateTime; + import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.SQLDelete; import com.moabam.global.common.entity.BaseTimeEntity; import com.moabam.global.error.exception.BadRequestException; @@ -26,6 +29,7 @@ @Getter @Table(name = "room") @NoArgsConstructor(access = AccessLevel.PROTECTED) +@SQLDelete(sql = "UPDATE room SET deleted_at = CURRENT_TIMESTAMP where id = ?") public class Room extends BaseTimeEntity { private static final int LEVEL_5 = 5; @@ -85,6 +89,9 @@ public class Room extends BaseTimeEntity { @Column(name = "manager_nickname", length = 30) private String managerNickname; + @Column(name = "deleted_at") + private LocalDateTime deletedAt; + @Builder private Room(Long id, String title, String password, RoomType roomType, int certifyTime, int maxUserCount) { this.id = id; diff --git a/src/test/java/com/moabam/api/presentation/RoomControllerTest.java b/src/test/java/com/moabam/api/presentation/RoomControllerTest.java index d04794d1..36acce1b 100644 --- a/src/test/java/com/moabam/api/presentation/RoomControllerTest.java +++ b/src/test/java/com/moabam/api/presentation/RoomControllerTest.java @@ -717,12 +717,8 @@ void manager_delete_room_success() throws Exception { .andExpect(status().isOk()) .andDo(print()); - List deletedRoom = roomRepository.findAll(); - List deletedRoutine = routineRepository.findAll(); List deletedParticipant = participantRepository.findAll(); - assertThat(deletedRoom).isEmpty(); - assertThat(deletedRoutine).hasSize(0); assertThat(deletedParticipant).hasSize(1); assertThat(deletedParticipant.get(0).getDeletedAt()).isNotNull(); assertThat(deletedParticipant.get(0).getDeletedRoomTitle()).isNotNull();