Skip to content

Commit

Permalink
Merge pull request #77 from Team-Lecue/feat/#76-favorite_additional_func
Browse files Browse the repository at this point in the history
[FEAT] 즐겨찾기 관련 추가 기능 개발
  • Loading branch information
ddongseop authored Mar 14, 2024
2 parents 61d9b94 + 0684dff commit b00654c
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import lombok.RequiredArgsConstructor;
import org.sopt.lequuServer.domain.book.dto.request.BookCreateRequestDto;
import org.sopt.lequuServer.domain.book.dto.response.BookCreateResponseDto;
import org.sopt.lequuServer.domain.book.dto.response.BookDetailFavoriteResponseDto;
import org.sopt.lequuServer.domain.book.dto.response.BookDetailResponseDto;
import org.sopt.lequuServer.domain.book.facade.BookFacade;
import org.sopt.lequuServer.global.auth.jwt.JwtProvider;
Expand Down Expand Up @@ -37,4 +38,9 @@ public ResponseEntity<?> deleteBook(@PathVariable Long bookId) {
public ResponseEntity<ApiResponse<BookDetailResponseDto>> getBookDetail(@PathVariable String bookUuid) {
return ResponseEntity.ok(ApiResponse.success(SuccessType.GET_BOOK_DETAIL_SUCCESS, bookFacade.getBookDetail(bookUuid)));
}

@GetMapping("/favorite/{bookUuid}")
public ResponseEntity<ApiResponse<BookDetailFavoriteResponseDto>> getBookDetailFavorite(Principal principal, @PathVariable String bookUuid) {
return ResponseEntity.ok(ApiResponse.success(SuccessType.GET_BOOK_DETAIL_SUCCESS, bookFacade.getBookDetailFavorite(JwtProvider.getUserFromPrincial(principal), bookUuid)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.sopt.lequuServer.domain.book.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import org.sopt.lequuServer.domain.book.model.Book;
import org.sopt.lequuServer.domain.member.model.Member;
import org.sopt.lequuServer.domain.note.dto.response.NoteDetailResponseDto;
import org.sopt.lequuServer.domain.note.model.Note;
import org.sopt.lequuServer.domain.sticker.dto.response.PostedStickerDetailResponseDto;
import org.sopt.lequuServer.domain.sticker.model.PostedSticker;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

import static java.util.Comparator.comparing;

public record BookDetailFavoriteResponseDto(

@Schema(description = "레큐북 고유 id", example = "1")
Long bookId,

@Schema(description = "레큐북 즐겨찾기 등록 여부", example = "true")
Boolean isFavorite,

@Schema(description = "최애 사진", example = "https://dzfv99wxq6tx0.cloudfront.net/books/favorite_image/b4006561-382b-479e-ae1d-e841922e883f.jpg")
String favoriteImage,

@Schema(description = "최애 이름", example = "LeoJ")
String favoriteName,

@Schema(description = "레큐북 제목", example = "1번째 레큐북")
String title,

@Schema(description = "레큐북 소개", example = "레큐북의 내용입니다.")
String description,

@Schema(description = "레큐북 생성 일시", example = "2024.01.11")
String bookDate,

@Schema(description = "레큐북 작성자 닉네임", example = "예딘")
String bookNickname,

@Schema(description = "레큐북 배경색", example = "#F5F5F5")
String bookBackgroundColor,

@Schema(description = "레큐 노트 개수", example = "100")
int noteNum,

List<NoteDetailResponseDto> noteList,

List<PostedStickerDetailResponseDto> postedStickerList
) {
public static BookDetailFavoriteResponseDto of(Member member, Book book) {
String bookDate = formatLocalDate(book);

List<Note> sortedNotes = book.getNotes().stream()
.sorted(comparing(Note::getCreatedAt).reversed())
.toList();

// 레큐노트 리스트 가공
int renderTypeCounter = 1;
List<NoteDetailResponseDto> noteList = new ArrayList<>();
for (Note note : sortedNotes) {
noteList.add(NoteDetailResponseDto.of(note, renderTypeCounter));
renderTypeCounter = (renderTypeCounter % 6 == 0) ? 1 : renderTypeCounter + 1;
}

// 부착된 스티커 리스트 가공
List<PostedSticker> postedStickers = book.getPostedStickers();
List<PostedStickerDetailResponseDto> postedStickerList = new ArrayList<>();
for (PostedSticker postedSticker : postedStickers) {
postedStickerList.add(PostedStickerDetailResponseDto.of(postedSticker));
}

Boolean isFavorite = member.getFavorites().stream()
.anyMatch(favorite -> favorite.getBook().equals(book));

return new BookDetailFavoriteResponseDto(book.getId(), isFavorite,
book.getFavoriteImage(), book.getFavoriteName(),
book.getTitle(), book.getDescription(), bookDate, book.getMember().getNickname(),
book.getBackgroundColor(), book.getNotes().size(), noteList, postedStickerList
);
}

private static String formatLocalDate(Book book) {
LocalDateTime createdAt = book.getCreatedAt();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd");
return createdAt.format(formatter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ public static BookDetailResponseDto of(Book book) {
postedStickerList.add(PostedStickerDetailResponseDto.of(postedSticker));
}

return new BookDetailResponseDto(book.getId(), book.getFavoriteImage(), book.getFavoriteName(),
return new BookDetailResponseDto(book.getId(),
book.getFavoriteImage(), book.getFavoriteName(),
book.getTitle(), book.getDescription(), bookDate, book.getMember().getNickname(),
book.getBackgroundColor(), book.getNotes().size(), noteList, postedStickerList
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import lombok.RequiredArgsConstructor;
import org.sopt.lequuServer.domain.book.dto.request.BookCreateRequestDto;
import org.sopt.lequuServer.domain.book.dto.response.BookCreateResponseDto;
import org.sopt.lequuServer.domain.book.dto.response.BookDetailFavoriteResponseDto;
import org.sopt.lequuServer.domain.book.dto.response.BookDetailResponseDto;
import org.sopt.lequuServer.domain.book.model.Book;
import org.sopt.lequuServer.domain.book.repository.BookRepository;
Expand Down Expand Up @@ -58,14 +59,14 @@ public BookCreateResponseDto createBook(BookCreateRequestDto request, Long membe
String imageUrl = s3Service.getCloudFrontURL(BOOK_FAVORITE_IMAGE_FOLDER_NAME.getValue() + request.favoriteImage());

Book book = Book.builder()
.uuid(bookUuid)
.favoriteName(badWordFilterService.badWordChange(memberId, request.favoriteName()))
.favoriteImage(imageUrl)
.title(badWordFilterService.badWordChange(memberId, request.title()))
.description(badWordFilterService.badWordChange(memberId, request.description()))
.backgroundColor(request.backgroundColor())
.member(member)
.build();
.uuid(bookUuid)
.favoriteName(badWordFilterService.badWordChange(memberId, request.favoriteName()))
.favoriteImage(imageUrl)
.title(badWordFilterService.badWordChange(memberId, request.title()))
.description(badWordFilterService.badWordChange(memberId, request.description()))
.backgroundColor(request.backgroundColor())
.member(member)
.build();

return bookService.createBook(book, member);
}
Expand Down Expand Up @@ -102,4 +103,11 @@ public BookDetailResponseDto getBookDetail(String bookUuid) {
return BookDetailResponseDto.of(book);
}

public BookDetailFavoriteResponseDto getBookDetailFavorite(Long userId, String bookUuid) {
Book book = bookRepository.findByUuidOrThrow(bookUuid);
Member member = memberRepository.findByIdOrThrow(userId);

return BookDetailFavoriteResponseDto.of(member, book);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.sopt.lequuServer.domain.favorite.dto.request.FavoriteCreateRequestDto;
import org.sopt.lequuServer.domain.favorite.dto.request.FavoriteRequestDto;
import org.sopt.lequuServer.domain.favorite.dto.response.FavoriteBookResponseDto;
import org.sopt.lequuServer.global.common.dto.ApiResponse;
import org.springframework.http.ResponseEntity;
Expand All @@ -20,18 +20,26 @@ public interface FavoriteApi {

@SecurityRequirement(name = "JWT Authorization")
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "201",
description = "즐겨찾기 레큐북 등록을 성공했습니다."
responseCode = "201",
description = "즐겨찾기 레큐북 등록을 성공했습니다."
)
@Operation(summary = "즐겨찾기 레큐북 생성")
public ResponseEntity<ApiResponse<?>> createFavorite(Principal principal, @RequestBody FavoriteCreateRequestDto request);
public ResponseEntity<ApiResponse<?>> createFavorite(Principal principal, @RequestBody FavoriteRequestDto request);

@SecurityRequirement(name = "JWT Authorization")
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "즐겨찾는 레큐북 조회에 성공했습니다.",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = FavoriteBookResponseDto.class)))
responseCode = "200",
description = "즐겨찾는 레큐북 조회에 성공했습니다.",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = FavoriteBookResponseDto.class)))
)
@Operation(summary = "즐겨찾는 레큐북 조회")
public ResponseEntity<ApiResponse<List<FavoriteBookResponseDto>>> getFavorite(Principal principal);

@SecurityRequirement(name = "JWT Authorization")
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "204",
description = "즐겨찾기 레큐북 삭제에 성공했습니다."
)
@Operation(summary = "즐겨찾기 레큐북 삭제")
public ResponseEntity<ApiResponse<?>> deleteFavorite(Principal principal, @RequestBody FavoriteRequestDto request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import lombok.RequiredArgsConstructor;
import org.sopt.lequuServer.domain.favorite.dto.request.FavoriteCreateRequestDto;
import org.sopt.lequuServer.domain.favorite.dto.request.FavoriteRequestDto;
import org.sopt.lequuServer.domain.favorite.dto.response.FavoriteBookResponseDto;
import org.sopt.lequuServer.domain.favorite.facade.FavoriteFacade;
import org.sopt.lequuServer.global.auth.jwt.JwtProvider;
Expand All @@ -24,7 +24,7 @@ public class FavoriteController implements FavoriteApi {
private final FavoriteFacade favoriteFacade;

@PostMapping
public ResponseEntity<ApiResponse<?>> createFavorite(Principal principal, @RequestBody FavoriteCreateRequestDto request) {
public ResponseEntity<ApiResponse<?>> createFavorite(Principal principal, @RequestBody FavoriteRequestDto request) {
favoriteFacade.createFavorite(JwtProvider.getUserFromPrincial(principal), request);
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(SuccessType.CREATE_FAVORITE_SUCCESS));
}
Expand All @@ -33,4 +33,10 @@ public ResponseEntity<ApiResponse<?>> createFavorite(Principal principal, @Reque
public ResponseEntity<ApiResponse<List<FavoriteBookResponseDto>>> getFavorite(Principal principal) {
return ResponseEntity.ok(ApiResponse.success(SuccessType.GET_FAVORITE_SUCCESS, favoriteFacade.getFavorite(JwtProvider.getUserFromPrincial(principal))));
}

@DeleteMapping
public ResponseEntity<ApiResponse<?>> deleteFavorite(Principal principal, @RequestBody FavoriteRequestDto request) {
favoriteFacade.deleteFavorite(JwtProvider.getUserFromPrincial(principal), request);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import io.swagger.v3.oas.annotations.media.Schema;

public record FavoriteCreateRequestDto(
@Schema(example = "1")
Long bookId
public record FavoriteRequestDto(
@Schema(example = "1")
Long bookId
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import lombok.RequiredArgsConstructor;
import org.sopt.lequuServer.domain.book.model.Book;
import org.sopt.lequuServer.domain.book.repository.BookRepository;
import org.sopt.lequuServer.domain.favorite.dto.request.FavoriteCreateRequestDto;
import org.sopt.lequuServer.domain.favorite.dto.request.FavoriteRequestDto;
import org.sopt.lequuServer.domain.favorite.dto.response.FavoriteBookResponseDto;
import org.sopt.lequuServer.domain.favorite.model.Favorite;
import org.sopt.lequuServer.domain.favorite.repository.FavoriteRepository;
import org.sopt.lequuServer.domain.member.model.Member;
import org.sopt.lequuServer.domain.member.repository.MemberRepository;
import org.sopt.lequuServer.global.exception.enums.ErrorType;
import org.sopt.lequuServer.global.exception.model.CustomException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -25,7 +27,7 @@ public class FavoriteFacade {
private final BookRepository bookRepository;

@Transactional
public void createFavorite(Long memberId, FavoriteCreateRequestDto request) {
public void createFavorite(Long memberId, FavoriteRequestDto request) {
Member member = memberRepository.findByIdOrThrow(memberId);
Book book = bookRepository.findByIdOrThrow(request.bookId());

Expand All @@ -38,8 +40,18 @@ public List<FavoriteBookResponseDto> getFavorite(Long memberId) {
List<Favorite> favorites = favoriteRepository.findByMemberOrderByCreatedAtDesc(member);

return favorites.stream()
.limit(3) // 최신순 3개만 가져오기
.map(favorite -> FavoriteBookResponseDto.of(favorite.getBook()))
.collect(Collectors.toList());
.limit(3) // 최신순 3개만 가져오기
.map(favorite -> FavoriteBookResponseDto.of(favorite.getBook()))
.collect(Collectors.toList());
} // memberId를 이용해 그 멤버가 즐겨찾기 해놓은 레큐북 목록들을 반환하는 로직

@Transactional
public void deleteFavorite(Long memberId, FavoriteRequestDto request) {
Member member = memberRepository.findByIdOrThrow(memberId);
Book book = bookRepository.findByIdOrThrow(request.bookId());

Favorite favorite = favoriteRepository.findByMemberAndBook(member, book)
.orElseThrow(() -> new CustomException(ErrorType.NOT_FOUND_FAVORITE_ERROR));
favoriteRepository.delete(favorite);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package org.sopt.lequuServer.domain.favorite.repository;

import org.sopt.lequuServer.domain.book.model.Book;
import org.sopt.lequuServer.domain.favorite.model.Favorite;
import org.sopt.lequuServer.domain.member.model.Member;
import org.springframework.data.jpa.repository.JpaRepository;

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

public interface FavoriteRepository extends JpaRepository<Favorite, Long> {
List<Favorite> findByMemberOrderByCreatedAtDesc(Member member);

Optional<Favorite> findByMemberAndBook(Member member, Book book);
}

Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public enum ErrorType {
NOT_FOUND_REFRESH_TOKEN_ERROR(HttpStatus.NOT_FOUND, "존재하지 않는 리프레시 토큰입니다."),
NOT_FOUND_BOOK_ERROR(HttpStatus.NOT_FOUND, "존재하지 않는 레큐북입니다."),
NOT_FOUND_STICKER_ERROR(HttpStatus.NOT_FOUND, "존재하지 않는 스티커입니다."),
NOT_FOUND_FAVORITE_ERROR(HttpStatus.NOT_FOUND, "존재하지 않는 즐겨찾기입니다."),

/**
* 409 CONFLICT
Expand Down

0 comments on commit b00654c

Please sign in to comment.