Skip to content

Commit

Permalink
[BE] 방 수정 기능 & 스케줄러 부분 분리 및 리팩토링(#605) (#606)
Browse files Browse the repository at this point in the history
* feat: repository 의존성 제거, 테스트 명확하게 변경

* feat: Reader/Writer 통해 조회 로직 분리

* feat: 룸 수정 기능 구현

* refactor: 피드백 반영

* fix: 충돌 해결

---------

Co-authored-by: youngsu5582 <[email protected]>
  • Loading branch information
github-actions[bot] and youngsu5582 authored Oct 16, 2024
1 parent 6cd7484 commit ab2235b
Show file tree
Hide file tree
Showing 20 changed files with 832 additions and 414 deletions.
1 change: 1 addition & 0 deletions backend/src/main/java/corea/exception/ExceptionType.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public enum ExceptionType {
ALREADY_APPLY(HttpStatus.BAD_REQUEST, "해당 방에 이미 참여했습니다."),
NOT_ALREADY_APPLY(HttpStatus.BAD_REQUEST, "아직 참여하지 않은 방입니다."),
ROOM_STATUS_INVALID(HttpStatus.BAD_REQUEST, "방이 마감되었습니다."),
MEMBER_IS_NOT_MANAGER(HttpStatus.BAD_REQUEST, "매니저가 아닙니다."),
ROOM_PARTICIPANT_EXCEED(HttpStatus.BAD_REQUEST, "방 참여 인원 수가 최대입니다."),
PARTICIPANT_SIZE_LACK(HttpStatus.BAD_REQUEST, "참여 인원이 부족하여 매칭을 진행할 수 없습니다."),
PARTICIPANT_SIZE_LACK_DUE_TO_PULL_REQUEST(HttpStatus.BAD_REQUEST, "pull request 미제출로 인해 인원이 부족하여 매칭을 진행할 수 없습니다."),
Expand Down
23 changes: 9 additions & 14 deletions backend/src/main/java/corea/room/controller/RoomController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,8 @@
import corea.auth.annotation.AccessedMember;
import corea.auth.annotation.LoginMember;
import corea.auth.domain.AuthInfo;
import corea.room.dto.RoomCreateRequest;
import corea.room.dto.RoomParticipantResponses;
import corea.room.dto.RoomResponse;
import corea.room.dto.RoomResponses;
import corea.room.dto.*;
import corea.room.service.RoomService;
import corea.scheduler.service.AutomaticMatchingService;
import corea.scheduler.service.AutomaticUpdateService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
Expand All @@ -22,20 +17,23 @@
public class RoomController implements RoomControllerSpecification {

private final RoomService roomService;
private final AutomaticUpdateService automaticUpdateService;
private final AutomaticMatchingService automaticMatchingService;

@PostMapping
public ResponseEntity<RoomResponse> create(@LoginMember AuthInfo authInfo, @RequestBody RoomCreateRequest request) {
RoomResponse response = roomService.create(authInfo.getId(), request);

automaticMatchingService.matchOnRecruitmentDeadline(response);
automaticUpdateService.updateAtReviewDeadline(response);

return ResponseEntity.created(URI.create(String.format("/rooms/%d", response.id())))
.body(response);
}

@PutMapping
public ResponseEntity<RoomResponse> update(@LoginMember AuthInfo authInfo, @RequestBody RoomUpdateRequest request) {
RoomResponse response = roomService.update(authInfo.getId(), request);

return ResponseEntity.ok()
.body(response);
}

@GetMapping("/{id}")
public ResponseEntity<RoomResponse> room(@PathVariable long id, @AccessedMember AuthInfo authInfo) {
RoomResponse response = roomService.findOne(id, authInfo.getId());
Expand All @@ -58,9 +56,6 @@ public ResponseEntity<RoomResponses> participatedRooms(@LoginMember AuthInfo aut
public ResponseEntity<Void> delete(@PathVariable long id, @LoginMember AuthInfo authInfo) {
roomService.delete(id, authInfo.getId());

automaticMatchingService.cancel(id);
automaticUpdateService.cancel(id);

return ResponseEntity.noContent()
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
import corea.exception.ExceptionType;
import corea.global.annotation.ApiErrorResponses;
import corea.matchresult.dto.MatchResultResponses;
import corea.room.dto.RoomCreateRequest;
import corea.room.dto.RoomParticipantResponses;
import corea.room.dto.RoomResponse;
import corea.room.dto.RoomResponses;
import corea.room.dto.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -26,6 +23,16 @@ public interface RoomControllerSpecification {
@ApiErrorResponses(value = ExceptionType.MEMBER_NOT_FOUND)
ResponseEntity<RoomResponse> create(AuthInfo authInfo, RoomCreateRequest request);

@Operation(summary = "새로운 방을 수정합니다.",
description = "상호 리뷰 인원을 모을 수 있는 방을 수정합니다. <br>" +
"요청 시 `Authorization Header`에 `Bearer JWT token`을 포함시켜야 합니다. " +
"이 토큰을 기반으로 `AuthInfo` 객체가 생성되며 사용자의 정보가 자동으로 주입됩니다. <br>" +
"JWT 토큰에서 추출된 사용자 정보는 피드백 작성에 필요한 인증된 사용자 정보를 제공합니다. " +
"<br><br>**참고:** 이 API를 사용하기 위해서는 유효한 JWT 토큰이 필요하며, " +
"토큰이 없거나 유효하지 않은 경우 인증 오류가 발생합니다.")
@ApiErrorResponses(value = ExceptionType.MEMBER_NOT_FOUND)
ResponseEntity<RoomResponse> update(AuthInfo authInfo, RoomUpdateRequest request);

@Operation(summary = "방 상세 정보를 반환합니다.",
description = "상세 페이지에 디스플레이 되는 방 상세 정보를 반환합니다. <br>" +
"요청 시 `Authorization Header`에 `Bearer JWT token`을 포함시켜야 합니다. " +
Expand Down
76 changes: 76 additions & 0 deletions backend/src/main/java/corea/room/dto/RoomUpdateRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package corea.room.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import corea.member.domain.Member;
import corea.room.domain.Room;
import corea.room.domain.RoomClassification;
import corea.room.domain.RoomStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDateTime;
import java.util.List;

@Schema(description = "방 수정 요청")
public record RoomUpdateRequest(
@Schema(description = "방 ID", example = "99")
@NotBlank
long roomId,

@Schema(description = "방 제목", example = "MVC를 아시나요?")
@NotBlank
String title,

@Schema(description = "방 내용", example = "MVC 패턴을 아시나요?")
String content,

@Schema(description = "repository 링크", example = "https://github.com/example/java-racingcar")
@NotBlank
String repositoryLink,

@Schema(description = "썸네일 링크", example = "https://gongu.copyright.or.kr/gongu/wrt/cmmn/wrtFileImageView.do?wrtSn=13301655&filePath=L2Rpc2sxL25ld2RhdGEvMjAyMS8yMS9DTFMxMDAwNC8xMzMwMTY1NV9XUlRfMjFfQ0xTMTAwMDRfMjAyMTEyMTNfMQ==&thumbAt=Y&thumbSe=b_tbumb&wrtTy=10004")
String thumbnailLink,

@Schema(description = "상호 리뷰 인원", example = "2")
@NotNull
int matchingSize,

@Schema(description = "중심으로 리뷰하면 좋은 키워드", example = "[\"TDD\", \"클린코드\"]")
List<String> keywords,

@Schema(description = "제한 참여 인원", example = "200")
@NotNull
int limitedParticipants,

@Schema(description = "모집 마감일", example = "2024-07-30 15:00")
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
LocalDateTime recruitmentDeadline,

@Schema(description = "리뷰 마감일", example = "2024-08-10 23:59")
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
LocalDateTime reviewDeadline,

@Schema(description = "방이 속하는 분야", example = "BE")
@NotNull
RoomClassification classification
) {

private static final int INITIAL_PARTICIPANTS_SIZE = 1;
private static final RoomStatus INITIAL_ROOM_STATUS = RoomStatus.OPEN;

public Room toEntity(Member manager) {
return new Room(
roomId,
title, content,
matchingSize, repositoryLink,
thumbnailLink, keywords,
INITIAL_PARTICIPANTS_SIZE, limitedParticipants,
manager, recruitmentDeadline,
reviewDeadline, classification,
INITIAL_ROOM_STATUS
);
}
}
84 changes: 84 additions & 0 deletions backend/src/main/java/corea/room/service/RoomAutomaticService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package corea.room.service;

import corea.room.domain.Room;
import corea.scheduler.domain.*;
import corea.scheduler.service.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class RoomAutomaticService {

private final AutomaticUpdateWriter automaticUpdateWriter;
private final AutomaticUpdateReader automaticUpdateReader;

private final AutomaticMatchingWriter automaticMatchingWriter;
private final AutomaticMatchingReader automaticMatchingReader;

private final AutomaticMatchingScheduler automaticMatchingScheduler;
private final AutomaticUpdateScheduler automaticUpdateScheduler;

@Transactional
public void updateTime(Room updateRoom) {
AutomaticMatching automaticMatching = automaticMatchingReader.findWithRoom(updateRoom);
AutomaticUpdate automaticUpdate = automaticUpdateReader.findWithRoom(updateRoom);

automaticMatchingWriter.updateTime(automaticMatching, updateRoom.getRecruitmentDeadline());
automaticUpdateWriter.updateTime(automaticUpdate, updateRoom.getReviewDeadline());

automaticMatchingScheduler.modifyTask(updateRoom);
automaticUpdateScheduler.modifyTask(updateRoom);
}

@Transactional
public void createAutomatic(Room room) {
automaticMatchingWriter.create(room);
automaticUpdateWriter.create(room);

automaticMatchingScheduler.matchOnRecruitmentDeadline(room);
automaticUpdateScheduler.updateAtReviewDeadline(room);
}

@Transactional
public void deleteAutomatic(Room room) {
AutomaticMatching automaticMatching = automaticMatchingReader.findWithRoom(room);
AutomaticUpdate automaticUpdate = automaticUpdateReader.findWithRoom(room);

automaticMatchingWriter.delete(automaticMatching);
automaticUpdateWriter.delete(automaticUpdate);

automaticMatchingScheduler.cancel(room.getId());
automaticUpdateScheduler.cancel(room.getId());
}

@EventListener(ApplicationReadyEvent.class)
public void schedulePendingAutomaticMatching() {
List<AutomaticMatching> matchings = automaticMatchingReader.findAllByStatus(ScheduleStatus.PENDING);

log.info("{}개의 방에 대해 자동 매칭 재예약 시작", matchings.size());

matchings.forEach(automaticMatchingScheduler::matchOnRecruitmentDeadline);

log.info("{}개의 방에 대해 자동 매칭 재예약 완료", matchings.size());
}

@EventListener(ApplicationReadyEvent.class)
public void schedulePendingAutomaticUpdate() {
List<AutomaticUpdate> updates = automaticUpdateReader.findAllByStatus(ScheduleStatus.PENDING);

log.info("{}개의 방에 대해 자동 상태 업데이트 재예약 시작", updates.size());

updates.forEach(automaticUpdateScheduler::updateAtReviewDeadline);

log.info("{}개의 방에 대해 자동 상태 업데이트 재예약 완료", updates.size());
}
}
35 changes: 21 additions & 14 deletions backend/src/main/java/corea/room/service/RoomService.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,10 @@
import corea.participation.domain.ParticipationStatus;
import corea.participation.repository.ParticipationRepository;
import corea.room.domain.Room;
import corea.room.domain.RoomClassification;
import corea.room.domain.RoomStatus;
import corea.room.dto.*;
import corea.room.repository.RoomRepository;
import corea.scheduler.domain.AutomaticMatching;
import corea.scheduler.domain.AutomaticUpdate;
import corea.scheduler.repository.AutomaticMatchingRepository;
import corea.scheduler.repository.AutomaticUpdateRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -46,8 +38,8 @@ public class RoomService {
private final MatchResultRepository matchResultRepository;
private final ParticipationRepository participationRepository;
private final FailedMatchingRepository failedMatchingRepository;
private final AutomaticMatchingRepository automaticMatchingRepository;
private final AutomaticUpdateRepository automaticUpdateRepository;
private final RoomAutomaticService roomAutomaticService;


@Transactional
public RoomResponse create(long memberId, RoomCreateRequest request) {
Expand All @@ -59,12 +51,28 @@ public RoomResponse create(long memberId, RoomCreateRequest request) {
Participation participation = new Participation(room, manager);

participationRepository.save(participation);
automaticMatchingRepository.save(new AutomaticMatching(room.getId(), request.recruitmentDeadline()));
automaticUpdateRepository.save(new AutomaticUpdate(room.getId(), request.reviewDeadline()));
roomAutomaticService.createAutomatic(room);

return RoomResponse.of(room, participation.getMemberRole(), ParticipationStatus.MANAGER);
}

@Transactional
public RoomResponse update(long memberId, RoomUpdateRequest request) {
Room room = getRoom(request.roomId());
if (room.isNotMatchingManager(memberId)) {
throw new CoreaException(ExceptionType.MEMBER_IS_NOT_MANAGER);
}
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new CoreaException(ExceptionType.MEMBER_NOT_FOUND));

Room updatedRoom = roomRepository.save(request.toEntity(member));
Participation participation = participationRepository.findByRoomIdAndMemberId(updatedRoom.getId(), memberId)
.orElseThrow(() -> new CoreaException(ExceptionType.NOT_ALREADY_APPLY));

roomAutomaticService.updateTime(updatedRoom);
return RoomResponse.of(updatedRoom, participation.getMemberRole(), ParticipationStatus.MANAGER);
}

private void validateDeadLine(LocalDateTime recruitmentDeadline, LocalDateTime reviewDeadline) {
LocalDateTime currentDateTime = LocalDateTime.now();

Expand Down Expand Up @@ -114,8 +122,7 @@ public void delete(long roomId, long memberId) {

roomRepository.delete(room);
participationRepository.deleteAllByRoomId(roomId);
automaticMatchingRepository.deleteByRoomId(roomId);
automaticUpdateRepository.deleteByRoomId(roomId);
roomAutomaticService.deleteAutomatic(room);
}

private void validateDeletionAuthority(Room room, long memberId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package corea.scheduler.domain;

import corea.exception.CoreaException;
import corea.exception.ExceptionType;
import corea.room.domain.Room;
import corea.scheduler.repository.AutomaticMatchingRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Component
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AutomaticMatchingReader {

private final AutomaticMatchingRepository automaticMatchingRepository;

public AutomaticMatching findWithRoom(Room room) {
return automaticMatchingRepository.findByRoomId(room.getId())
.orElseThrow(() -> new CoreaException(ExceptionType.AUTOMATIC_MATCHING_NOT_FOUND));
}

public List<AutomaticMatching> findAllByStatus(ScheduleStatus status) {
return automaticMatchingRepository.findAllByStatus(status);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package corea.scheduler.domain;

import corea.room.domain.Room;
import corea.scheduler.repository.AutomaticMatchingRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

@Component
@RequiredArgsConstructor
@Transactional
public class AutomaticMatchingWriter {

private final AutomaticMatchingRepository automaticMatchingRepository;

public AutomaticMatching updateTime(AutomaticMatching automaticMatching, LocalDateTime matchingStartTime) {
AutomaticMatching updateEntity = new AutomaticMatching(
automaticMatching.getId(),
automaticMatching.getRoomId(),
matchingStartTime,
automaticMatching.getStatus()
);
return automaticMatchingRepository.save(updateEntity);
}

public AutomaticMatching create(Room room) {
AutomaticMatching entity = new AutomaticMatching(room.getId(),room.getRecruitmentDeadline());
return automaticMatchingRepository.save(entity);
}

public void delete(AutomaticMatching automaticMatching) {
automaticMatchingRepository.delete(automaticMatching);
}
}
Loading

0 comments on commit ab2235b

Please sign in to comment.