Skip to content

Commit

Permalink
[feat/CK-240] 골룸 관련 부분 의존성 리팩토링을 한다 (#207)
Browse files Browse the repository at this point in the history
* refactor: GoalRoom 관련 패키지 의존성 분리

* refactor: 리뷰 반영 및 골룸 생성 로직 변경

* feat: GoalRoom과 GoalRoomMember, GoalRoomPendingMember의 양방향 의존성 제거

* feat: 골룸 생성 시 리더 업데이트하는 로직 이벤트 분리

* feat: 골룸 나갈 때 빈 골룸을 삭제하는 로직 이벤트 분리

* feat: 인증피드 등록 시 달성률 업데이트 로직 이벤트 분리

* refactor: 요구 사항 반영 (DashBoardCheckFeedService)

* refactor: CheckFeed 생성 시 달성률 업데이트 이벤트 네이밍 변경

* refactor: 골룸 생성, 나가기 이벤트 네이밍 변경

* feat: 골룸 참여 시, 골룸 펜딩 멤버 테이블에 락이 걸리도록 수정

* refactor: 골룸 시작, 나가기 쿼리 수정
  • Loading branch information
miseongk authored May 18, 2024
1 parent 9152846 commit 837aaf6
Show file tree
Hide file tree
Showing 220 changed files with 8,049 additions and 6,342 deletions.
14 changes: 7 additions & 7 deletions backend/kirikiri/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ jacocoTestReport {
classDirectories.setFrom(
files(classDirectories.files.collect {
fileTree(dir: it, excludes: [
"co/kirikiri/persistence/QuerydslRepositorySupporter",
"co/kirikiri/domain/**",
"co/kirikiri/common/persistence/QuerydslRepositorySupporter",
"co/kirikiri/common/service/dto/**",
"co/kirikiri/**/domain/**",
"co/kirikiri/persistence/goalroom/dto/**",
"co/kirikiri/**/persistence/dto/**",
"**/*Application*",
"**/*Config*",
"**/*Dto*",
Expand Down Expand Up @@ -113,12 +113,12 @@ jacocoTestCoverageVerification {
value = 'COVEREDRATIO'
minimum = 0.80
}

excludes = [
"co.kirikiri.persistence.QuerydslRepositorySupporter",
"co.kirikiri.domain.**",
"co.kirikiri.common.persistence.QuerydslRepositorySupporter",
"co.kirikiri.common.service.dto.**",
"co.kirikiri.**.domain.**",
"co.kirikiri.persistence.goalroom.dto.**",
"co.kirikiri.**.persistence.dto.**",
"**.*Application*",
"**.*Config*",
"**.*Dto*",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package co.kirikiri.checkfeed.controller;

import co.kirikiri.checkfeed.service.GoalRoomCheckFeedService;
import co.kirikiri.checkfeed.service.dto.request.CheckFeedRequest;
import co.kirikiri.checkfeed.service.dto.response.GoalRoomCheckFeedResponse;
import co.kirikiri.common.interceptor.Authenticated;
import co.kirikiri.common.resolver.MemberIdentifier;
import java.net.URI;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/goal-rooms/{goalRoomId}/checkFeeds")
@RequiredArgsConstructor
public class GoalRoomCheckFeedController {

private final GoalRoomCheckFeedService goalRoomCheckFeedService;

@PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
@Authenticated
public ResponseEntity<Void> createCheckFeed(@MemberIdentifier final String identifier,
@PathVariable("goalRoomId") final Long goalRoomId,
@ModelAttribute final CheckFeedRequest checkFeedRequest) {
final String imageUrl = goalRoomCheckFeedService.createCheckFeed(identifier, goalRoomId, checkFeedRequest);
return ResponseEntity.created(URI.create(imageUrl)).build();
}

@GetMapping
@Authenticated
public ResponseEntity<List<GoalRoomCheckFeedResponse>> findGoalRoomCheckFeeds(
@MemberIdentifier final String identifier,
@PathVariable("goalRoomId") final Long goalRoomId) {
final List<GoalRoomCheckFeedResponse> response = goalRoomCheckFeedService.findGoalRoomCheckFeeds(identifier,
goalRoomId);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
package co.kirikiri.domain.goalroom;
package co.kirikiri.checkfeed.domain;

import co.kirikiri.common.entity.BaseCreatedTimeEntity;
import co.kirikiri.common.type.ImageContentType;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Expand All @@ -30,31 +26,26 @@ public class CheckFeed extends BaseCreatedTimeEntity {

private String description;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "goal_room_roadmap_node_id", nullable = false)
private GoalRoomRoadmapNode goalRoomRoadmapNode;
private Long goalRoomRoadmapNodeId;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinColumn(name = "goal_room_member_id", nullable = false)
private GoalRoomMember goalRoomMember;
private Long goalRoomMemberId;

public CheckFeed(final String serverFilePath, final ImageContentType imageContentType,
final String originalFileName, final String description,
final GoalRoomRoadmapNode goalRoomRoadmapNode, final GoalRoomMember goalRoomMember) {
this(serverFilePath, imageContentType, originalFileName, description, goalRoomRoadmapNode, goalRoomMember,
final Long goalRoomRoadmapNodeId, final Long goalRoomMemberId) {
this(serverFilePath, imageContentType, originalFileName, description, goalRoomRoadmapNodeId, goalRoomMemberId,
null);
}

public CheckFeed(final String serverFilePath, final ImageContentType imageContentType,
final String originalFileName, final String description,
final GoalRoomRoadmapNode goalRoomRoadmapNode, final GoalRoomMember goalRoomMember, final
LocalDateTime createdAt) {
final Long goalRoomRoadmapNodeId, final Long goalRoomMemberId, final LocalDateTime createdAt) {
this.serverFilePath = serverFilePath;
this.imageContentType = imageContentType;
this.originalFileName = originalFileName;
this.description = description;
this.goalRoomRoadmapNode = goalRoomRoadmapNode;
this.goalRoomMember = goalRoomMember;
this.goalRoomRoadmapNodeId = goalRoomRoadmapNodeId;
this.goalRoomMemberId = goalRoomMemberId;
this.createdAt = createdAt;
}

Expand All @@ -66,7 +57,7 @@ public String getDescription() {
return description;
}

public GoalRoomMember getGoalRoomMember() {
return goalRoomMember;
public Long getGoalRoomMemberId() {
return goalRoomMemberId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package co.kirikiri.checkfeed.persistence;

import co.kirikiri.checkfeed.domain.CheckFeed;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface CheckFeedRepository extends JpaRepository<CheckFeed, Long> {

@Query("SELECT cf"
+ " FROM CheckFeed cf"
+ " WHERE cf.goalRoomMemberId = :goalRoomMemberId"
+ " AND cf.createdAt >= :start"
+ " AND cf.createdAt < :end")
Optional<CheckFeed> findByGoalRoomMemberIdAndDateTime(final Long goalRoomMemberId, final LocalDateTime start,
final LocalDateTime end);

@Query("SELECT COUNT(cf)"
+ " FROM CheckFeed cf"
+ " WHERE cf.goalRoomMemberId = :goalRoomMemberId"
+ " AND cf.goalRoomRoadmapNodeId = :goalRoomRoadmapNodeId")
int countByGoalRoomMemberIdAndGoalRoomRoadmapNodeId(final Long goalRoomMemberId, final Long goalRoomRoadmapNodeId);

@Query(value = "SELECT cf.* FROM check_feed as cf "
+ "LEFT JOIN goal_room_member as gm ON cf.goal_room_member_id = gm.id "
+ "JOIN goal_room as g ON gm.goal_room_id = g.id "
+ "WHERE g.id = :goalRoomId "
+ "ORDER BY cf.created_at DESC ", nativeQuery = true)
List<CheckFeed> findByGoalRoomIdOrderByCreatedAtDesc(@Param("goalRoomId") final Long goalRoomId);

List<CheckFeed> findByGoalRoomRoadmapNodeIdOrderByCreatedAtDesc(final Long goalRoomRoadmapNodeId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package co.kirikiri.checkfeed.service;

import co.kirikiri.checkfeed.domain.CheckFeed;
import co.kirikiri.checkfeed.persistence.CheckFeedRepository;
import co.kirikiri.checkfeed.service.event.CheckFeedCreateEvent;
import co.kirikiri.common.aop.ExceptionConvert;
import co.kirikiri.common.exception.BadRequestException;
import co.kirikiri.common.exception.NotFoundException;
import co.kirikiri.goalroom.domain.GoalRoom;
import co.kirikiri.goalroom.domain.GoalRoomMember;
import co.kirikiri.goalroom.domain.GoalRoomRoadmapNode;
import co.kirikiri.goalroom.persistence.GoalRoomMemberRepository;
import co.kirikiri.goalroom.persistence.GoalRoomRepository;
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

@Service
@RequiredArgsConstructor
@ExceptionConvert
public class CheckFeedCreateEventListener {

private final CheckFeedRepository checkFeedRepository;
private final GoalRoomRepository goalRoomRepository;
private final GoalRoomMemberRepository goalRoomMemberRepository;

@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
@Transactional
public void handleUpdateAccomplishmentRate(final CheckFeedCreateEvent checkFeedCreateEvent) {
final CheckFeed checkFeed = findCheckFeedById(checkFeedCreateEvent.checkFeedId());
final GoalRoom goalRoom = findGoalRoomById(checkFeedCreateEvent.goalRoomId());
final GoalRoomMember goalRoomMember = findGoalRoomMemberById(checkFeed.getGoalRoomMemberId());
final GoalRoomRoadmapNode currentNode = getNodeByDate(goalRoom);
final int currentCheckCount = getCurrentCheckCount(goalRoomMember, currentNode);

updateAccomplishmentRate(goalRoom, goalRoomMember, currentCheckCount);
}

private CheckFeed findCheckFeedById(final Long checkFeedId) {
return checkFeedRepository.findById(checkFeedId)
.orElseThrow(() -> new NotFoundException("존재하지 않는 인증피드입니다."));
}

private GoalRoom findGoalRoomById(final Long goalRoomId) {
return goalRoomRepository.findById(goalRoomId)
.orElseThrow(() -> new NotFoundException("존재하지 않는 골룸입니다."));
}

private GoalRoomMember findGoalRoomMemberById(final Long goalRoomMemberId) {
return goalRoomMemberRepository.findById(goalRoomMemberId)
.orElseThrow(() -> new NotFoundException("존재하지 않는 골룸 멤버입니다."));
}

private GoalRoomRoadmapNode getNodeByDate(final GoalRoom goalRoom) {
return goalRoom.findNodeByDate(LocalDate.now())
.orElseThrow(() -> new BadRequestException("인증 피드는 노드 기간 내에만 작성할 수 있습니다."));
}

private int getCurrentCheckCount(final GoalRoomMember goalRoomMember, final GoalRoomRoadmapNode currentNode) {
return checkFeedRepository.countByGoalRoomMemberIdAndGoalRoomRoadmapNodeId(
goalRoomMember.getId(), currentNode.getId());
}

private void updateAccomplishmentRate(final GoalRoom goalRoom, final GoalRoomMember goalRoomMember,
final int currentCheckCount) {
final int wholeCheckCount = goalRoom.getAllCheckCount();
final Double accomplishmentRate = 100 * currentCheckCount / (double) wholeCheckCount;

goalRoomMember.updateAccomplishmentRate(accomplishmentRate);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package co.kirikiri.checkfeed.service;

import co.kirikiri.checkfeed.domain.CheckFeed;
import co.kirikiri.checkfeed.persistence.CheckFeedRepository;
import co.kirikiri.common.aop.ExceptionConvert;
import co.kirikiri.common.service.FileService;
import co.kirikiri.goalroom.domain.GoalRoom;
import co.kirikiri.goalroom.domain.GoalRoomRoadmapNode;
import co.kirikiri.goalroom.service.DashBoardCheckFeedService;
import co.kirikiri.goalroom.service.dto.response.DashBoardCheckFeedResponse;
import java.net.URL;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
@ExceptionConvert
public class DashBoardCheckFeedServiceImpl implements DashBoardCheckFeedService {

private final CheckFeedRepository checkFeedRepository;
private final FileService fileService;

@Override
@Transactional(readOnly = true)
public List<DashBoardCheckFeedResponse> findCheckFeedsByNodeAndGoalRoomStatus(final GoalRoom goalRoom) {
final Optional<GoalRoomRoadmapNode> currentGoalRoomRoadmapNode = findCurrentGoalRoomNode(goalRoom);
final List<CheckFeed> checkFeeds = findCheckFeeds(goalRoom, currentGoalRoomRoadmapNode);
return makeCheckFeedResponses(checkFeeds);
}

private Optional<GoalRoomRoadmapNode> findCurrentGoalRoomNode(final GoalRoom goalRoom) {
return goalRoom.findNodeByDate(LocalDate.now());
}

private List<CheckFeed> findCheckFeeds(final GoalRoom goalRoom,
final Optional<GoalRoomRoadmapNode> currentGoalRoomRoadmapNode) {
if (goalRoom.isCompleted()) {
return checkFeedRepository.findByGoalRoomIdOrderByCreatedAtDesc(goalRoom.getId());
}
if (goalRoom.isRunning() && currentGoalRoomRoadmapNode.isPresent()) {
return checkFeedRepository.findByGoalRoomRoadmapNodeIdOrderByCreatedAtDesc(
currentGoalRoomRoadmapNode.get().getId());
}
return Collections.emptyList();
}

private List<DashBoardCheckFeedResponse> makeCheckFeedResponses(final List<CheckFeed> checkFeeds) {
return checkFeeds.stream()
.map(this::makeCheckFeedResponse)
.toList();
}

private DashBoardCheckFeedResponse makeCheckFeedResponse(final CheckFeed checkFeed) {
final URL checkFeedImageUrl = fileService.generateUrl(checkFeed.getServerFilePath(), HttpMethod.GET);
return new DashBoardCheckFeedResponse(checkFeed.getId(), checkFeedImageUrl.toExternalForm(),
checkFeed.getDescription(), checkFeed.getCreatedAt().toLocalDate());
}
}
Loading

0 comments on commit 837aaf6

Please sign in to comment.