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] 즐겨찾기 관련 추가 기능 개발 #77

Merged
merged 2 commits into from
Mar 14, 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
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
Loading