Skip to content

Commit

Permalink
Merge pull request #2 from JECT-Study/J01-2-be-북마크-작업
Browse files Browse the repository at this point in the history
J01 2 be 북마크 작업
  • Loading branch information
SSUHYUNKIM authored Jan 3, 2025
2 parents cdacc40 + 29f2edc commit 191aae1
Show file tree
Hide file tree
Showing 19 changed files with 639 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package ject.componote.domain.bookmark.api;

import jakarta.validation.Valid;
import ject.componote.domain.auth.model.AuthPrincipal;
import ject.componote.domain.auth.model.Authenticated;
import ject.componote.domain.bookmark.application.BookmarkService;
import ject.componote.domain.bookmark.dto.request.BookmarkRequest;
import ject.componote.domain.bookmark.dto.response.BookmarkResponse;
import ject.componote.domain.common.dto.response.PageResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RequestMapping("/bookmarks")
@RequiredArgsConstructor
@RestController
public class BookmarkController {

private final BookmarkService bookmarkService;

@PostMapping
public ResponseEntity<BookmarkResponse> addBookmark(
@Authenticated AuthPrincipal authPrincipal,
@Valid @RequestBody BookmarkRequest bookmarkRequest) {
return ResponseEntity.ok(bookmarkService.addBookmark(authPrincipal, bookmarkRequest));
}

@GetMapping
public ResponseEntity<PageResponse<BookmarkResponse>> getBookmark(
@Authenticated final AuthPrincipal authPrincipal,
@PageableDefault(size = 10, page = 0) Pageable pageable,
@RequestParam(required = false, defaultValue = "component") String type,
@RequestParam(required = false, defaultValue = "createdAt") String sortType) {

return ResponseEntity.ok(
bookmarkService.getBookmark(authPrincipal, pageable, type, sortType));
}

@DeleteMapping
public ResponseEntity<BookmarkResponse> deleteBookmark(@Authenticated final AuthPrincipal authPrincipal, @Valid @RequestBody BookmarkRequest bookmarkRequest) {
return ResponseEntity.ok()
.body(bookmarkService.deleteBookmark(authPrincipal, bookmarkRequest));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package ject.componote.domain.bookmark.application;

import ject.componote.domain.auth.dao.MemberRepository;
import ject.componote.domain.auth.domain.Member;
import ject.componote.domain.auth.model.AuthPrincipal;
import ject.componote.domain.bookmark.domain.Bookmark;
import ject.componote.domain.bookmark.domain.BookmarkRepository;
import ject.componote.domain.bookmark.dto.request.BookmarkRequest;
import ject.componote.domain.bookmark.dto.response.BookmarkResponse;
import ject.componote.domain.bookmark.error.ExistedBookmarkError;
import ject.componote.domain.bookmark.error.InvalidBookmarkTypeError;
import ject.componote.domain.bookmark.error.NotFoundBookmarkException;
import ject.componote.domain.bookmark.error.NotFoundComponentException;
import ject.componote.domain.bookmark.error.NotFoundDesignSystemException;
import ject.componote.domain.bookmark.error.NotFoundMemberException;
import ject.componote.domain.common.dto.response.PageResponse;
import ject.componote.domain.component.domain.Component;
import ject.componote.domain.component.domain.ComponentRepository;
import ject.componote.domain.design.domain.Design;
import ject.componote.domain.design.domain.DesignSystem;
import ject.componote.domain.design.domain.DesignSystemRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
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;

@RequiredArgsConstructor
@Service
public class BookmarkService {

private final BookmarkRepository bookmarkRepository;
private final MemberRepository memberRepository;
private final ComponentRepository componentRepository;
private final DesignSystemRepository designSystemRepository;

@Transactional
public BookmarkResponse addBookmark(AuthPrincipal authPrincipal, BookmarkRequest bookmarkRequest) {
// 북마크 중복 여부 확인
if (bookmarkRepository.existsByMemberIdAndResourceIdAndType(
authPrincipal.id(), bookmarkRequest.id(), bookmarkRequest.type())) {
throw new ExistedBookmarkError(
authPrincipal.id(), bookmarkRequest.id(), bookmarkRequest.type());
}

Member member = findMemberOrThrow(authPrincipal.id());
Bookmark bookmark;

if ("component".equals(bookmarkRequest.type())) {
Component component = findComponentOrThrow(bookmarkRequest.id());
bookmark = Bookmark.of(member, component);
bookmarkRepository.save(bookmark);
return BookmarkResponse.from(bookmark, component);
} else if ("designSystem".equals(bookmarkRequest.type())) {
Design designSystem = findDesignSystemOrThrow(bookmarkRequest.id());
bookmark = Bookmark.of(member, designSystem);
bookmarkRepository.save(bookmark);
return BookmarkResponse.from(bookmark, designSystem);
} else {
throw new InvalidBookmarkTypeError(bookmarkRequest.type());
}
}

@Transactional(readOnly = true)
public PageResponse<BookmarkResponse> getBookmark(
AuthPrincipal authPrincipal, Pageable pageable, String type, String sortType) {

Page<Bookmark> bookmarks;

Pageable sortedPageable = applySort(pageable, type, sortType);

if ("component".equals(type)) {
bookmarks = bookmarkRepository.findAllByMemberIdAndType(
authPrincipal.id(), "component", sortedPageable);
} else if ("designSystem".equals(type)) {
bookmarks = bookmarkRepository.findAllByMemberIdAndType(
authPrincipal.id(), "designSystem", sortedPageable);
} else {
throw new InvalidBookmarkTypeError(type);
}

Page<BookmarkResponse> bookmarkResponsePage = bookmarks.map(bookmark -> {
if ("component".equals(bookmark.getType())) {
Component component = findComponentOrThrow(bookmark.getResourceId());
return BookmarkResponse.from(bookmark, component);
} else {
Design designSystem = findDesignSystemOrThrow(bookmark.getResourceId());
return BookmarkResponse.from(bookmark, designSystem);
}
});

return PageResponse.from(bookmarkResponsePage);
}

@Transactional
public BookmarkResponse deleteBookmark(AuthPrincipal authPrincipal, BookmarkRequest bookmarkRequest) {
Bookmark bookmark = bookmarkRepository.findByMemberIdAndResourceIdAndType(
authPrincipal.id(), bookmarkRequest.id(), bookmarkRequest.type())
.orElseThrow(() -> new NotFoundBookmarkException(
authPrincipal.id(), bookmarkRequest.id(), bookmarkRequest.type()));

bookmarkRepository.delete(bookmark);

if ("component".equals(bookmarkRequest.type())) {
Component component = findComponentOrThrow(bookmarkRequest.id());
return BookmarkResponse.from(bookmark, component);
} else if ("designSystem".equals(bookmarkRequest.type())) {
Design designSystem = findDesignSystemOrThrow(bookmarkRequest.id());
return BookmarkResponse.from(bookmark, designSystem);
} else {
throw new InvalidBookmarkTypeError(bookmarkRequest.type());
}
}

private Member findMemberOrThrow(Long memberId) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new NotFoundMemberException(memberId));
}

private Component findComponentOrThrow(Long componentId) {
return componentRepository.findById(componentId)
.orElseThrow(() -> new NotFoundComponentException(componentId));
}

private Design findDesignSystemOrThrow(Long designSystemId) {
return designSystemRepository.findById(designSystemId)
.orElseThrow(() -> new NotFoundDesignSystemException(designSystemId));
}

private Pageable applySort(Pageable pageable, String type, String sortType) {
Sort sort;

if ("createdAt".equals(sortType)) {
sort = Sort.by(Sort.Order.desc("createdAt"));
}
else if ("name".equals(sortType) && "component".equals(type)) {
sort = Sort.by(Sort.Order.asc("summary.title"))
.and(Sort.by(Sort.Order.desc("createdAt")));
} else if ("viewCount".equals(sortType) && "component".equals(type)) {
sort = Sort.by(Sort.Order.desc("summary.viewCount"))
.and(Sort.by(Sort.Order.desc("createdAt")));
} else if ("commentCount".equals(sortType) && "component".equals(type)) {
sort = Sort.by(Sort.Order.desc("commentCount"))
.and(Sort.by(Sort.Order.desc("createdAt")));
}

else if ("name".equals(sortType) && "designSystem".equals(type)) {
sort = Sort.by(Sort.Order.asc("summary.name"))
.and(Sort.by(Sort.Order.desc("createdAt")));
} else if ("viewCount".equals(sortType) && "designSystem".equals(type)) {
sort = Sort.by(Sort.Order.desc("summary.viewCount"))
.and(Sort.by(Sort.Order.desc("createdAt")));
} else if ("recommendCount".equals(sortType) && "designSystem".equals(type)) {
sort = Sort.by(Sort.Order.desc("summary.recommendCount"))
.and(Sort.by(Sort.Order.desc("createdAt")));
} else {
sort = Sort.by(Sort.Order.desc("createdAt"));
}

return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort);
}
}
22 changes: 16 additions & 6 deletions src/main/java/ject/componote/domain/bookmark/domain/Bookmark.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import ject.componote.domain.auth.domain.Member;
import ject.componote.domain.common.domain.BaseEntity;
import ject.componote.domain.component.domain.Component;
import ject.componote.domain.design.domain.Design;
import ject.componote.domain.design.domain.DesignSystem;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -25,15 +27,23 @@ public class Bookmark extends BaseEntity {
@Column(name = "member_id", nullable = false)
private Long memberId;

@Column(name = "component_id", nullable = false)
private Long componentId;
@Column(name = "resource_id", nullable = false)
private Long resourceId;

private Bookmark(final Long memberId, final Long componentId) {
@Column(name = "type", nullable = false)
private String type;

private Bookmark(final Long memberId, final Long resourceId, final String type) {
this.memberId = memberId;
this.componentId = componentId;
this.resourceId = resourceId;
this.type = type;
}

public static Bookmark of(final Member member, final Component component) {
return new Bookmark(member.getId(), component.getId(), "component");
}

public static Bookmark of(final Member Member, final Component component) {
return new Bookmark(Member.getId(), component.getId());
public static Bookmark of(final Member member, final Design designSystem) {
return new Bookmark(member.getId(), designSystem.getId(), "designSystem");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ject.componote.domain.bookmark.domain;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;
import org.springframework.stereotype.Repository;

@Repository
public interface BookmarkRepository extends JpaRepository<Bookmark, Long> {
boolean existsByMemberIdAndResourceIdAndType(Long memberId, Long resourceId, String type);

Optional<Bookmark> findByMemberIdAndResourceIdAndType(Long memberId, Long resourceId, String type);

Page<Bookmark> findAllByMemberIdAndType(Long memberId, String type, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ject.componote.domain.bookmark.dto.request;

import com.google.firebase.database.annotations.NotNull;

public record BookmarkRequest(
@NotNull Long id,
@NotNull String type // "component" or "designSystem"
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package ject.componote.domain.bookmark.dto.response;

import ject.componote.domain.bookmark.domain.Bookmark;
import ject.componote.domain.common.model.BaseImage;
import ject.componote.domain.component.domain.Component;
import ject.componote.domain.design.domain.Design;
import ject.componote.domain.design.domain.DesignSystem;

import java.time.LocalDateTime;

public record BookmarkResponse(
Long bookmarkId,
String type,
Long resourceId,
String resourceName,
String organization,
BaseImage thumbnailUrl,
Long bookmarkCount,
Long commentCount,
LocalDateTime createdAt
) {
// Component 북마크 변환
public static BookmarkResponse from(Bookmark bookmark, Component component) {
return new BookmarkResponse(
bookmark.getId(),
"component",
component.getId(),
component.getSummary().getTitle(),
null,
component.getSummary().getThumbnail(),
component.getBookmarkCount().getValue(),
component.getCommentCount().getValue(),
bookmark.getCreatedAt()
);
}

// DesignSystem 북마크 변환
public static BookmarkResponse from(Bookmark bookmark, Design designSystem) {
return new BookmarkResponse(
bookmark.getId(),
"designSystem",
designSystem.getId(),
designSystem.getSummary().getName(),
designSystem.getSummary().getOrganization(),
designSystem.getSummary().getThumbnail(),
designSystem.getBookmarkCount().getValue(),
null,
bookmark.getCreatedAt()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
package ject.componote.domain.bookmark.error;

public class BookmarkException {
import ject.componote.global.error.ComponoteException;
import org.springframework.http.HttpStatus;

public class BookmarkException extends ComponoteException {
public BookmarkException(final String message, final HttpStatus status) {
super(message, status);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ject.componote.domain.bookmark.error;

import org.springframework.http.HttpStatus;

public class ExistedBookmarkError extends BookmarkException {
public ExistedBookmarkError(final Long memberId, final Long resourceId, final String type) {
super(String.format("해당 회원은 이미 %s를 북마크에 추가하였습니다. 소셜 ID : %d, %s ID : %d", type, memberId, type, resourceId), HttpStatus.BAD_REQUEST);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ject.componote.domain.bookmark.error;

import org.springframework.http.HttpStatus;

public class ExistedComponentError extends BookmarkException {
public ExistedComponentError(final Long memberId, final Long componentId) {
super(String.format("해당 회원은 이미 해당 컴포넌트를 북마크에 추가하였습니다. 소셜 ID : %d, 컴포넌트 ID : %d", memberId, componentId), HttpStatus.BAD_REQUEST);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ject.componote.domain.bookmark.error;

import org.springframework.http.HttpStatus;

public class InvalidBookmarkTypeError extends BookmarkException {
public InvalidBookmarkTypeError(final String type) {
super(String.format("유효하지 않은 북마크 타입입니다: %s", type), HttpStatus.BAD_REQUEST);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ject.componote.domain.bookmark.error;

import org.springframework.http.HttpStatus;

public class NotFoundBookmarkException extends BookmarkException {
public NotFoundBookmarkException(final Long memberId, final Long resourceId, final String type) {
super(String.format("해당 회원의 일치하는 북마크를 찾을 수 없습니다. 회원 ID : %d, %s ID : %d", memberId, type, resourceId), HttpStatus.NOT_FOUND);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ject.componote.domain.bookmark.error;

import org.springframework.http.HttpStatus;

public class NotFoundComponentException extends BookmarkException {
public NotFoundComponentException(final Long componentId) {
super(String.format("일치하는 컴포넌트를 찾을 수 없습니다. 컴포넌트 ID : %d", componentId), HttpStatus.NOT_FOUND);
}
}
Loading

0 comments on commit 191aae1

Please sign in to comment.