Skip to content

Commit

Permalink
Feat: 홈 화면과 관련된 API 구현 (#154)
Browse files Browse the repository at this point in the history
* #131 - feat: 홈화면에서 추천하는 책, 게시글, 한줄요약 개수 설정

* #131 - feat: 홈 화면 상단 책 랜덤 추천 API 구현

* #131 - feat: 홈 화면 상단 책 랜덤 추천 API 테스트 구현

* #131 - feat: 홈 화면 중단 좋아요 순서 게시글 API 구현

* #131 - test: 홈 화면 중단 좋아요 순서 게시글 API 테스트
구현

* #131 - fix: 삭제된 LikeBook 메소드 추가

* #131 - feat: 홈 화면 중단 좋아요 순서 한줄요약 API 구현

* #131 - test: 홈 화면 중단 좋아요 순서 한줄요약 API  테스트 구현

* #131 - feat: 홈 화면 중단 최신 순서 게시글 API 구현

* #131 - test: 홈 화면 중단 최신 순서 게시글 API 테스트 구현

* #131 - feat: 홈 화면 중단 최신 순서 한줄요약 API 구현

* #131 - test: 홈 화면 중단 최신 순서 한줄요약 API 테스트 구현

* #131 - feat: 책 추천 시 응답이 리스트인 것을 JSON 형식으로 수정

* #131 - refactor: Member의 profileStatus 변수명을 status로 변경

다른 Entity 와 통일

* #131 - feat: 홈 화면 하단 최신 순서 멤버 API 구현

* #131 - test: 홈 화면 하단 최신 순서 멤버 API 테스트 구현

* #131 - test: 홈화면에 반환하는 개수도 테스트하도록 보강

* #131 - fix: rebase 시 나타난 import 문제 수정

* #131 - refactor: sql 쿼리 소문자로 통일

* #131 - feat: 게시글, 한줄요약 추천 API 를 하나도 통일하고 타입을 파라미터로 받도록 개선

* #131 - feat: 파라미터로 변경으로 인한 테스트 변경
  • Loading branch information
morenow98 authored Feb 19, 2024
1 parent 631937a commit caffca2
Show file tree
Hide file tree
Showing 27 changed files with 361 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

import cotato.bookitlist.book.annotation.IsValidIsbn;
import cotato.bookitlist.book.dto.request.BookIsbn13Request;
import cotato.bookitlist.book.dto.response.BookApiListResponse;
import cotato.bookitlist.book.dto.response.BookApiResponse;
import cotato.bookitlist.book.dto.response.BookListResponse;
import cotato.bookitlist.book.dto.response.BookResponse;
import cotato.bookitlist.book.dto.response.*;
import cotato.bookitlist.book.service.BookService;
import io.swagger.v3.oas.annotations.Parameter;
import jakarta.validation.Valid;
Expand Down Expand Up @@ -80,6 +77,11 @@ public ResponseEntity<Void> registerBook(
return ResponseEntity.created(location).build();
}

@GetMapping("/recommend")
public ResponseEntity<BookRecommendListResponse> recommendBook() {
return ResponseEntity.ok(bookService.recommendBook());
}

}


Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package cotato.bookitlist.book.dto.response;

import java.util.List;

