Skip to content

Commit

Permalink
Merge pull request #75 from Team-Lecue/feat/#74-home_favorites_api
Browse files Browse the repository at this point in the history
[FEAT] 홈 화면 즐겨찾기 API 구현
  • Loading branch information
eeddiinn authored Mar 13, 2024
2 parents 3e7c917 + 0b545cb commit 61d9b94
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 8 deletions.
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 @@ -56,6 +57,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) {
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

0 comments on commit 61d9b94

Please sign in to comment.