Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat/CK-240] 골룸 관련 부분 의존성 리팩토링을 한다 #207

Merged
merged 12 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.domain.BaseCreatedTimeEntity;
import co.kirikiri.domain.ImageContentType;
import jakarta.persistence.CascadeType;
import co.kirikiri.common.entity.BaseCreatedTimeEntity;
import co.kirikiri.common.type.ImageContentType;
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);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이벤트랑 이벤트 리스너 자체에 무엇을 할지 들어나있는거 같아요! 이벤트는 단순히 인증피드 등록을 한다는 것을 발행하고 그것을 Listen 하는 쪽에서 구체적인 행위를 하면 좋을 것 같네요.

https://techblog.woowahan.com/7835/

위 링크의 무엇을 이벤트로 발행할 것인가? 부분 읽어보시면 좋을 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 그렇군요!
이벤트 자체에 의도를 담으면 개념적인 결합도는 아직 높은 상태인 것이군요!
덕분에 아주 중요한 것을 깨달았어요 👍👍
이벤트에 의도가 드러나지 않게 모두 수정하겠습니다!

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package co.kirikiri.checkfeed.service;

import co.kirikiri.checkfeed.service.event.AccomplishmentRateUpdateEvent;
import co.kirikiri.common.aop.ExceptionConvert;
import co.kirikiri.common.exception.NotFoundException;
import co.kirikiri.goalroom.domain.GoalRoom;
import co.kirikiri.goalroom.domain.GoalRoomMember;
import co.kirikiri.goalroom.persistence.GoalRoomMemberRepository;
import co.kirikiri.goalroom.persistence.GoalRoomRepository;
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 AccomplishmentRateUpdateEventListener {

private final GoalRoomRepository goalRoomRepository;
private final GoalRoomMemberRepository goalRoomMemberRepository;

@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
@Transactional
public void handleUpdateAccomplishmentRate(final AccomplishmentRateUpdateEvent accomplishmentRateUpdateEvent) {
final GoalRoom goalRoom = findGoalRoomById(accomplishmentRateUpdateEvent.goalRoomId());
final GoalRoomMember goalRoomMember = findGoalRoomMemberById(accomplishmentRateUpdateEvent.goalRoomMemberId());

updateAccomplishmentRate(goalRoom, goalRoomMember, accomplishmentRateUpdateEvent.pastCheckCount());
}

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 void updateAccomplishmentRate(final GoalRoom goalRoom, final GoalRoomMember goalRoomMember,
final int pastCheckCount) {
final int wholeCheckCount = goalRoom.getAllCheckCount();
final int memberCheckCount = pastCheckCount + 1;
final Double accomplishmentRate = 100 * memberCheckCount / (double) wholeCheckCount;

goalRoomMember.updateAccomplishmentRate(accomplishmentRate);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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.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) {
final List<CheckFeed> checkFeeds = findCheckFeeds(goalRoom, currentGoalRoomRoadmapNode);
return makeCheckFeedResponses(checkFeeds);
}

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
Loading