-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FEAT] 게시글 찜하기 기능 구현, Mypage 찜한 게시글 목록 조회 (#143)
* feat: 좋아요 Entity, Repository 추가 * feat: 게시글 좋아요 엔드포인트 추가, Service 로직 작성, Custom Repository 추가 * fix: memberServicae -> memberService 오타 수정 * feat: @repository 추가 * feat: Mypage 찜한 게시글 조회 * style: { 앞 공백 추가 * refactor: @repository 제거 * refactor: 안쓰는 함수 제거 * feat: 게시글 조회시 잘못된 정렬 방식에 대한 예외 처리 * feat: PostLike에 Optional 적용 및 찜 관리시 조회 순서 변경 * refactor: 찜 관리 기능 map, orElseGet사용하여 구현
- Loading branch information
Showing
11 changed files
with
241 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
src/main/java/econo/buddybridge/post/controller/PostLikeController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
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; | ||
import econo.buddybridge.utils.session.SessionUtils; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/api/posts/likes") | ||
@Tag(name = "게시글 좋아요 API", description = "게시글 좋아요 관련 API") | ||
public class PostLikeController { | ||
|
||
private final PostLikeService postLikeService; | ||
|
||
@Operation(summary = "찜한 게시글 관리", description = "찜한 게시글 관리 찜을(생성, 삭제)합니다.") | ||
@PostMapping("/{post-id}") | ||
public ApiResponse<ApiResponse.CustomBody<Boolean>> managePostLike( | ||
@PathVariable(name = "post-id") Long postId, | ||
HttpServletRequest request | ||
) { | ||
Long memberId = SessionUtils.getMemberId(request); | ||
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); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package econo.buddybridge.post.entity; | ||
|
||
import econo.buddybridge.member.entity.Member; | ||
import jakarta.persistence.*; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Entity | ||
@Getter | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@Table(name = "POST_LIKE") | ||
public class PostLike { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "post_id") | ||
private Post post; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "member_id") | ||
private Member member; | ||
|
||
public PostLike(Post post, Member member) { | ||
this.post = post; | ||
this.member = member; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
src/main/java/econo/buddybridge/post/exception/PostInvalidSortValueException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package econo.buddybridge.post.exception; | ||
|
||
import econo.buddybridge.common.exception.BusinessException; | ||
|
||
public class PostInvalidSortValueException extends BusinessException { | ||
|
||
public static BusinessException EXCEPTION = new PostInvalidSortValueException(); | ||
|
||
private PostInvalidSortValueException() { | ||
super(PostErrorCode.POST_INVALID_SORT_VALUE); | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
src/main/java/econo/buddybridge/post/repository/PostLikeRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package econo.buddybridge.post.repository; | ||
|
||
import econo.buddybridge.post.entity.PostLike; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
public interface PostLikeRepository extends JpaRepository<PostLike, Long>, PostLikeRepositoryCustom { | ||
|
||
} |
14 changes: 14 additions & 0 deletions
14
src/main/java/econo/buddybridge/post/repository/PostLikeRepositoryCustom.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
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); | ||
} |
70 changes: 70 additions & 0 deletions
70
src/main/java/econo/buddybridge/post/repository/PostLikeRepositoryCustomImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package econo.buddybridge.post.repository; | ||
|
||
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 lombok.RequiredArgsConstructor; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
import static econo.buddybridge.post.entity.QPostLike.postLike; | ||
|
||
@RequiredArgsConstructor | ||
public class PostLikeRepositoryCustomImpl implements PostLikeRepositoryCustom { | ||
|
||
private final JPAQueryFactory queryFactory; | ||
|
||
@Override | ||
public Optional<PostLike> findByPostIdAndMemberId(Long postId, Long memberId) { | ||
|
||
PostLike like = queryFactory.selectFrom(postLike) | ||
.where(postLike.post.id.eq(postId).and(postLike.member.id.eq(memberId))) | ||
.fetchOne(); | ||
|
||
return Optional.ofNullable(like); | ||
} | ||
|
||
@Override | ||
public PostCustomPage findPostsByLikes(Long memberId, Integer page, Integer size, String sort, PostType postType) { | ||
|
||
// Todo: 쿼리 최적화 고려 | ||
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(PostResDto::new) | ||
.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; | ||
}; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
src/main/java/econo/buddybridge/post/service/PostLikeService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package econo.buddybridge.post.service; | ||
|
||
import econo.buddybridge.member.entity.Member; | ||
import econo.buddybridge.member.service.MemberService; | ||
import econo.buddybridge.post.dto.PostCustomPage; | ||
import econo.buddybridge.post.entity.Post; | ||
import econo.buddybridge.post.entity.PostLike; | ||
import econo.buddybridge.post.entity.PostType; | ||
import econo.buddybridge.post.repository.PostLikeRepository; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class PostLikeService { | ||
|
||
private final MemberService memberService; | ||
private final PostService postService; | ||
private final PostLikeRepository postLikeRepository; | ||
|
||
@Transactional | ||
public Boolean managePostLike(Long memberId, Long postId) { | ||
|
||
Member member = memberService.findMemberByIdOrThrow(memberId); | ||
Post post = postService.findPostByIdOrThrow(postId); | ||
|
||
return postLikeRepository.findByPostIdAndMemberId(postId, memberId) | ||
.map(this::removeLike) | ||
.orElseGet(() -> addLike(post, member)); | ||
} | ||
|
||
private boolean removeLike(PostLike postLike) { | ||
postLikeRepository.delete(postLike); | ||
return false; | ||
} | ||
|
||
private boolean addLike(Post post, Member member) { | ||
PostLike newLike = new PostLike(post, member); | ||
postLikeRepository.save(newLike); | ||
return true; | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public PostCustomPage getPostsLikes(Long memberId, Integer page, Integer size, String sort, PostType postType) { | ||
return postLikeRepository.findPostsByLikes(memberId, page - 1, size, sort, postType); | ||
} | ||
|
||
|
||
|
||
} |