diff --git a/src/main/java/com/example/sharemind/comment/application/CommentServiceImpl.java b/src/main/java/com/example/sharemind/comment/application/CommentServiceImpl.java index 3e18124a..155b4a5e 100644 --- a/src/main/java/com/example/sharemind/comment/application/CommentServiceImpl.java +++ b/src/main/java/com/example/sharemind/comment/application/CommentServiceImpl.java @@ -12,7 +12,6 @@ import com.example.sharemind.customer.application.CustomerService; import com.example.sharemind.customer.domain.Customer; import com.example.sharemind.post.application.PostService; -import com.example.sharemind.post.content.PostStatus; import com.example.sharemind.post.domain.Post; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -24,8 +23,6 @@ @Transactional(readOnly = true) @RequiredArgsConstructor public class CommentServiceImpl implements CommentService { - - private static final Integer MAX_COMMENTS = 5; private static final Boolean COMMENT_IS_NOT_LIKED = false; private final PostService postService; @@ -82,11 +79,7 @@ public void createComment(CommentCreateRequest commentCreateRequest, Long custom } commentRepository.save(commentCreateRequest.toEntity(post, counselor)); - - List comments = commentRepository.findByPostAndIsActivatedIsTrue(post); - if (comments.size() == MAX_COMMENTS) { - post.updatePostStatus(PostStatus.COMPLETED); - } + post.increaseTotalComment(); } @Override diff --git a/src/main/java/com/example/sharemind/comment/repository/CommentCustomRepository.java b/src/main/java/com/example/sharemind/comment/repository/CommentCustomRepository.java new file mode 100644 index 00000000..215005d7 --- /dev/null +++ b/src/main/java/com/example/sharemind/comment/repository/CommentCustomRepository.java @@ -0,0 +1,11 @@ +package com.example.sharemind.comment.repository; + +import com.example.sharemind.comment.domain.Comment; +import com.example.sharemind.counselor.domain.Counselor; + +import java.util.List; + +public interface CommentCustomRepository { + + List findAllByCounselorAndIsActivatedIsTrue(Counselor counselor, Boolean filter, Long postId, int size); +} diff --git a/src/main/java/com/example/sharemind/comment/repository/CommentCustomRepositoryImpl.java b/src/main/java/com/example/sharemind/comment/repository/CommentCustomRepositoryImpl.java new file mode 100644 index 00000000..3fea321a --- /dev/null +++ b/src/main/java/com/example/sharemind/comment/repository/CommentCustomRepositoryImpl.java @@ -0,0 +1,53 @@ +package com.example.sharemind.comment.repository; + +import com.example.sharemind.comment.domain.Comment; +import com.example.sharemind.comment.domain.QComment; +import com.example.sharemind.counselor.domain.Counselor; +import com.example.sharemind.post.content.PostStatus; +import com.example.sharemind.post.domain.QPost; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +@RequiredArgsConstructor +public class CommentCustomRepositoryImpl implements CommentCustomRepository { + + private final JPAQueryFactory jpaQueryFactory; + private final QComment comment = QComment.comment; + private final QPost post = comment.post; + + @Override + public List findAllByCounselorAndIsActivatedIsTrue(Counselor counselor, Boolean filter, Long postId, int size) { + return jpaQueryFactory + .selectFrom(comment) + .where( + commentByCounselor(counselor), + filterCondition(filter), + postIdCondition(postId) + ) + .orderBy(post.postId.desc()) + .limit(size) + .fetch(); + } + + private BooleanExpression commentByCounselor(Counselor counselor) { + return comment.isActivated.isTrue() + .and(QComment.comment.counselor.eq(counselor)); + } + + private BooleanExpression filterCondition(Boolean filter) { + if (Boolean.FALSE.equals(filter)) { + return QPost.post.postStatus.eq(PostStatus.PROCEEDING); + } + return null; + } + + private BooleanExpression postIdCondition(Long postId) { + if (postId != null && postId != 0) { + return QPost.post.postId.lt(postId); + } + return null; + } +} diff --git a/src/main/java/com/example/sharemind/comment/repository/CommentRepository.java b/src/main/java/com/example/sharemind/comment/repository/CommentRepository.java index 8f63acfe..52c2337d 100644 --- a/src/main/java/com/example/sharemind/comment/repository/CommentRepository.java +++ b/src/main/java/com/example/sharemind/comment/repository/CommentRepository.java @@ -10,7 +10,7 @@ import java.util.List; @Repository -public interface CommentRepository extends JpaRepository { +public interface CommentRepository extends JpaRepository, CommentCustomRepository { List findByPostAndIsActivatedIsTrue(Post post); diff --git a/src/main/java/com/example/sharemind/global/constants/Constants.java b/src/main/java/com/example/sharemind/global/constants/Constants.java index 77afd9f9..14743c3e 100644 --- a/src/main/java/com/example/sharemind/global/constants/Constants.java +++ b/src/main/java/com/example/sharemind/global/constants/Constants.java @@ -18,4 +18,6 @@ public class Constants { public static final Boolean IS_LETTER = false; public static final Long FEE = 1000L; + + public static final Long MAX_COMMENTS = 5L; } diff --git a/src/main/java/com/example/sharemind/post/application/PostService.java b/src/main/java/com/example/sharemind/post/application/PostService.java index 1206011c..c67ae483 100644 --- a/src/main/java/com/example/sharemind/post/application/PostService.java +++ b/src/main/java/com/example/sharemind/post/application/PostService.java @@ -3,10 +3,8 @@ import com.example.sharemind.post.domain.Post; import com.example.sharemind.post.dto.request.PostCreateRequest; import com.example.sharemind.post.dto.request.PostUpdateRequest; -import com.example.sharemind.post.dto.response.PostGetIsSavedResponse; -import com.example.sharemind.post.dto.response.PostGetListResponse; -import com.example.sharemind.post.dto.response.PostGetPopularityResponse; -import com.example.sharemind.post.dto.response.PostGetResponse; +import com.example.sharemind.post.dto.response.*; + import java.time.LocalDateTime; import java.util.List; @@ -26,6 +24,8 @@ public interface PostService { List getPostsByCustomer(Boolean filter, Long postId, Long customerId); + List getPostsByCounselor(Boolean filter, Long postId, Long customerId); + List getPublicPostsByCustomer(Long postId, LocalDateTime finishedAt, Long customerId); diff --git a/src/main/java/com/example/sharemind/post/application/PostServiceImpl.java b/src/main/java/com/example/sharemind/post/application/PostServiceImpl.java index c6e2d419..4fb10e02 100644 --- a/src/main/java/com/example/sharemind/post/application/PostServiceImpl.java +++ b/src/main/java/com/example/sharemind/post/application/PostServiceImpl.java @@ -10,16 +10,15 @@ import com.example.sharemind.post.domain.Post; import com.example.sharemind.post.dto.request.PostCreateRequest; import com.example.sharemind.post.dto.request.PostUpdateRequest; -import com.example.sharemind.post.dto.response.PostGetIsSavedResponse; -import com.example.sharemind.post.dto.response.PostGetListResponse; -import com.example.sharemind.post.dto.response.PostGetPopularityResponse; -import com.example.sharemind.post.dto.response.PostGetResponse; +import com.example.sharemind.post.dto.response.*; import com.example.sharemind.post.exception.PostErrorCode; import com.example.sharemind.post.exception.PostException; import com.example.sharemind.post.repository.PostRepository; import com.example.sharemind.postLike.repository.PostLikeRepository; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -30,8 +29,10 @@ @Transactional(readOnly = true) public class PostServiceImpl implements PostService { - private static final int POST_CUSTOMER_PAGE_SIZE = 4; + private static final int POST_PAGE_SIZE = 4; private static final int POST_POPULARITY_SIZE = 3; + private static final int TOTAL_POSTS = 50; + private static final int POSTS_AFTER_24H_COUNT = TOTAL_POSTS / 3; private static final Boolean POST_IS_NOT_LIKED = false; private final CustomerService customerService; @@ -103,7 +104,7 @@ public List getPostsByCustomer(Boolean filter, Long postId, Customer customer = customerService.getCustomerByCustomerId(customerId); return postRepository.findAllByCustomerAndIsActivatedIsTrue(customer, filter, postId, - POST_CUSTOMER_PAGE_SIZE).stream() + POST_PAGE_SIZE).stream() .map(post -> (post.getIsCompleted() != null && !post.getIsCompleted()) ? PostGetListResponse.ofIsNotCompleted(post) : PostGetListResponse.of(post, postLikeRepository.existsByPostAndCustomerAndIsActivatedIsTrue(post, @@ -111,6 +112,17 @@ public List getPostsByCustomer(Boolean filter, Long postId, .toList(); } + @Override + public List getPostsByCounselor(Boolean filter, Long postId, + Long customerId) { + + Counselor counselor = counselorService.getCounselorByCustomerId(customerId); + List comments = commentRepository.findAllByCounselorAndIsActivatedIsTrue(counselor, filter, postId, POST_PAGE_SIZE); + return comments.stream() + .map(comment -> PostGetCounselorListResponse.of(comment.getPost(), comment)) + .toList(); + } + @Override public List getPublicPostsByCustomer(Long postId, LocalDateTime finishedAt, Long customerId) { @@ -118,7 +130,7 @@ public List getPublicPostsByCustomer(Long postId, LocalDate Customer customer = customerService.getCustomerByCustomerId(customerId); return postRepository.findAllByIsPublicAndIsActivatedIsTrue(postId, finishedAt, - POST_CUSTOMER_PAGE_SIZE).stream() + POST_PAGE_SIZE).stream() .map(post -> PostGetListResponse.of(post, postLikeRepository.existsByPostAndCustomerAndIsActivatedIsTrue(post, customer))) @@ -126,7 +138,7 @@ public List getPublicPostsByCustomer(Long postId, LocalDate } return postRepository.findAllByIsPublicAndIsActivatedIsTrue(postId, finishedAt, - POST_CUSTOMER_PAGE_SIZE).stream() + POST_PAGE_SIZE).stream() .map(post -> PostGetListResponse.of(post, POST_IS_NOT_LIKED)) .toList(); } @@ -141,7 +153,25 @@ public List getPopularityPosts() { @Override public List getRandomPosts() { - return postRepository.findRandomProceedingPostIds(); + List postsAfter24h = postRepository.findRandomProceedingPostIdsAfter24Hours(); + List postsWithin24h = postRepository.findRandomProceedingPostIdsWithin24Hours(); + + List randomPosts = new ArrayList<>(TOTAL_POSTS); + + for (int i = 0; i < Math.min(POSTS_AFTER_24H_COUNT, postsAfter24h.size()); i++) { + randomPosts.add(postsAfter24h.get(i)); + } + + List remainingPosts = new ArrayList<>(postsWithin24h); + remainingPosts.addAll(postsAfter24h.subList(randomPosts.size(), postsAfter24h.size())); + Collections.shuffle(remainingPosts); + + int remainingSize = TOTAL_POSTS - randomPosts.size(); + for(int i = 0; i < Math.min(remainingSize,remainingPosts.size()); i++) { + randomPosts.add(remainingPosts.get(i)); + } + Collections.shuffle(randomPosts); + return randomPosts; } @Override diff --git a/src/main/java/com/example/sharemind/post/domain/Post.java b/src/main/java/com/example/sharemind/post/domain/Post.java index 149742d0..abac4477 100644 --- a/src/main/java/com/example/sharemind/post/domain/Post.java +++ b/src/main/java/com/example/sharemind/post/domain/Post.java @@ -23,6 +23,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import static com.example.sharemind.global.constants.Constants.MAX_COMMENTS; + @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Entity @@ -72,6 +74,9 @@ public class Post extends BaseEntity { @Column(name = "is_completed") private Boolean isCompleted; + @Column(name = "published_at") + private LocalDateTime publishedAt; + @Column(name = "finished_at") private LocalDateTime finishedAt; @@ -94,6 +99,10 @@ public void updateIsPaid() { public void updatePostStatus(PostStatus postStatus) { this.postStatus = postStatus; + if (this.postStatus == PostStatus.PROCEEDING) { + this.publishedAt = LocalDateTime.now(); + } + if (this.postStatus == PostStatus.COMPLETED) { this.finishedAt = LocalDateTime.now(); } @@ -107,6 +116,13 @@ public void decreaseTotalLike() { this.totalLike--; } + public void increaseTotalComment() { + this.totalComment++; + if (totalComment.equals(MAX_COMMENTS)) { + this.updatePostStatus(PostStatus.COMPLETED); + } + } + public void updatePost(ConsultCategory consultCategory, String title, String content, Boolean isCompleted, Customer customer) { checkUpdatability(); @@ -118,7 +134,7 @@ public void updatePost(ConsultCategory consultCategory, String title, String con this.isCompleted = isCompleted; if (isCompleted) { - this.postStatus = PostStatus.PROCEEDING; + updatePostStatus(PostStatus.PROCEEDING); } } diff --git a/src/main/java/com/example/sharemind/post/dto/response/PostGetCounselorListResponse.java b/src/main/java/com/example/sharemind/post/dto/response/PostGetCounselorListResponse.java new file mode 100644 index 00000000..954ac2ea --- /dev/null +++ b/src/main/java/com/example/sharemind/post/dto/response/PostGetCounselorListResponse.java @@ -0,0 +1,74 @@ +package com.example.sharemind.post.dto.response; + +import com.example.sharemind.comment.domain.Comment; +import com.example.sharemind.global.utils.TimeUtil; +import com.example.sharemind.post.domain.Post; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.persistence.Column; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class PostGetCounselorListResponse { + + @Schema(description = "일대다 질문 아이디") + private final Long postId; + + @Schema(description = "제목") + private final String title; + + @Schema(description = "상담 내용") + private final String content; + + @Schema(description = "상담 카테고리") + private final String consultCategory; + + @Schema(description = "공개/비공개 여부") + private final Boolean isPublic; + + @Schema(description = "좋아요 수") + private final Long totalLike; + + @Schema(description = "스크랩 수") + private final Long totalScrap; + + @Schema(description = "답변 수") + private final Long totalComment; + + @Column(name = "게시글 등록 일자") + private final String publishedAt; + + @Column(name = "셰어 채택 여부") + private final Boolean isChosen; + + @Builder + public PostGetCounselorListResponse(Long postId, String title, String content, String consultCategory, + Boolean isPublic, Long totalLike, Long totalScrap, Long totalComment, + String publishedAt, Boolean isChosen) { + this.postId = postId; + this.title = title; + this.content = content; + this.consultCategory = consultCategory; + this.isPublic = isPublic; + this.totalLike = totalLike; + this.totalScrap = totalScrap; + this.totalComment = totalComment; + this.publishedAt = publishedAt; + this.isChosen = isChosen; + } + + public static PostGetCounselorListResponse of(Post post, Comment comment) { + return PostGetCounselorListResponse.builder() + .postId(post.getPostId()) + .title(post.getTitle()) + .content(post.getContent()) + .consultCategory(post.getConsultCategory().getDisplayName()) + .isPublic(post.getIsPublic()) + .totalLike(post.getTotalLike()) + .totalScrap(post.getTotalScrap()) + .totalComment(post.getTotalComment()) + .publishedAt(TimeUtil.getUpdatedAt(post.getPublishedAt())) + .isChosen(comment.getIsChosen()) + .build(); + } +} diff --git a/src/main/java/com/example/sharemind/post/presentation/PostController.java b/src/main/java/com/example/sharemind/post/presentation/PostController.java index 623e5d60..2404b51e 100644 --- a/src/main/java/com/example/sharemind/post/presentation/PostController.java +++ b/src/main/java/com/example/sharemind/post/presentation/PostController.java @@ -5,10 +5,7 @@ import com.example.sharemind.post.application.PostService; import com.example.sharemind.post.dto.request.PostCreateRequest; import com.example.sharemind.post.dto.request.PostUpdateRequest; -import com.example.sharemind.post.dto.response.PostGetIsSavedResponse; -import com.example.sharemind.post.dto.response.PostGetListResponse; -import com.example.sharemind.post.dto.response.PostGetPopularityResponse; -import com.example.sharemind.post.dto.response.PostGetResponse; +import com.example.sharemind.post.dto.response.*; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; @@ -147,6 +144,28 @@ public ResponseEntity> getPostsByCustomer( customUserDetails.getCustomer().getCustomerId())); } + @Operation(summary = "상담사 본인 일대다 상담 리스트 조회", description = """ + - 상담사 상담 탭에서 본인이 댓글 작성한 일대다 상담 질문 리스트 조회 + - 주소 형식: /api/v1/posts/counselors?filter=true&postId=0""") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공") + }) + @Parameters({ + @Parameter(name = "filter", description = "완료/취소된 상담 제외: true, 포함: false"), + @Parameter(name = "postId", description = """ + - 조회 결과는 4개씩 반환하며, postId로 구분 + 1. 최초 조회 요청이면 postId는 0 + 2. 2번째 요청부터 postId는 직전 요청의 조회 결과 4개 중 마지막 postId""") + }) + @GetMapping("/counselors") + public ResponseEntity> getPostsByCounselor( + @RequestParam Boolean filter, + @RequestParam Long postId, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + return ResponseEntity.ok(postService.getPostsByCounselor(filter, postId, + customUserDetails.getCustomer().getCustomerId())); + } + @Operation(summary = "구매자 사이드 공개상담 탭 일대다 상담 리스트 기본순 조회", description = """ - 구매자 사이드의 공개상담 탭에서 답변 완료된 일대다 상담 질문 리스트 기본순 조회 - 로그인한 사용자일 경우 헤더에 accessToken을 넣어주세요 diff --git a/src/main/java/com/example/sharemind/post/repository/PostRepository.java b/src/main/java/com/example/sharemind/post/repository/PostRepository.java index 2459e378..8d08d87a 100644 --- a/src/main/java/com/example/sharemind/post/repository/PostRepository.java +++ b/src/main/java/com/example/sharemind/post/repository/PostRepository.java @@ -21,8 +21,9 @@ public interface PostRepository extends JpaRepository, PostCustomRep "ORDER BY total_like DESC LIMIT :size", nativeQuery = true) List findPopularityPosts(LocalDate weekAgo, int size); - @Query(value = "SELECT post_id FROM post " - + "WHERE post_status = 'PROCEEDING' " - + "ORDER BY RAND() LIMIT 50", nativeQuery = true) - List findRandomProceedingPostIds(); + @Query(value = "SELECT post_id FROM Post WHERE post_status = 'PROCEEDING' AND published_at <= CURRENT_TIMESTAMP - INTERVAL 1 DAY ORDER BY RAND() LIMIT 50", nativeQuery = true) + List findRandomProceedingPostIdsAfter24Hours(); + + @Query(value = "SELECT post_id FROM Post WHERE post_status = 'PROCEEDING' AND published_at > CURRENT_TIMESTAMP - INTERVAL 1 DAY ORDER BY RAND() LIMIT 50", nativeQuery = true) + List findRandomProceedingPostIdsWithin24Hours(); }