Skip to content

Commit

Permalink
[Feat] 매칭 상태에 따른 matchingDoneCount 계산 및 게시글 상태 업데이트 기능 구현 (#182)
Browse files Browse the repository at this point in the history
* feat: @formula를 이용해 Native Query로 matchingDoneCount 계산

* feat: 매칭이 다 찼을 때 예외 코드 작성

* feat: 매칭 완료된 수를 가져오는 네이티브 쿼리 함수 작성

* feat: 매칭 상태 업데이트 및 매칭 상태의 수에 따른 게시글 상태 업데이트 로직 구현

* rename: 모집완료 열거형 식별자 EXCEEDED_MATCHING_LIMIT에서 MATCHING_COMPLETED로 변경

* refactor: MatchingRepository에 집계 가능 함수 추가 하므로 @formula로 집계하던 matchingDoneCount 필드 제거

* feat: MatchingRepository 집계 함수로 matchingDoneCount 집계

* feat: MatchingRepository 집계 함수 사용

* refactor: 집계 함수 Native Query -> JPQL로 변경

* refactor: 게시글 목록을 조회하는 경우 PostRepository 사용하도록 변경, PostLikeRepository는 찜만 관리

* feat: BooleanExpression, OrderSpecifier에 Qpost 지정을 통해 함수 중복 제거
  • Loading branch information
injae-348 authored Oct 15, 2024
1 parent 6ed567c commit 520abe4
Show file tree
Hide file tree
Showing 14 changed files with 119 additions and 109 deletions.
15 changes: 3 additions & 12 deletions src/main/java/econo/buddybridge/matching/entity/Matching.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import econo.buddybridge.common.persistence.BaseEntity;
import econo.buddybridge.member.entity.Member;
import econo.buddybridge.post.entity.Post;
import econo.buddybridge.post.entity.PostStatus;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand All @@ -18,13 +17,14 @@
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.ArrayList;
import java.util.List;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Table(name = "MATCHING")
Expand Down Expand Up @@ -63,16 +63,7 @@ public Matching(Post post, Member taker, Member giver, MatchingStatus matchingSt
this.matchingStatus = matchingStatus;
}

// 매칭 상태 변경
public void updateMatching(MatchingStatus matchingStatus) {
this.matchingStatus = matchingStatus;
switch (matchingStatus) {
case PENDING, FAILED:
post.changeStatus(PostStatus.RECRUITING);
break;
case DONE:
post.changeStatus(PostStatus.FINISHED);
break;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package econo.buddybridge.matching.exception;

import econo.buddybridge.common.exception.BusinessException;

public class MatchingCompletedException extends BusinessException {

public static BusinessException EXCEPTION = new MatchingCompletedException();

private MatchingCompletedException() {
super(MatchingErrorCode.MATCHING_COMPLETED);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

public enum MatchingErrorCode implements ErrorCode {
MATCHING_NOT_FOUND("MA001", HttpStatus.NOT_FOUND, "존재하지 않는 매칭입니다."),
MATCHING_UNAUTHORIZED_ACCESS("MA002", HttpStatus.FORBIDDEN, "사용자가 생성한 매칭방이 아닙니다.")
MATCHING_UNAUTHORIZED_ACCESS("MA002", HttpStatus.FORBIDDEN, "사용자가 생성한 매칭방이 아닙니다."),
MATCHING_COMPLETED("MA003", HttpStatus.BAD_REQUEST, "모집이 완료되었습니다.")
;

private final String code;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package econo.buddybridge.matching.repository;

import econo.buddybridge.matching.entity.Matching;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.Optional;

public interface MatchingRepository extends JpaRepository<Matching, Long> {

@Query("SELECT m FROM Matching m JOIN FETCH m.post JOIN FETCH m.giver JOIN FETCH m.taker WHERE m.id = :matchingId")
Optional<Matching> findByIdWithMembersAndPost(Long matchingId);

@Query("SELECT COUNT(m) FROM Matching m WHERE m.post.id = :post_id AND m.matchingStatus = 'DONE'")
Integer countMatchingDoneByPostId(Long post_id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
import econo.buddybridge.matching.dto.MatchingUpdateDto;
import econo.buddybridge.matching.entity.Matching;
import econo.buddybridge.matching.entity.MatchingStatus;
import econo.buddybridge.matching.exception.MatchingCompletedException;
import econo.buddybridge.matching.exception.MatchingNotFoundException;
import econo.buddybridge.matching.repository.MatchingRepository;
import econo.buddybridge.member.entity.Member;
import econo.buddybridge.member.service.MemberService;
import econo.buddybridge.post.entity.Post;
import econo.buddybridge.post.entity.PostStatus;
import econo.buddybridge.post.entity.PostType;
import econo.buddybridge.post.exception.PostUnauthorizedAccessException;
import econo.buddybridge.post.service.PostService;
Expand Down Expand Up @@ -77,11 +79,15 @@ public Long createMatchingById(MatchingReqDto matchingReqDto, Long memberId) {
public Long updateMatching(Long matchingId, MatchingUpdateDto matchingUpdateDto, Long memberId) {

Matching matching = findMatchingByIdOrThrow(matchingId);

validatePostAuthor(matching.getPost(), memberId);

// 모집이 완료되었으며, 상태가 DONE인 경우 예외 처리
if (isFullAndMatchingStatusIsDone(matching.getPost(), matchingUpdateDto.matchingStatus())) {
throw MatchingCompletedException.EXCEPTION;
}

matching.updateMatching(matchingUpdateDto.matchingStatus());

updatePostStatusByMatchingDoneCount(matching.getPost().getId());
return matching.getId();
}

Expand All @@ -103,6 +109,21 @@ private Matching matchingReqToMatching(Post post, Member taker, Member giver) {
.build();
}

private void updatePostStatusByMatchingDoneCount(Long postId) {
Post post = postService.findPostByIdOrThrow(postId);

Integer headcount = post.getHeadcount();
Integer matchingDoneCount = matchingRepository.countMatchingDoneByPostId(postId);

PostStatus status = headcount.equals(matchingDoneCount) ? PostStatus.FINISHED : PostStatus.RECRUITING;
post.changeStatus(status);
}

private boolean isFullAndMatchingStatusIsDone(Post post, MatchingStatus matchingStatus) {
Integer matchingDoneCount = matchingRepository.countMatchingDoneByPostId(post.getId());
return post.getHeadcount().equals(matchingDoneCount) && matchingStatus == MatchingStatus.DONE;
}

// 게시글 작성 회원과 현재 로그인한 회원 일치 여부 판단
private void validatePostAuthor(Post post, Long memberId) {
if ((post.getPostType() == PostType.GIVER && !post.getAuthor().getId().equals(memberId)) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ public class PostController {

private final PostService postService;

@Operation(summary = "찜한 게시글 목록 조회", description = "찜한 게시글 목록을 조회합니다.")
@GetMapping("/likes/my-page")
public ApiResponse<ApiResponse.CustomBody<PostCustomPage>> getPostLikes(
@RequestParam("page") Integer page,
@RequestParam("size") Integer size,
@RequestParam(defaultValue = "desc", required = false) String sort,
@RequestParam(value = "post-type", required = false) PostType postType,
HttpServletRequest request
) {
Long memberId = SessionUtils.getMemberId(request);
PostCustomPage posts = postService.getPostsLikes(memberId, page, size, sort, postType);
return ApiResponseGenerator.success(posts, HttpStatus.OK);
}

@Operation(summary = "내가 작성한 게시글 조회", description = "내가 작성한 게시글 목록을 조회합니다.")
@GetMapping("/my-page")
public ApiResponse<ApiResponse.CustomBody<PostCustomPage>> getAllPostsMyPage(
Expand Down Expand Up @@ -121,5 +135,4 @@ public ApiResponse<ApiResponse.CustomBody<Void>> deletePost(
postService.deletePost(postId, memberId);
return ApiResponseGenerator.success(HttpStatus.NO_CONTENT);
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package econo.buddybridge.post.controller;

import econo.buddybridge.post.dto.PostCustomPage;
import econo.buddybridge.post.entity.PostType;
import econo.buddybridge.post.service.PostLikeService;
import econo.buddybridge.utils.api.ApiResponse;
import econo.buddybridge.utils.api.ApiResponseGenerator;
Expand All @@ -11,11 +9,9 @@
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
Expand All @@ -36,19 +32,4 @@ public ApiResponse<ApiResponse.CustomBody<Boolean>> managePostLike(
Boolean isLike = postLikeService.managePostLike(memberId, postId);
return ApiResponseGenerator.success(isLike, HttpStatus.OK);
}

@Operation(summary = "찜한 게시글 목록 조회", description = "찜한 게시글 목록을 조회합니다.")
@GetMapping("/my-page")
public ApiResponse<ApiResponse.CustomBody<PostCustomPage>> getPostLikes(
@RequestParam("page") Integer page,
@RequestParam("size") Integer size,
@RequestParam(defaultValue = "desc", required = false) String sort,
@RequestParam(value = "post-type", required = false) PostType postType,
HttpServletRequest request
) {
Long memberId = SessionUtils.getMemberId(request);
PostCustomPage posts = postLikeService.getPostsLikes(memberId, page, size, sort, postType);
return ApiResponseGenerator.success(posts, HttpStatus.OK);
}

}
4 changes: 3 additions & 1 deletion src/main/java/econo/buddybridge/post/dto/PostResDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ public record PostResDto(
LocalDateTime assistanceStartTime,
LocalDateTime assistanceEndTime,
Integer headcount,
Integer matchingDoneCount,
Boolean isLiked
) {

public PostResDto(Post post, Boolean isLiked) {
public PostResDto(Post post, Boolean isLiked, Integer matchingDoneCount) {
this(
post.getId(),
new MemberResDto(post.getAuthor()),
Expand All @@ -60,6 +61,7 @@ public PostResDto(Post post, Boolean isLiked) {
post.getAssistanceTime().getAssistanceStartTime(),
post.getAssistanceTime().getAssistanceEndTime(),
post.getHeadcount(),
matchingDoneCount,
isLiked
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package econo.buddybridge.post.repository;

import econo.buddybridge.post.dto.PostCustomPage;
import econo.buddybridge.post.entity.PostLike;
import econo.buddybridge.post.entity.PostType;

import java.util.Optional;

public interface PostLikeRepositoryCustom {

Optional<PostLike> findByPostIdAndMemberId(Long postId, Long memberId);

PostCustomPage findPostsByLikes(Long memberId, Integer page, Integer size, String sort, PostType postType);
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
package econo.buddybridge.post.repository;

import static econo.buddybridge.post.entity.QPostLike.postLike;

import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import econo.buddybridge.post.dto.PostCustomPage;
import econo.buddybridge.post.dto.PostResDto;
import econo.buddybridge.post.entity.PostLike;
import econo.buddybridge.post.entity.PostType;
import econo.buddybridge.post.exception.PostInvalidSortValueException;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;

import java.util.Optional;

import static econo.buddybridge.post.entity.QPostLike.postLike;

@RequiredArgsConstructor
public class PostLikeRepositoryCustomImpl implements PostLikeRepositoryCustom {

Expand All @@ -28,39 +22,4 @@ public Optional<PostLike> findByPostIdAndMemberId(Long postId, Long memberId) {

return Optional.ofNullable(like);
}

@Override
public PostCustomPage findPostsByLikes(Long memberId, Integer page, Integer size, String sort, PostType postType) {

List<PostResDto> content = queryFactory
.select(postLike.post)
.from(postLike)
.where(postLike.member.id.eq(memberId), buildPostTypeExpression(postType))
.offset((long) page * size)
.orderBy(buildOrderSpecifier(sort))
.fetch()
.stream()
.map(post -> new PostResDto(post, true))
.toList();

Long totalElements = queryFactory
.select(postLike.count())
.from(postLike)
.where(postLike.member.id.eq(memberId))
.fetchOne();

return new PostCustomPage(content, totalElements, content.size() < size);
}

private BooleanExpression buildPostTypeExpression(PostType postType) {
return postType != null ? postLike.post.postType.eq(postType) : null;
}

private OrderSpecifier<?> buildOrderSpecifier(String sort) {
return switch (sort.toLowerCase()) {
case "desc" -> postLike.post.createdAt.desc();
case "asc" -> postLike.post.createdAt.asc();
default -> throw PostInvalidSortValueException.EXCEPTION;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ PostCustomPage findPosts(Long memberId, Integer page, Integer size, String sort,
PostStatus postStatus, List<DisabilityType> disabilityType, List<AssistanceType> assistanceType);

PostCustomPage findPostsMyPage(Long memberId, Integer page, Integer size, String sort, PostType postType);

PostCustomPage findPostsByLikes(Long memberId, Integer page, Integer size, String sort, PostType postType);
}
Loading

0 comments on commit 520abe4

Please sign in to comment.