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] 홈 화면 즐겨찾기 API 구현 #75

Merged
merged 8 commits into from
Mar 13, 2024
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package org.sopt.lequuServer.domain.book.facade;

import static org.sopt.lequuServer.global.s3.enums.ImageFolderName.BOOK_FAVORITE_IMAGE_FOLDER_NAME;

import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.sopt.lequuServer.domain.book.dto.request.BookCreateRequestDto;
import org.sopt.lequuServer.domain.book.dto.response.BookCreateResponseDto;
Expand All @@ -23,6 +19,11 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.UUID;

import static org.sopt.lequuServer.global.s3.enums.ImageFolderName.BOOK_FAVORITE_IMAGE_FOLDER_NAME;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
Expand Down Expand Up @@ -100,4 +101,5 @@ public BookDetailResponseDto getBookDetail(String bookUuid) {

return BookDetailResponseDto.of(book);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.persistence.*;
import lombok.*;
import org.sopt.lequuServer.domain.favorite.model.Favorite;
import org.sopt.lequuServer.domain.note.model.Note;
import org.sopt.lequuServer.domain.sticker.model.PostedSticker;
import org.sopt.lequuServer.domain.member.model.Member;
Expand Down Expand Up @@ -58,6 +59,13 @@ public void addPostedSticker(PostedSticker postedSticker) {
postedStickers.add(postedSticker);
}

@OneToMany(mappedBy = "book")
private final List<Favorite> favorites = new ArrayList<>();

public void addFavorite(Favorite favorite) {
favorites.add(favorite);
}

@Builder
public Book(String uuid, String favoriteName, String favoriteImage, String title, String description, String backgroundColor, Member member, int popularRate) {
this.uuid = uuid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,4 @@ public ResponseEntity<ApiResponse<List<PopularBookResponseDto>>> getHome() {
public ResponseEntity<ApiResponse<?>> test() {
throw new RuntimeException("테스트용 에러 발생");
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.sopt.lequuServer.domain.favorite.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
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.response.FavoriteBookResponseDto;
import org.sopt.lequuServer.global.common.dto.ApiResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;

import java.security.Principal;
import java.util.List;

@Tag(name = "Favorite", description = "즐겨찾기 API")
public interface FavoriteApi {

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

@SecurityRequirement(name = "JWT Authorization")
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "즐겨찾는 레큐북 조회에 성공했습니다.",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = FavoriteBookResponseDto.class)))
)
@Operation(summary = "즐겨찾는 레큐북 조회")
public ResponseEntity<ApiResponse<List<FavoriteBookResponseDto>>> getFavorite(Principal principal);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.sopt.lequuServer.domain.favorite.controller;

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.response.FavoriteBookResponseDto;
import org.sopt.lequuServer.domain.favorite.facade.FavoriteFacade;
import org.sopt.lequuServer.global.auth.jwt.JwtProvider;
import org.sopt.lequuServer.global.common.dto.ApiResponse;
import org.sopt.lequuServer.global.exception.enums.SuccessType;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.List;

@SecurityRequirement(name = "JWT Authorization")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/favorite")
public class FavoriteController implements FavoriteApi {

private final FavoriteFacade favoriteFacade;

@PostMapping
public ResponseEntity<ApiResponse<?>> createFavorite(Principal principal, @RequestBody FavoriteCreateRequestDto request) {
favoriteFacade.createFavorite(JwtProvider.getUserFromPrincial(principal), request);
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(SuccessType.CREATE_FAVORITE_SUCCESS));
}

@GetMapping
public ResponseEntity<ApiResponse<List<FavoriteBookResponseDto>>> getFavorite(Principal principal) {
return ResponseEntity.ok(ApiResponse.success(SuccessType.GET_FAVORITE_SUCCESS, favoriteFacade.getFavorite(JwtProvider.getUserFromPrincial(principal))));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.sopt.lequuServer.domain.favorite.dto.request;

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

public record FavoriteCreateRequestDto(
@Schema(example = "1")
Long bookId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.sopt.lequuServer.domain.favorite.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import org.sopt.lequuServer.domain.book.model.Book;

public record FavoriteBookResponseDto(

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

@Schema(description = "레큐북 UUID", example = "ee4f66f9-9cf4-4b28-90f4-f71d0ecba021")
String bookUuid,

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

@Schema(description = "최애 사진", example = "https://dzfv99wxq6tx0.cloudfront.net/books/favorite_image/b4006561-382b-479e-ae1d-e841922e883f.jpg")
String favoriteImage
) {
public static FavoriteBookResponseDto of(Book book) {
return new FavoriteBookResponseDto(book.getId(), book.getUuid(), book.getFavoriteName(),
book.getFavoriteImage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.sopt.lequuServer.domain.favorite.facade;

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.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.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

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

private final FavoriteRepository favoriteRepository;
private final MemberRepository memberRepository;
private final BookRepository bookRepository;

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

Favorite favorite = Favorite.of(member, book);
favoriteRepository.save(favorite);
} // memberId와 bookId를 favorite 에 저장하는 로직

public List<FavoriteBookResponseDto> getFavorite(Long memberId) {
Member member = memberRepository.findByIdOrThrow(memberId);
List<Favorite> favorites = favoriteRepository.findByMemberOrderByCreatedAtDesc(member);

return favorites.stream()
.limit(3) // 최신순 3개만 가져오기
.map(favorite -> FavoriteBookResponseDto.of(favorite.getBook()))
.collect(Collectors.toList());
} // memberId를 이용해 그 멤버가 즐겨찾기 해놓은 레큐북 목록들을 반환하는 로직
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.sopt.lequuServer.domain.favorite.model;

import jakarta.persistence.*;
import lombok.*;
import org.sopt.lequuServer.domain.book.model.Book;
import org.sopt.lequuServer.domain.member.model.Member;
import org.sopt.lequuServer.global.common.model.BaseTimeEntity;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "favorite")
public class Favorite extends BaseTimeEntity {

@Id
@Column(name = "favorite_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "book_id")
private Book book;

@Builder
public Favorite(Member member, Book book) {
this.member = member;
this.book = book;
}

public static Favorite of(Member member, Book book) {
Favorite favorite = new Favorite(member, book);
book.addFavorite(favorite);
member.addFavorite(favorite);
return favorite;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.sopt.lequuServer.domain.favorite.repository;

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;

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

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.persistence.*;
import lombok.*;
import org.sopt.lequuServer.domain.favorite.model.Favorite;
import org.sopt.lequuServer.domain.note.model.Note;
import org.sopt.lequuServer.domain.book.model.Book;
import org.sopt.lequuServer.domain.sticker.model.PostedSticker;
Expand Down Expand Up @@ -73,6 +74,13 @@ public void addPostedSticker(PostedSticker postedSticker) {
postedStickers.add(postedSticker);
}

@OneToMany(mappedBy = "member")
private final List<Favorite> favorites = new ArrayList<>();

public void addFavorite(Favorite favorite) {
favorites.add(favorite);
}

/**
* 유저가 최초로 생성될 때 필요한 최소 정보
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ public enum SuccessType {
GET_MYPAGE_BOOK_SUCCESS(HttpStatus.OK, "마이페이지의 유저 닉네임과 내 레큐북 조회에 성공했습니다."),
GET_MYPAGE_NOTE_SUCCESS(HttpStatus.OK, "마이페이지의 유저 닉네임과 내 레큐노트 조회에 성공했습니다."),
GET_BOOK_DETAIL_SUCCESS(HttpStatus.OK, "레큐북 상세 조회에 성공했습니다"),
GET_FAVORITE_SUCCESS(HttpStatus.OK, "즐겨찾는 레큐북 조회에 성공했습니다."),

/**
* 201 CREATED
*/
CREATE_BOOK_SUCCESS(HttpStatus.CREATED, "레큐북이 성공적으로 생성됐습니다."),
POST_STICKER_SUCCESS(HttpStatus.CREATED, "스티커 부착에 성공했습니다."),
CREATE_NOTE_SUCCESS(HttpStatus.CREATED, "레큐노트를 성공적으로 생성했습니다.")
CREATE_NOTE_SUCCESS(HttpStatus.CREATED, "레큐노트를 성공적으로 생성했습니다."),
CREATE_FAVORITE_SUCCESS(HttpStatus.CREATED, "즐겨찾기 레큐북 등록을 성공했습니다."),

/**
* 204 NO CONTENT
Expand Down
Loading