diff --git a/src/main/java/com/double_o/dambap/post/domain/Media.java b/src/main/java/com/double_o/dambap/media/Media.java similarity index 77% rename from src/main/java/com/double_o/dambap/post/domain/Media.java rename to src/main/java/com/double_o/dambap/media/Media.java index dd2c1ad..b5a8c32 100644 --- a/src/main/java/com/double_o/dambap/post/domain/Media.java +++ b/src/main/java/com/double_o/dambap/media/Media.java @@ -1,7 +1,9 @@ -package com.double_o.dambap.post.domain; +package com.double_o.dambap.media; +import com.double_o.dambap.post.domain.Type; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -24,13 +26,13 @@ public class Media { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name = "post_id") - private Long postId; + @Column(name = "target_id") + private Long targetId; @Column(name = "media_url") private String mediaUrl; -@Enumerated(EnumType.STRING) + @Enumerated(EnumType.STRING) private Type type; private int sequence; // 사용자가 이미지를 보낸 순서 diff --git a/src/main/java/com/double_o/dambap/post/infrastructure/MediaRepository.java b/src/main/java/com/double_o/dambap/media/MediaRepository.java similarity index 58% rename from src/main/java/com/double_o/dambap/post/infrastructure/MediaRepository.java rename to src/main/java/com/double_o/dambap/media/MediaRepository.java index 3a53a73..b74c73f 100644 --- a/src/main/java/com/double_o/dambap/post/infrastructure/MediaRepository.java +++ b/src/main/java/com/double_o/dambap/media/MediaRepository.java @@ -1,6 +1,5 @@ -package com.double_o.dambap.post.infrastructure; +package com.double_o.dambap.media; -import com.double_o.dambap.post.domain.Media; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -10,7 +9,11 @@ @Transactional(readOnly = true) public interface MediaRepository extends JpaRepository { - void deleteAllByPostId(Long postId); + void deleteAllByTargetId(Long postId); - List findALLByPostIdOrderBySequenceAsc(Long postId); + void deleteByTargetId(Long targetId); + + List findALLByTargetIdOrderBySequenceAsc(Long postId); + + Media findByTargetId(Long postId); } diff --git a/src/main/java/com/double_o/dambap/post/application/PostService.java b/src/main/java/com/double_o/dambap/post/application/PostService.java index af43cde..c375e57 100644 --- a/src/main/java/com/double_o/dambap/post/application/PostService.java +++ b/src/main/java/com/double_o/dambap/post/application/PostService.java @@ -1,33 +1,29 @@ package com.double_o.dambap.post.application; -import static com.double_o.dambap.post.utils.TaggedUserConstants.TAGGED_USER_MAX_SIZE; - import com.double_o.dambap.auth.model.AuthUser; import com.double_o.dambap.exception.dto.ErrorType; import com.double_o.dambap.exception.post.PostInvalidException; -import com.double_o.dambap.post.domain.PostLike; -import com.double_o.dambap.post.domain.Media; +import com.double_o.dambap.recommendation.application.RecommendService; +import com.double_o.dambap.media.Media; +import com.double_o.dambap.post.domain.Post; import com.double_o.dambap.post.domain.TaggedUser; import com.double_o.dambap.post.domain.Type; +import com.double_o.dambap.post.dto.request.PostRequest; import com.double_o.dambap.post.dto.response.PostInfoResponse; -import com.double_o.dambap.post.dto.response.PostLikeResponse; import com.double_o.dambap.post.dto.response.PostPageResponse; -import com.double_o.dambap.post.infrastructure.PostLikeRepository; -import com.double_o.dambap.post.domain.Post; -import com.double_o.dambap.post.dto.request.PostRequest; import com.double_o.dambap.post.dto.response.PostResponse; -import com.double_o.dambap.post.infrastructure.MediaRepository; import com.double_o.dambap.post.infrastructure.PostRepository; -import com.double_o.dambap.auth.service.AuthValidationUtils; import com.double_o.dambap.post.infrastructure.TaggedUserRepository; +import com.double_o.dambap.recommendation.dto.response.RecommendResponse; +import com.double_o.dambap.media.MediaRepository; +import com.double_o.dambap.auth.service.AuthValidationUtils; +import com.double_o.dambap.post.utils.TaggedUserConstants; import com.double_o.dambap.user.domain.User; import com.double_o.dambap.user.application.UserValidationService; import com.double_o.dambap.common.dto.SuccessResponse; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import java.time.LocalDate; import java.util.List; -import java.util.Optional; import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -41,9 +37,9 @@ public class PostService { private final UserValidationService userValidationService; private final PostRepository postRepository; - private final PostLikeRepository postLikeRepository; private final TaggedUserRepository taggedUserRepository; private final MediaRepository mediaRepository; + private final RecommendService recommendService; /** * 게시글 등록 @@ -59,7 +55,7 @@ public PostResponse createPost(AuthUser user, PostRequest request) { .expireDays(request.getExpireDays()) .content(request.getContent()) .isPublic(request.isPublic()) - .likeCnt(0) + .recommendationCnt(0) .writerId(findUser.getId()) .build(); @@ -129,18 +125,25 @@ public SuccessResponse deletePost(AuthUser user, Long postId) { * 게시글 좋아요 */ @Transactional - public PostLikeResponse updatePostLike(AuthUser user, Long postId) { + public RecommendResponse updatePostRecommendStatus(AuthUser user, Long postId) { User findUser = userValidationService.getUserOrThrowIfNotExist(user.getId()); Post findPost = getPostOrThrowIfNotExist(postId); - Optional postLike = postLikeRepository.findByPostIdAndLikerId( - findPost.getId(), findUser.getId()); + recommendService.updateRecommendStatus(findPost, findUser, Type.POST); - updateLikeStatus(postLike, findPost, findUser); + return RecommendResponse.toResponse(findPost.getId(), findPost.getRecommendationCnt()); + } - return PostLikeResponse.toResponse(findPost.getId(), findPost.getLikeCnt()); + /** + * 게시글 추천 수 조회 + */ + public RecommendResponse getRecommendationCnt(Long postId) { + + Post findPost = getPostOrThrowIfNotExist(postId); + + return RecommendResponse.toResponse(findPost.getId(), findPost.getRecommendationCnt()); } /** @@ -149,27 +152,30 @@ public PostLikeResponse updatePostLike(AuthUser user, Long postId) { @Transactional public SuccessResponse changePublicity(AuthUser user, Long postId) { - userValidationService.getUserOrThrowIfNotExist(user.getId()); - Post findPost = getPostOrThrowIfNotExist(postId); + User findUser = userValidationService.getUserOrThrowIfNotExist(user.getId()); + + AuthValidationUtils.verifySameUser(findUser.getId(), findPost.getWriterId()); + findPost.changePublicity(); + return new SuccessResponse("공개여부가 성공적으로 전환되었습니다."); } /** * 내가 나눈 음식 게시글 목록 최신순으로 반환 */ - public PostPageResponse getAllMySharedPost(AuthUser user, Pageable pageable) { + public PostPageResponse getAllMyPost(AuthUser user, Pageable pageable) { User findUser = userValidationService.getUserOrThrowIfNotExist(user.getId()); - Page findAllMySharedPost = postRepository.findAllByWriterIdOrderByCreatedAtDesc( + Page findAllMyPost = postRepository.findAllByWriterIdOrderByCreatedAtDesc( findUser.getId(), pageable); return PostPageResponse.toResponse( pageable.getPageNumber(), - findAllMySharedPost.map(this::convertToPostInfoResponse).toList()); + findAllMyPost.map(this::convertToPostInfoResponse).toList()); } /** @@ -177,6 +183,7 @@ public PostPageResponse getAllMySharedPost(AuthUser user, Pageable pageable) { */ @Transactional(readOnly = true) public PostPageResponse getAllLatestPost(Pageable pageable) { + Page findAllPosts = postRepository.findAllByOrderByCreatedAtDesc( pageable); @@ -189,6 +196,7 @@ public PostPageResponse getAllLatestPost(Pageable pageable) { // 게시글 응답 형변환 private PostResponse getPostResponse(Post post, List mediaUrls, List taggedUserIds) { + return PostResponse.toResponse( post.getId(), post.getCreatedAt(), @@ -200,7 +208,7 @@ private PostResponse getPostResponse(Post post, List mediaUrls, mediaUrls, taggedUserIds, post.isPublic(), - post.getLikeCnt(), + post.getRecommendationCnt(), post.getWriterId() ); } @@ -209,14 +217,14 @@ private PostResponse getPostResponse(Post post, List mediaUrls, private List saveAndGetMediaUrls(PostRequest request, Post targetPost) { // 기존 등록된 게시글 관련 미디어 전부 삭제 - mediaRepository.deleteAllByPostId(targetPost.getId()); + mediaRepository.deleteAllByTargetId(targetPost.getId()); // request 에서 받아온 미디어 목록에 시퀀스 반영(오름차순), 순서 보장하여 저장, 리스트로 가공하여 반환 List medias = IntStream.range(0, request.getMediaUrls().size()) .mapToObj(i -> Media.builder() - .postId(targetPost.getId()) + .targetId(targetPost.getId()) .mediaUrl(request.getMediaUrls().get(i)) - .type(Type.SHARED) + .type(Type.POST) .sequence(i) // 순서 정보 부여 .build()) .toList(); @@ -230,7 +238,7 @@ private List saveAndGetMediaUrls(PostRequest request, Post targetPost) { private List saveAndGetTaggedUserIds(PostRequest request, Post targetPost) { // 사용자 태그를 20명으로 제한 - if (request.getTaggedUserIds().size() > TAGGED_USER_MAX_SIZE) { + if (request.getTaggedUserIds().size() > TaggedUserConstants.TAGGED_USER_MAX_SIZE) { throw new PostInvalidException(ErrorType.TAGGED_USER_MAX_SIZE_20_ERROR); } @@ -253,42 +261,32 @@ private List saveAndGetTaggedUserIds(PostRequest request, Post targetPost) // 게시글 연관 미디어 순서 보장하여 조회 private List getMediaUrls(Post post) { - return mediaRepository.findALLByPostIdOrderBySequenceAsc(post.getId()).stream() + + return mediaRepository.findALLByTargetIdOrderBySequenceAsc(post.getId()).stream() .map(Media::getMediaUrl) .toList(); } // 게시글 연관 태그 순서 보장하여 조회 private List getTaggedUserIds(Post post) { + return taggedUserRepository.findAllByPostIdOrderBySequenceAsc(post.getId()).stream() .map(TaggedUser::getTaggedUserId) .toList(); } - // 기존 추천한 이력 유무에 따른 추천수 증감 - private void updateLikeStatus(Optional postLike, Post findPost, User findUser) { - if (postLike.isEmpty()) { - postLikeRepository.save(PostLike.builder() - .likedAt(LocalDate.now()) - .postId(findPost.getId()) - .likerId(findUser.getId()) - .build()); - findPost.increaseRecommendationCnt(); - } else { - postLikeRepository.deleteById(postLike.get().getId()); - findPost.decreaseRecommendationCnt(); - } - } - // 게시글 정보 응답 dto 로 변환 private PostInfoResponse convertToPostInfoResponse(Post post) { - Media thumbnailMedia = mediaRepository.findALLByPostIdOrderBySequenceAsc(post.getId()).get(0); + + Media thumbnailMedia = mediaRepository.findALLByTargetIdOrderBySequenceAsc(post.getId()) + .get(0); return PostInfoResponse.toResponse(post.getId(), post.getContent(), thumbnailMedia.getMediaUrl()); } // 게시글 반환, 없으면 예외처리 public Post getPostOrThrowIfNotExist(Long postId) { + return postRepository.findById(postId).orElseThrow( () -> new PostInvalidException(ErrorType.POST_NOT_FOUND_ERROR) ); diff --git a/src/main/java/com/double_o/dambap/post/domain/Post.java b/src/main/java/com/double_o/dambap/post/domain/Post.java index d7d5d0b..4caf57d 100644 --- a/src/main/java/com/double_o/dambap/post/domain/Post.java +++ b/src/main/java/com/double_o/dambap/post/domain/Post.java @@ -3,6 +3,7 @@ import com.double_o.dambap.common.entity.BaseEntity; import com.double_o.dambap.exception.dto.ErrorType; import com.double_o.dambap.exception.post.PostInvalidException; +import com.double_o.dambap.recommendation.common.Recommendable; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -21,7 +22,7 @@ @AllArgsConstructor @Getter @Builder -public class Post extends BaseEntity { +public class Post extends BaseEntity implements Recommendable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -38,8 +39,8 @@ public class Post extends BaseEntity { private boolean isPublic; // cnt 정보 - @Column(name = "like_cnt") - private int likeCnt; + @Column(name = "recommendation_cnt", nullable = false) + private int recommendationCnt = 0; // 참조 정보 @Column(name = "writer_id") @@ -58,13 +59,15 @@ public void updatePost( this.isPublic = isPublic; } + @Override public void increaseRecommendationCnt() { - this.likeCnt++; + this.recommendationCnt++; } + @Override public void decreaseRecommendationCnt() { - if (this.likeCnt > 0) { - this.likeCnt--; + if (this.recommendationCnt > 0) { + this.recommendationCnt--; } else { throw new PostInvalidException(ErrorType.CNT_NEGATIVE_ERROR); } diff --git a/src/main/java/com/double_o/dambap/post/domain/PostLike.java b/src/main/java/com/double_o/dambap/post/domain/PostLike.java deleted file mode 100644 index 73986b3..0000000 --- a/src/main/java/com/double_o/dambap/post/domain/PostLike.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.double_o.dambap.post.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Index; -import jakarta.persistence.Table; -import java.time.LocalDate; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -//@Table(name = "post_like") -// 조회 성능을 위한 JPA 이용 인덱스 추가 -@Table( - name = "post_like", - indexes = { - @Index(name = "idx_postlike_postid", columnList = "post_id"), - @Index(name = "idx_postlike_likerid", columnList = "liker_id"), - @Index(name = "uq_postlike_postid_likerid", columnList = "post_id, liker_id", unique = true) - } -) -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Builder -public class PostLike { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "liked_at") - private LocalDate likedAt; - - @Column(name = "post_id") - private Long postId; - - @Column(name = "liker_id") - private Long likerId; -} diff --git a/src/main/java/com/double_o/dambap/post/domain/Type.java b/src/main/java/com/double_o/dambap/post/domain/Type.java index 82cc2d9..1842ed1 100644 --- a/src/main/java/com/double_o/dambap/post/domain/Type.java +++ b/src/main/java/com/double_o/dambap/post/domain/Type.java @@ -1,5 +1,5 @@ package com.double_o.dambap.post.domain; public enum Type { - SHARED, RECEIVED + POST, REVIEW } diff --git a/src/main/java/com/double_o/dambap/post/dto/response/PostLikeResponse.java b/src/main/java/com/double_o/dambap/post/dto/response/PostLikeResponse.java deleted file mode 100644 index 52ea209..0000000 --- a/src/main/java/com/double_o/dambap/post/dto/response/PostLikeResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.double_o.dambap.post.dto.response; - -import lombok.AllArgsConstructor; -import lombok.Data; - -@Data -@AllArgsConstructor -public class PostLikeResponse { - - private Long postId; - private int likeCnt; - - public static PostLikeResponse toResponse( - Long postId, int likeCnt - ) { - return new PostLikeResponse(postId, likeCnt); - } -} diff --git a/src/main/java/com/double_o/dambap/post/dto/response/PostResponse.java b/src/main/java/com/double_o/dambap/post/dto/response/PostResponse.java index 9c9965f..bedd661 100644 --- a/src/main/java/com/double_o/dambap/post/dto/response/PostResponse.java +++ b/src/main/java/com/double_o/dambap/post/dto/response/PostResponse.java @@ -24,7 +24,7 @@ public class PostResponse { private List mediaUrls; private List taggedUserIds; private boolean isPublic; - private int likeCnt; + private int recommendationCnt; private Long writerId; public static PostResponse toResponse( @@ -38,10 +38,10 @@ public static PostResponse toResponse( List mediaUrls, List taggedUserIds, boolean isPublic, - int likeCnt, + int recommendationCnt, Long writerId ) { return new PostResponse(id, createdAt, modifiedAt, category, manufactureDate, expireDays, - content, mediaUrls, taggedUserIds, isPublic, likeCnt, writerId); + content, mediaUrls, taggedUserIds, isPublic, recommendationCnt, writerId); } } diff --git a/src/main/java/com/double_o/dambap/post/infrastructure/PostLikeRepository.java b/src/main/java/com/double_o/dambap/post/infrastructure/PostLikeRepository.java deleted file mode 100644 index b9ca931..0000000 --- a/src/main/java/com/double_o/dambap/post/infrastructure/PostLikeRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.double_o.dambap.post.infrastructure; - -import com.double_o.dambap.post.domain.PostLike; -import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; - -@Repository -@Transactional(readOnly = true) -public interface PostLikeRepository extends JpaRepository { - - Optional findByPostIdAndLikerId(Long postId, Long likerId); -} diff --git a/src/main/java/com/double_o/dambap/post/presentation/PostController.java b/src/main/java/com/double_o/dambap/post/presentation/PostController.java index 81d65e9..00e5cd7 100644 --- a/src/main/java/com/double_o/dambap/post/presentation/PostController.java +++ b/src/main/java/com/double_o/dambap/post/presentation/PostController.java @@ -2,8 +2,8 @@ import com.double_o.dambap.auth.model.AuthUser; import com.double_o.dambap.common.model.ResponseDto; -import com.double_o.dambap.post.application.PostService; import com.double_o.dambap.post.dto.request.PostRequest; +import com.double_o.dambap.post.application.PostService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import jakarta.validation.Valid; @@ -22,7 +22,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping(path = "/api/v1/posts/shared") +@RequestMapping(path = "/api/v1/posts") @RequiredArgsConstructor public class PostController { @@ -30,7 +30,7 @@ public class PostController { @Operation(summary = "게시글 생성") @PostMapping - public ResponseEntity createQuestion( + public ResponseEntity createSharePost( AuthUser user, @Parameter(required = true, description = "게시글 생성 요청") @RequestBody @Valid PostRequest request) { @@ -40,7 +40,7 @@ public ResponseEntity createQuestion( @Operation(summary = "게시글 조회") @GetMapping(path = "/{postId}") - public ResponseEntity readQuestion( + public ResponseEntity readSharePost( @Parameter(description = "게시글 고유 번호") @PathVariable("postId") Long postId) { var response = postService.getPost(postId); @@ -49,7 +49,7 @@ public ResponseEntity readQuestion( @Operation(summary = "게시글 수정") @PutMapping(path = "/{postId}") - public ResponseEntity updateQuestion( + public ResponseEntity updateSharePost( AuthUser user, @Parameter(description = "게시글 고유 번호") @PathVariable("postId") Long postId, @@ -61,7 +61,7 @@ public ResponseEntity updateQuestion( @Operation(summary = "게시글 삭제") @DeleteMapping(path = "/{postId}") - public ResponseEntity deleteQuestion( + public ResponseEntity deleteSharePost( AuthUser user, @Parameter(description = "게시글 고유 번호") @PathVariable("postId") Long postId @@ -72,12 +72,21 @@ public ResponseEntity deleteQuestion( @Operation(summary = "게시글 추천") @PostMapping(path = "/{postId}/recommendation") - public ResponseEntity recommendQuestion( + public ResponseEntity recommendSharePost( AuthUser user, @Parameter(description = "게시글 고유 번호") @PathVariable("postId") Long postId ) { - var response = postService.updatePostLike(user, postId); + var response = postService.updatePostRecommendStatus(user, postId); + return ResponseDto.ok(response); + } + + @Operation(summary = "게시글 추천 수 조회") + @GetMapping(path = "/{postId}/recommendation") + public ResponseEntity getRecommendCnt( + @Parameter(description = "게시글 고유 번호") + @PathVariable("postId") Long postId) { + var response = postService.getRecommendationCnt(postId); return ResponseDto.ok(response); } @@ -93,18 +102,18 @@ public ResponseEntity changePublicity( } @Operation(summary = "내가 나눈 음식 게시글 목록 조회") - @GetMapping(path = "/all-my-shared-post") - public ResponseEntity getAllMySharedPost( + @GetMapping(path = "/all-my-posts") + public ResponseEntity getAllMySharePost( AuthUser user, @Parameter(description = "한 페이지의 데이터 개수") @PageableDefault(size = 12) Pageable pageable ) { - var response = postService.getAllMySharedPost(user, pageable); + var response = postService.getAllMyPost(user, pageable); return ResponseDto.ok(response); } @Operation(summary = "전체 나눈 음식 게시글 목록 조회") - @GetMapping(path = "/all-latest-post") + @GetMapping(path = "/all-latest-posts") public ResponseEntity getAllLatestPost( @Parameter(description = "한 페이지의 데이터 개수") @PageableDefault(size = 12) Pageable pageable diff --git a/src/main/java/com/double_o/dambap/recommendation/application/RecommendService.java b/src/main/java/com/double_o/dambap/recommendation/application/RecommendService.java new file mode 100644 index 0000000..47cf73e --- /dev/null +++ b/src/main/java/com/double_o/dambap/recommendation/application/RecommendService.java @@ -0,0 +1,39 @@ +package com.double_o.dambap.recommendation.application; + +import com.double_o.dambap.recommendation.common.Recommendable; +import com.double_o.dambap.recommendation.domain.Recommendation; +import com.double_o.dambap.recommendation.infrastructure.RecommendRepository; +import com.double_o.dambap.post.domain.Type; +import com.double_o.dambap.user.domain.User; +import java.time.LocalDate; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RecommendService { + + private final RecommendRepository recommendRepository; + + // 기존 추천한 이력 유무에 따른 추천수 증감 + public void updateRecommendStatus(T target, User findUser, Type type) { + + Optional recommendation = recommendRepository.findByTargetIdAndRecommenderIdAndType( + target.getId(), findUser.getId(), type); + + if (recommendation.isPresent()) { + recommendRepository.deleteById(recommendation.get().getId()); + target.decreaseRecommendationCnt(); + } else { + recommendRepository.save(Recommendation.builder() + .recommendedAt(LocalDate.now()) + .targetId(target.getId()) + .recommenderId(findUser.getId()) + .type(type) + .build()); + target.increaseRecommendationCnt(); + } + } + +} diff --git a/src/main/java/com/double_o/dambap/recommendation/common/Recommendable.java b/src/main/java/com/double_o/dambap/recommendation/common/Recommendable.java new file mode 100644 index 0000000..e381d8c --- /dev/null +++ b/src/main/java/com/double_o/dambap/recommendation/common/Recommendable.java @@ -0,0 +1,10 @@ +package com.double_o.dambap.recommendation.common; + +public interface Recommendable { + + Long getId(); + + void increaseRecommendationCnt(); + + void decreaseRecommendationCnt(); +} diff --git a/src/main/java/com/double_o/dambap/recommendation/domain/Recommendation.java b/src/main/java/com/double_o/dambap/recommendation/domain/Recommendation.java new file mode 100644 index 0000000..99f9967 --- /dev/null +++ b/src/main/java/com/double_o/dambap/recommendation/domain/Recommendation.java @@ -0,0 +1,50 @@ +package com.double_o.dambap.recommendation.domain; + +import com.double_o.dambap.post.domain.Type; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.Table; +import java.time.LocalDate; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +// 조회 성능을 위한 JPA 이용 인덱스 추가 +@Table( + name = "recommendation", + indexes = { + @Index(name = "idx_recommendation_targetid", columnList = "target_id"), + @Index(name = "idx_recommendation_recommenderid", columnList = "recommender_id"), + @Index(name = "uq_recommendation_targetid_recommenderid_type", columnList = "target_id, recommender_id, type", unique = true) + } +) +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Builder +public class Recommendation { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "recommended_at") + private LocalDate recommendedAt; + + @Column(name = "target_id") + private Long targetId; + + @Column(name = "recommender_id") + private Long recommenderId; + + @Enumerated(EnumType.STRING) + private Type type; +} diff --git a/src/main/java/com/double_o/dambap/recommendation/dto/response/RecommendResponse.java b/src/main/java/com/double_o/dambap/recommendation/dto/response/RecommendResponse.java new file mode 100644 index 0000000..e05d442 --- /dev/null +++ b/src/main/java/com/double_o/dambap/recommendation/dto/response/RecommendResponse.java @@ -0,0 +1,18 @@ +package com.double_o.dambap.recommendation.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class RecommendResponse { + + private Long targetId; + private int recommendationCnt; + + public static RecommendResponse toResponse( + Long targetId, int recommendationCnt + ) { + return new RecommendResponse(targetId, recommendationCnt); + } +} diff --git a/src/main/java/com/double_o/dambap/recommendation/infrastructure/RecommendRepository.java b/src/main/java/com/double_o/dambap/recommendation/infrastructure/RecommendRepository.java new file mode 100644 index 0000000..e25c706 --- /dev/null +++ b/src/main/java/com/double_o/dambap/recommendation/infrastructure/RecommendRepository.java @@ -0,0 +1,15 @@ +package com.double_o.dambap.recommendation.infrastructure; + +import com.double_o.dambap.post.domain.Type; +import com.double_o.dambap.recommendation.domain.Recommendation; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +@Repository +@Transactional(readOnly = true) +public interface RecommendRepository extends JpaRepository { + + Optional findByTargetIdAndRecommenderIdAndType(Long targetId, Long recommenderId, Type type); +}