Skip to content

Commit

Permalink
feat: 방 참여, 나가기 기능 구현 (#28)
Browse files Browse the repository at this point in the history
* feat: Room, Participant, Routine, Certification 엔티티 생성

* feat: Room 엔티티 인증 시간 검증 로직 추가

* test: Room 엔티티 테스트 코드 작성

* refactor: Room 관련 엔티티 수정

* feat: 방 생성 기능 구현

* chore: DynamicQuery Jacoco 예외 추가

* test: 방 생성 테스트 코드 작성

* feat: 방 수정 기능 구현

* test: 방 수정 통합 테스트 작성

* refactor: Member 관련 파일 이동

* refactor: checkStyle에 맞춰서 변경

* test: 추가 테스트 코드 작성

* chore: Apache Commons Lang 의존성 추가

* feat: 방 참여 기능 구현

* test: 방 참여 기능 테스트 작성

* feat: 방 나가기 기능 구현

* chore: test yml JPA 로그 추가

* test: 방 참여, 나가기 일부 테스트 작성

* feat: 방 나가기 구현 마무리

* fix: Morning -> Night 수정

* test: 방 나가기 추가 테스트 코드 작성

* test: 방 나가기 추가 테스트 작성

* feat: 방 ID로 존재 확인 로직 추가

* refactor: 오타 수정

* fix: 테스트 실행 불가 해결

* fix: CI 오류 해결

* refactor: 코드 리뷰 반영
  • Loading branch information
ymkim97 authored Nov 3, 2023
1 parent 8177609 commit b3bd297
Show file tree
Hide file tree
Showing 14 changed files with 641 additions and 67 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ dependencies {

// Configuration Binding
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"

// Apache Commons Lang 3
implementation 'org.apache.commons:commons-lang3:3.13.0'

// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
Expand Down
117 changes: 113 additions & 4 deletions src/main/java/com/moabam/api/application/RoomService.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
package com.moabam.api.application;

import static com.moabam.api.domain.entity.enums.RoomType.*;
import static com.moabam.global.error.model.ErrorMessage.*;

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.domain.entity.Member;
import com.moabam.api.domain.entity.Participant;
import com.moabam.api.domain.entity.Room;
import com.moabam.api.domain.entity.Routine;
import com.moabam.api.domain.entity.enums.RoomType;
import com.moabam.api.domain.repository.ParticipantRepository;
import com.moabam.api.domain.repository.ParticipantSearchRepository;
import com.moabam.api.domain.repository.RoomRepository;
import com.moabam.api.domain.repository.RoutineRepository;
import com.moabam.api.dto.CreateRoomRequest;
import com.moabam.api.dto.EnterRoomRequest;
import com.moabam.api.dto.ModifyRoomRequest;
import com.moabam.api.dto.RoomMapper;
import com.moabam.global.error.exception.BadRequestException;
import com.moabam.global.error.exception.ForbiddenException;
import com.moabam.global.error.exception.NotFoundException;

Expand All @@ -31,6 +37,7 @@ public class RoomService {
private final RoutineRepository routineRepository;
private final ParticipantRepository participantRepository;
private final ParticipantSearchRepository participantSearchRepository;
private final MemberService memberService;

@Transactional
public void createRoom(Long memberId, CreateRoomRequest createRoomRequest) {
Expand All @@ -40,6 +47,7 @@ public void createRoom(Long memberId, CreateRoomRequest createRoomRequest) {
.room(room)
.memberId(memberId)
.build();

participant.enableManager();
roomRepository.save(room);
routineRepository.saveAll(routines);
Expand All @@ -48,18 +56,119 @@ public void createRoom(Long memberId, CreateRoomRequest createRoomRequest) {

@Transactional
public void modifyRoom(Long memberId, Long roomId, ModifyRoomRequest modifyRoomRequest) {
// TODO: 추후에 별도 메서드로 뺄듯
Participant participant = participantSearchRepository.findParticipant(roomId, memberId)
.orElseThrow(() -> new NotFoundException(PARTICIPANT_NOT_FOUND));
Participant participant = getParticipant(memberId, roomId);

if (!participant.isManager()) {
throw new ForbiddenException(ROOM_MODIFY_UNAUTHORIZED_REQUEST);
}

Room room = roomRepository.findById(roomId).orElseThrow(() -> new NotFoundException(ROOM_NOT_FOUND));
Room room = getRoom(roomId);
room.changeTitle(modifyRoomRequest.title());
room.changePassword(modifyRoomRequest.password());
room.changeCertifyTime(modifyRoomRequest.certifyTime());
room.changeMaxCount(modifyRoomRequest.maxUserCount());
}

@Transactional
public void enterRoom(Long memberId, Long roomId, EnterRoomRequest enterRoomRequest) {
Room room = getRoom(roomId);
validateRoomEnter(memberId, enterRoomRequest.password(), room);

room.increaseCurrentUserCount();
increaseRoomCount(memberId, room.getRoomType());

Participant participant = Participant.builder()
.room(room)
.memberId(memberId)
.build();
participantRepository.save(participant);
}

@Transactional
public void exitRoom(Long memberId, Long roomId) {
Participant participant = getParticipant(memberId, roomId);
Room room = participant.getRoom();

if (participant.isManager() && room.getCurrentUserCount() != 1) {
throw new BadRequestException(ROOM_EXIT_MANAGER_FAIL);
}

decreaseRoomCount(memberId, room.getRoomType());
participant.removeRoom();
participantRepository.flush();
participantRepository.delete(participant);

if (!participant.isManager()) {
room.decreaseCurrentUserCount();
return;
}

roomRepository.flush();
roomRepository.delete(room);
}

public void validateRoomById(Long roomId) {
if (!roomRepository.existsById(roomId)) {
throw new NotFoundException(ROOM_NOT_FOUND);
}
}

private Participant getParticipant(Long memberId, Long roomId) {
return participantSearchRepository.findParticipant(memberId, roomId)
.orElseThrow(() -> new NotFoundException(PARTICIPANT_NOT_FOUND));
}

private Room getRoom(Long roomId) {
return roomRepository.findById(roomId).orElseThrow(() -> new NotFoundException(ROOM_NOT_FOUND));
}

private void validateRoomEnter(Long memberId, String requestPassword, Room room) {
if (!isEnterRoomAvailable(memberId, room.getRoomType())) {
throw new BadRequestException(MEMBER_ROOM_EXCEED);
}

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 boolean isEnterRoomAvailable(Long memberId, RoomType roomType) {
Member member = memberService.getById(memberId);

if (roomType.equals(MORNING) && member.getCurrentMorningCount() >= 3) {
return false;
}

if (roomType.equals(NIGHT) && member.getCurrentNightCount() >= 3) {
return false;
}

return true;
}

private void increaseRoomCount(Long memberId, RoomType roomType) {
Member member = memberService.getById(memberId);

if (roomType.equals(MORNING)) {
member.enterMorningRoom();
return;
}

member.enterNightRoom();
}

private void decreaseRoomCount(Long memberId, RoomType roomType) {
Member member = memberService.getById(memberId);

if (roomType.equals(MORNING)) {
member.exitMorningRoom();
return;
}

member.exitNightRoom();
}
}
4 changes: 2 additions & 2 deletions src/main/java/com/moabam/api/domain/entity/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
@Getter
@Table(name = "member")
@SQLDelete(sql = "UPDATE member SET deleted_at = CURRENT_TIMESTAMP where id = ?")
@Where(clause = "deleted_at IS NOT NULL")
@Where(clause = "deleted_at IS NULL")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseTimeEntity {

Expand Down Expand Up @@ -103,7 +103,7 @@ public void exitMorningRoom() {
}

public void exitNightRoom() {
if (currentMorningCount > 0) {
if (currentNightCount > 0) {
currentNightCount--;
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/com/moabam/api/domain/entity/Participant.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class Participant {
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "room_id", updatable = false, nullable = false)
@JoinColumn(name = "room_id", updatable = false)
private Room room;

@Column(name = "member_id", updatable = false, nullable = false)
Expand All @@ -48,6 +48,9 @@ public class Participant {
@Column(name = "deleted_at")
private LocalDateTime deletedAt;

@Column(name = "deleted_room_title", length = 30)
private String deletedRoomTitle;

@Builder
private Participant(Long id, Room room, Long memberId) {
this.id = id;
Expand All @@ -68,4 +71,9 @@ public void enableManager() {
public void updateCertifyCount() {
this.certifyCount += 1;
}

public void removeRoom() {
this.deletedRoomTitle = this.room.getTitle();
this.room = null;
}
}
14 changes: 12 additions & 2 deletions src/main/java/com/moabam/api/domain/entity/Room.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ public class Room extends BaseTimeEntity {
@Column(name = "id")
private Long id;

// TODO: 한글 10자도 맞나?
@Column(name = "title", nullable = false, length = 30)
private String title;

Expand All @@ -66,7 +65,6 @@ public class Room extends BaseTimeEntity {
@Column(name = "max_user_count", nullable = false)
private int maxUserCount;

// TODO: 한글 길이 고려
@Column(name = "announcement", length = 255)
private String announcement;

Expand Down Expand Up @@ -111,6 +109,18 @@ public void changeMaxCount(int maxUserCount) {
this.maxUserCount = maxUserCount;
}

public void increaseCurrentUserCount() {
this.currentUserCount += 1;

if (this.currentUserCount > this.maxUserCount) {
throw new BadRequestException(ROOM_MAX_USER_REACHED);
}
}

public void decreaseCurrentUserCount() {
this.currentUserCount -= 1;
}

public void upgradeRoomImage(String roomImage) {
this.roomImage = roomImage;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class ParticipantSearchRepository {

private final JPAQueryFactory jpaQueryFactory;

public Optional<Participant> findParticipant(Long roomId, Long memberId) {
public Optional<Participant> findParticipant(Long memberId, Long roomId) {
return Optional.ofNullable(
jpaQueryFactory.selectFrom(participant)
.where(
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/moabam/api/dto/EnterRoomRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.moabam.api.dto;

import jakarta.validation.constraints.Pattern;

public record EnterRoomRequest(
@Pattern(regexp = "^(|[0-9]{4,8})$") String password
) {

}
14 changes: 14 additions & 0 deletions src/main/java/com/moabam/api/presentation/RoomController.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.moabam.api.presentation;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
Expand All @@ -11,6 +12,7 @@

import com.moabam.api.application.RoomService;
import com.moabam.api.dto.CreateRoomRequest;
import com.moabam.api.dto.EnterRoomRequest;
import com.moabam.api.dto.ModifyRoomRequest;

import jakarta.validation.Valid;
Expand All @@ -35,4 +37,16 @@ public void modifyRoom(@Valid @RequestBody ModifyRoomRequest modifyRoomRequest,
@PathVariable("roomId") Long roomId) {
roomService.modifyRoom(1L, roomId, modifyRoomRequest);
}

@PostMapping("/{roomId}")
@ResponseStatus(HttpStatus.OK)
public void enterRoom(@Valid @RequestBody EnterRoomRequest enterRoomRequest, @PathVariable("roomId") Long roomId) {
roomService.enterRoom(1L, roomId, enterRoomRequest);
}

@DeleteMapping("/{roomId}")
@ResponseStatus(HttpStatus.OK)
public void exitRoom(@PathVariable("roomId") Long roomId) {
roomService.exitRoom(1L, roomId);
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/moabam/global/error/model/ErrorMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ public enum ErrorMessage {
ROOM_NOT_FOUND("존재하지 않는 방 입니다."),
ROOM_MAX_USER_COUNT_MODIFY_FAIL("잘못된 최대 인원수 설정입니다."),
ROOM_MODIFY_UNAUTHORIZED_REQUEST("방장이 아닌 사용자는 방을 수정할 수 없습니다."),
ROOM_EXIT_MANAGER_FAIL("인원수가 2명 이상일 때는 방장을 위임해야 합니다."),
PARTICIPANT_NOT_FOUND("방에 대한 참여자의 정보가 없습니다."),
WRONG_ROOM_PASSWORD("방의 비밀번호가 일치하지 않습니다."),
ROOM_MAX_USER_REACHED("방의 인원수가 찼습니다."),

LOGIN_FAILED("로그인에 실패했습니다."),
REQUEST_FAILED("네트워크 접근 실패입니다."),
GRANT_FAILED("인가 코드 실패"),
MEMBER_NOT_FOUND("존재하지 않는 회원입니다."),
MEMBER_ROOM_EXCEED("참여할 수 있는 방의 개수가 모두 찼습니다."),

INVALID_BUG_COUNT("벌레 개수는 0 이상이어야 합니다."),
INVALID_PRICE("가격은 0 이상이어야 합니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;

import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -24,7 +23,6 @@

@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class BugControllerTest {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
package com.moabam.api.presentation;

import static com.moabam.global.common.util.OAuthParameterNames.CLIENT_ID;
import static com.moabam.global.common.util.OAuthParameterNames.CLIENT_SECRET;
import static com.moabam.global.common.util.OAuthParameterNames.CODE;
import static com.moabam.global.common.util.OAuthParameterNames.GRANT_TYPE;
import static com.moabam.global.common.util.OAuthParameterNames.REDIRECT_URI;
import static com.moabam.global.common.util.OAuthParameterNames.RESPONSE_TYPE;
import static com.moabam.global.common.util.OAuthParameterNames.SCOPE;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static com.moabam.global.common.util.OAuthParameterNames.*;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.*;
import static org.springframework.test.web.client.response.MockRestResponseCreators.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
Expand All @@ -23,7 +15,6 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.client.match.MockRestRequestMatchers;
Expand All @@ -44,7 +35,6 @@

@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class MemberControllerTest {

@Autowired
Expand Down
Loading

0 comments on commit b3bd297

Please sign in to comment.