public record BookRecommendListResponse(
List<BookRecommendResponse> bookList
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package cotato.bookitlist.book.dto.response;

import cotato.bookitlist.book.dto.BookDto;

public record BookRecommendResponse(
String title,
String author,
String description,
String isbn13,
String cover
) {
public static BookRecommendResponse from(BookDto dto) {
return new BookRecommendResponse(
dto.title(),
dto.author(),
dto.description(),
dto.isbn13(),
dto.cover()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;

public interface BookRepository extends JpaRepository<Book, Long> {

Optional<Book> findByIsbn13(String isbn13);

// TODO: 단순 like query로 한영에 따라 결과가 다르게 나온다. 이를 해결해야함!!
@Query("select b from Book b where lower(b.title) like lower(concat('%', :keyword, '%')) or lower(b.author) like lower(concat('%', :keyword, '%')) or lower(b.description) like lower(concat('%', :keyword, '%'))")
@Query("select b from Book b where LOWER(b.title) like LOWER(CONCAT('%', :keyword, '%')) or LOWER(b.author) like LOWER(CONCAT('%', :keyword, '%')) or LOWER(b.description) like LOWER(CONCAT('%', :keyword, '%'))")
Page<Book> findAllByKeyword(@Param("keyword") String keyword, Pageable pageable);

@Query("select b from BookLike l join l.book b join l.member m where m.id = :memberId")
Page<Book> findLikeBookByMemberId(Long memberId, Pageable pageable);

@Query(value = "select * from Book b order by RAND() LIMIT :count", nativeQuery = true)
List<Book> findBooksByRandom(int count);
}
17 changes: 17 additions & 0 deletions src/main/java/cotato/bookitlist/book/service/BookService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,29 @@
import cotato.bookitlist.book.dto.BookDto;
import cotato.bookitlist.book.dto.response.BookApiListResponse;
import cotato.bookitlist.book.dto.response.BookListResponse;
import cotato.bookitlist.book.dto.response.BookRecommendListResponse;
import cotato.bookitlist.book.dto.response.BookRecommendResponse;
import cotato.bookitlist.book.redis.BookApiCache;
import cotato.bookitlist.book.repository.BookRepository;
import jakarta.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class BookService {

@Value("${recommend.count.book}")
private int recommendCount;

private final BookApiComponent bookApiComponent;
private final BookApiCacheService bookApiCacheService;
private final BookRepository bookRepository;
Expand Down Expand Up @@ -66,4 +74,13 @@ public Long registerBook(String isbn13) {
public BookListResponse getLikeBooks(Long memberId, Pageable pageable) {
return BookListResponse.from(bookRepository.findLikeBookByMemberId(memberId, pageable));
}

public BookRecommendListResponse recommendBook() {
List<BookRecommendResponse> bookRecommendList = bookRepository.findBooksByRandom(recommendCount).stream()
.map(BookDto::from)
.map(BookRecommendResponse::from)
.toList();

return new BookRecommendListResponse(bookRecommendList);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package cotato.bookitlist.common.domain;

public enum RecommendType {
LIKE, NEW
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import cotato.bookitlist.config.security.jwt.AuthDetails;
import cotato.bookitlist.member.dto.request.NameChangeRequest;
import cotato.bookitlist.member.dto.response.MemberRecommendListResponse;
import cotato.bookitlist.member.dto.response.MemberResponse;
import cotato.bookitlist.member.dto.response.ProfileResponse;
import cotato.bookitlist.member.service.MemberService;
Expand Down Expand Up @@ -57,4 +58,9 @@ public ResponseEntity<Void> changeName(
return ResponseEntity.ok().build();
}

@GetMapping("/recommend/new")
public ResponseEntity<MemberRecommendListResponse> getNewMembers() {
return ResponseEntity.ok(memberService.getNewMembers());
}

}
10 changes: 5 additions & 5 deletions src/main/java/cotato/bookitlist/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class Member extends BaseEntity {
private String profileLink;

@Enumerated(EnumType.STRING)
private ProfileStatus profileStatus = ProfileStatus.PUBLIC;
private ProfileStatus status = ProfileStatus.PUBLIC;

private boolean deleted = false;

Expand Down Expand Up @@ -66,16 +66,16 @@ public String updateProfileLink(String url) {
}

public void validatePublicProfile(Long memberId) {
if (profileStatus.equals(ProfileStatus.PRIVATE) && !id.equals(memberId)) {
if (status.equals(ProfileStatus.PRIVATE) && !id.equals(memberId)) {
throw new AccessDeniedException("권한이 존재하지 않는 멤버입니다.");
}
}

public void changeProfileStatus() {
if (profileStatus.equals(ProfileStatus.PRIVATE)) {
profileStatus = ProfileStatus.PUBLIC;
if (status.equals(ProfileStatus.PRIVATE)) {
status = ProfileStatus.PUBLIC;
} else {
profileStatus = ProfileStatus.PRIVATE;
status = ProfileStatus.PRIVATE;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/cotato/bookitlist/member/dto/MemberDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static MemberDto from(Member entity, Long memberId) {
entity.getEmail(),
entity.getName(),
entity.getProfileLink(),
entity.getProfileStatus(),
entity.getStatus(),
entity.getId().equals(memberId)
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cotato.bookitlist.member.dto.response;

import cotato.bookitlist.member.domain.Member;
import org.springframework.data.domain.Page;

import java.util.List;

public record MemberRecommendListResponse (
List<MemberRecommendResponse> memberList
) {
public static MemberRecommendListResponse of(Page<Member> page) {
return new MemberRecommendListResponse(
page.stream().map(MemberRecommendResponse::of).toList()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package cotato.bookitlist.member.dto.response;

import cotato.bookitlist.member.domain.Member;

public record MemberRecommendResponse(
Long memberId,
String profileLink
) {
public static MemberRecommendResponse of(Member member) {
return new MemberRecommendResponse(member.getId(), member.getProfileLink());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@

import cotato.bookitlist.config.security.oauth.AuthProvider;
import cotato.bookitlist.member.domain.Member;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface MemberRepository extends JpaRepository<Member, Long> {

Member findByOauth2IdAndAuthProvider(String oauth2Id, AuthProvider authProvider);

@Query("select m from Member m where m.status = 'PUBLIC'")
Page<Member> findPublicMember(Pageable pageable);

}
14 changes: 14 additions & 0 deletions src/main/java/cotato/bookitlist/member/service/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
import cotato.bookitlist.file.service.FileService;
import cotato.bookitlist.member.domain.Member;
import cotato.bookitlist.member.dto.MemberDto;
import cotato.bookitlist.member.dto.response.MemberRecommendListResponse;
import cotato.bookitlist.member.repository.MemberRepository;
import jakarta.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
Expand All @@ -15,6 +20,9 @@
@RequiredArgsConstructor
public class MemberService {

@Value("${recommend.count.member}")
private int recommendCount;

private static final String PROFILE_FILE_NAME = "profile";

private final MemberRepository memberRepository;
Expand Down Expand Up @@ -48,4 +56,10 @@ public void changeName(String name, Long memberId) {
member.changeName(name);
}


public MemberRecommendListResponse getNewMembers() {
Pageable pageable = PageRequest.of(0, recommendCount, Sort.by("createdAt").descending());

return MemberRecommendListResponse.of(memberRepository.findPublicMember(pageable));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cotato.bookitlist.post.controller;

import cotato.bookitlist.book.annotation.IsValidIsbn;
import cotato.bookitlist.common.domain.RecommendType;
import cotato.bookitlist.config.security.jwt.AuthDetails;
import cotato.bookitlist.post.dto.request.PostRegisterRequest;
import cotato.bookitlist.post.dto.request.PostUpdateRequest;
Expand Down Expand Up @@ -139,6 +140,18 @@ public ResponseEntity<PostListResponse> getMyPosts(
return ResponseEntity.ok(postService.getMyPosts(details.getId(), pageable));
}

@GetMapping("/recommend")
public ResponseEntity<PostListResponse> getRecommendPosts(
@RequestParam RecommendType type,
@RequestParam int start,
@AuthenticationPrincipal AuthDetails details
) {
if (details == null) {
return ResponseEntity.ok(postService.getRecommendPosts(type, start, DEFAULT_USER_ID));
}
return ResponseEntity.ok(postService.getRecommendPosts(type, start, details.getId()));
}

private void handlePostViewCount(HttpServletRequest request, HttpServletResponse response, Long postId) {
Cookie[] cookies = request.getCookies();
Cookie postViewCookie = findCookie(cookies);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@

public interface PostRepository extends PostRepositoryCustom, JpaRepository<Post, Long> {

@Query("select p from Post p where p.status = 'PUBLIC' and p.member.profileStatus = 'PUBLIC'")
@Query("select p from Post p where p.status = 'PUBLIC' and p.member.status = 'PUBLIC'")
Page<Post> findPublicPostAll(Pageable pageable);

@Query("select count(p) from Post p where p.status = 'PUBLIC' and p.book.isbn13 = :isbn13 and p.member.profileStatus = 'PUBLIC'")
@Query("select count(p) from Post p where p.status = 'PUBLIC' and p.book.isbn13 = :isbn13 and p.member.status = 'PUBLIC'")
int countPublicPostByBook_Isbn13(String isbn13);

Optional<Post> findByIdAndMemberId(Long postId, Long memberId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public Page<PostDto> findPublicPostWithLikedByIsbn13(String isbn13, Long memberI
)
.from(post)
.join(post.member, member)
.where(isbnEq(isbn13), memberIdEq(memberId), post.status.eq(PostStatus.PUBLIC), post.member.profileStatus.eq(ProfileStatus.PUBLIC))
.where(isbnEq(isbn13), memberIdEq(memberId), post.status.eq(PostStatus.PUBLIC), post.member.status.eq(ProfileStatus.PUBLIC))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
Expand Down Expand Up @@ -159,6 +159,6 @@ private BooleanExpression buildPostAccessCondition(NumberPath<Long> postMemberId
.when(postMemberId.eq(memberId))
.then(true)
.otherwise(post.status.eq(PostStatus.PUBLIC)
.and(post.member.profileStatus.eq(ProfileStatus.PUBLIC)));
.and(post.member.status.eq(ProfileStatus.PUBLIC)));
}
}
30 changes: 30 additions & 0 deletions src/main/java/cotato/bookitlist/post/service/PostService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import cotato.bookitlist.book.domain.Book;
import cotato.bookitlist.book.repository.BookRepository;
import cotato.bookitlist.book.service.BookService;
import cotato.bookitlist.common.domain.RecommendType;
import cotato.bookitlist.member.domain.Member;
import cotato.bookitlist.member.repository.MemberRepository;
import cotato.bookitlist.post.domain.entity.Post;
Expand All @@ -14,7 +15,11 @@
import cotato.bookitlist.post.repository.PostRepository;
import jakarta.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -23,6 +28,9 @@
@RequiredArgsConstructor
public class PostService {

@Value("${recommend.count.post}")
private int recommendCount;

private final BookService bookService;
private final PostRepository postRepository;
private final MemberRepository memberRepository;
Expand Down Expand Up @@ -92,4 +100,26 @@ public PostListResponse searchLikePost(Long memberId, Pageable pageable) {
public PostListResponse getMyPosts(Long memberId, Pageable pageable) {
return PostListResponse.from(postRepository.findByMemberId(memberId, pageable), memberId);
}

@Transactional(readOnly = true)
public PostListResponse getRecommendPosts(RecommendType recommendType, int start, Long memberId) {
return switch (recommendType) {
case LIKE -> getMostLikePosts(start, memberId);
case NEW -> getNewPosts(start, memberId);
};
}

public PostListResponse getMostLikePosts(int start, Long memberId) {
Pageable pageable = PageRequest.of(start, recommendCount, Sort.by("likeCount").descending());
Page<Post> postPage = postRepository.findPublicPostAll(pageable);

return PostListResponse.from(postPage, memberId);
}

public PostListResponse getNewPosts(int start, Long memberId) {
Pageable pageable = PageRequest.of(start, recommendCount, Sort.by("createdAt").descending());
Page<Post> postPage = postRepository.findPublicPostAll(pageable);

return PostListResponse.from(postPage, memberId);
}
}
Loading

0 comments on commit caffca2

Please sign in to comment.