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 all 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
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
Loading