diff --git a/module-api/src/main/java/com/mile/controller/moim/MoimController.java b/module-api/src/main/java/com/mile/controller/moim/MoimController.java index 9c036a1d..edba9f15 100644 --- a/module-api/src/main/java/com/mile/controller/moim/MoimController.java +++ b/module-api/src/main/java/com/mile/controller/moim/MoimController.java @@ -3,6 +3,7 @@ import com.mile.dto.SuccessResponse; import com.mile.exception.message.SuccessMessage; import com.mile.moim.service.MoimService; +import com.mile.moim.service.dto.BestMoimListResponse; import com.mile.moim.service.dto.CategoryListResponse; import com.mile.moim.service.dto.ContentListResponse; import com.mile.moim.service.dto.MoimAuthenticateResponse; @@ -11,14 +12,13 @@ import com.mile.moim.service.dto.MoimTopicResponse; import com.mile.moim.service.dto.TemporaryPostExistResponse; import com.mile.writerName.service.dto.PopularWriterListResponse; +import java.security.Principal; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.security.Principal; - @RestController @RequiredArgsConstructor @RequestMapping("/api/moim") @@ -86,6 +86,11 @@ public SuccessResponse getMostCuriousPostByMoim( return SuccessResponse.of(SuccessMessage.MOIM_TOP_2_POST_GET_SUCCESS, moimService.getMostCuriousPostFromMoim(moimId)); } + @GetMapping("/best") + public SuccessResponse getBestMoimAndPostList() { + return SuccessResponse.of(SuccessMessage.BEST_MOIM_POSTS_GET_SUCCESS, moimService.getBestMoimAndPostList()); + } + @Override @GetMapping("/{moimId}/temporary") public SuccessResponse getTemporaryPost( diff --git a/module-api/src/main/java/com/mile/controller/moim/MoimControllerSwagger.java b/module-api/src/main/java/com/mile/controller/moim/MoimControllerSwagger.java index ae288efd..646d0557 100644 --- a/module-api/src/main/java/com/mile/controller/moim/MoimControllerSwagger.java +++ b/module-api/src/main/java/com/mile/controller/moim/MoimControllerSwagger.java @@ -2,6 +2,7 @@ import com.mile.dto.ErrorResponse; import com.mile.dto.SuccessResponse; +import com.mile.moim.service.dto.BestMoimListResponse; import com.mile.moim.service.dto.CategoryListResponse; import com.mile.moim.service.dto.ContentListResponse; import com.mile.moim.service.dto.MoimCuriousPostListResponse; @@ -123,6 +124,17 @@ SuccessResponse getCategoryList( @PathVariable final Long moimId ); + @Operation(summary = "베스트 활동 모임 및 글 리스트 조회") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "베스트 활동 모임과 글 리스트 조회가 완료되었습니다."), + @ApiResponse(responseCode = "500", description = "서버 내부 오류입니다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + } + ) + SuccessResponse getBestMoimAndPostList(); + + @Operation(summary = "임시 저장 글 존재 여부 조회") @ApiResponses( value = { diff --git a/module-common/src/main/java/com/mile/exception/message/SuccessMessage.java b/module-common/src/main/java/com/mile/exception/message/SuccessMessage.java index d587900b..0d379f6d 100644 --- a/module-common/src/main/java/com/mile/exception/message/SuccessMessage.java +++ b/module-common/src/main/java/com/mile/exception/message/SuccessMessage.java @@ -30,6 +30,7 @@ public enum SuccessMessage { MOIM_TOP_2_POST_GET_SUCCESS(HttpStatus.OK.value(), "궁금해요 상위 2개의 글이 조회 완료되었습니다."), POST_GET_SUCCESS(HttpStatus.OK.value(), "글 조회가 완료되었습니다."), MOIM_POST_GET_SUCCESS(HttpStatus.OK.value(), "카테고리별 글 리스트 조회가 완료되었습니다."), + BEST_MOIM_POSTS_GET_SUCCESS(HttpStatus.OK.value(), "베스트 활동 모임과 글 조회가 완료되었습니다."), IS_TEMPORARY_POST_EXIST_GET_SUCCESS(HttpStatus.OK.value(), "임시저장 글 존재 여부 조회가 완료되었습니다."), /* 201 CREATED diff --git a/module-domain/src/main/java/com/mile/moim/repository/MoimRepository.java b/module-domain/src/main/java/com/mile/moim/repository/MoimRepository.java index 57d5f003..715830d4 100644 --- a/module-domain/src/main/java/com/mile/moim/repository/MoimRepository.java +++ b/module-domain/src/main/java/com/mile/moim/repository/MoimRepository.java @@ -2,9 +2,12 @@ import com.mile.moim.domain.Moim; import com.mile.post.domain.Post; +import java.time.LocalDateTime; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; -public interface MoimRepository extends JpaRepository { +public interface MoimRepository extends JpaRepository, MoimRepositoryCustom { List getPostsById(final Long id); } diff --git a/module-domain/src/main/java/com/mile/moim/repository/MoimRepositoryCustom.java b/module-domain/src/main/java/com/mile/moim/repository/MoimRepositoryCustom.java new file mode 100644 index 00000000..06c56cc6 --- /dev/null +++ b/module-domain/src/main/java/com/mile/moim/repository/MoimRepositoryCustom.java @@ -0,0 +1,9 @@ +package com.mile.moim.repository; + +import com.mile.moim.domain.Moim; +import java.util.List; + +public interface MoimRepositoryCustom { + + List findTop3MoimsByPostCount(); +} diff --git a/module-domain/src/main/java/com/mile/moim/repository/MoimRepositoryCustomImpl.java b/module-domain/src/main/java/com/mile/moim/repository/MoimRepositoryCustomImpl.java new file mode 100644 index 00000000..496be969 --- /dev/null +++ b/module-domain/src/main/java/com/mile/moim/repository/MoimRepositoryCustomImpl.java @@ -0,0 +1,32 @@ +package com.mile.moim.repository; + +import static com.mile.moim.domain.QMoim.moim; +import static com.mile.post.domain.QPost.post; + +import com.mile.moim.domain.Moim; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class MoimRepositoryCustomImpl implements MoimRepositoryCustom { + private final JPAQueryFactory queryFactory; + + public List findTop3MoimsByPostCount() { + + LocalDateTime oneWeekAgo = LocalDateTime.now().minusWeeks(1); + + List result = queryFactory + .select(moim) + .from(moim) + .leftJoin(post).on(moim.eq(post.topic.moim)) + .where(post.createdAt.after(oneWeekAgo)) + .groupBy(moim) + .orderBy(post.id.count().desc()) + .limit(3) + .fetch(); + + return result; + } +} diff --git a/module-domain/src/main/java/com/mile/moim/service/MoimService.java b/module-domain/src/main/java/com/mile/moim/service/MoimService.java index cb600e6e..43a86388 100644 --- a/module-domain/src/main/java/com/mile/moim/service/MoimService.java +++ b/module-domain/src/main/java/com/mile/moim/service/MoimService.java @@ -5,6 +5,7 @@ import com.mile.exception.model.NotFoundException; import com.mile.moim.domain.Moim; import com.mile.moim.repository.MoimRepository; +import com.mile.moim.service.dto.BestMoimListResponse; import com.mile.moim.service.dto.CategoryListResponse; import com.mile.moim.service.dto.ContentListResponse; import com.mile.moim.service.dto.MoimAuthenticateResponse; @@ -12,22 +13,24 @@ import com.mile.moim.service.dto.MoimInfoResponse; import com.mile.moim.service.dto.MoimTopicResponse; import com.mile.moim.service.dto.TemporaryPostExistResponse; +import com.mile.post.domain.Post; +import com.mile.post.service.PostAuthenticateService; import com.mile.post.service.PostCuriousService; +import com.mile.post.service.PostGetService; import com.mile.post.service.PostService; import com.mile.post.service.PostTemporaryService; import com.mile.topic.service.TopicService; import com.mile.utils.DateUtil; import com.mile.writerName.domain.WriterName; - -import java.util.List; - import com.mile.writerName.service.WriterNameService; import com.mile.writerName.service.dto.PopularWriterListResponse; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.List; - @Service @RequiredArgsConstructor public class MoimService { @@ -36,7 +39,9 @@ public class MoimService { private final TopicService topicService; private final MoimRepository moimRepository; private final PostCuriousService postCuriousService; + private final PostAuthenticateService postAuthenticateService; private final PostTemporaryService postTemporaryService; + private final PostGetService postGetService; private static final int NUMBER_OF_MOST_CURIOUS_WRITERS = 2; @@ -44,7 +49,7 @@ public ContentListResponse getContentsFromMoim( final Long moimId, final Long userId ) { - authenticateUserOfMoim(moimId, userId); + postAuthenticateService.authenticateUserOfMoim(moimId, userId); return ContentListResponse.of(topicService.getContentsFromMoim(moimId)); } @@ -118,6 +123,23 @@ public CategoryListResponse getCategoryList( ) { return CategoryListResponse.of(topicService.getKeywordsFromMoim(moimId)); } + + public List getBestMoimByPostNumber() { + LocalDateTime beforeWeek = LocalDateTime.now().minusWeeks(1); + List moims = moimRepository.findTop3MoimsByPostCount(); + return moims; + } + + public BestMoimListResponse getBestMoimAndPostList() { + Map> BestMoimAndPost = new HashMap<>(); + List bestMoimsByPostNumber = getBestMoimByPostNumber(); + for (Moim moim : bestMoimsByPostNumber) { + List latestPosts = postGetService.getLatestPostsByMoim(moim); + BestMoimAndPost.put(moim, latestPosts); + } + return BestMoimListResponse.of(BestMoimAndPost); + } + public TemporaryPostExistResponse getTemporaryPost( final Long moimId, final Long userId diff --git a/module-domain/src/main/java/com/mile/moim/service/dto/BestMoimInfoResponse.java b/module-domain/src/main/java/com/mile/moim/service/dto/BestMoimInfoResponse.java new file mode 100644 index 00000000..95e234ac --- /dev/null +++ b/module-domain/src/main/java/com/mile/moim/service/dto/BestMoimInfoResponse.java @@ -0,0 +1,18 @@ +package com.mile.moim.service.dto; + +import com.mile.moim.domain.Moim; +import com.mile.post.domain.Post; +import java.util.List; +import java.util.stream.Collectors; + +public record BestMoimInfoResponse(Long moimId, String moimName, List moimPosts) { + public static BestMoimInfoResponse of(Moim moim, final List posts) { + return new BestMoimInfoResponse( + moim.getId(), + moim.getName(), + posts.stream() + .map(BestMoimPostResponse::of) + .collect(Collectors.toList()) + ); + } +} diff --git a/module-domain/src/main/java/com/mile/moim/service/dto/BestMoimListResponse.java b/module-domain/src/main/java/com/mile/moim/service/dto/BestMoimListResponse.java new file mode 100644 index 00000000..d18db754 --- /dev/null +++ b/module-domain/src/main/java/com/mile/moim/service/dto/BestMoimListResponse.java @@ -0,0 +1,18 @@ +package com.mile.moim.service.dto; + +import com.mile.moim.domain.Moim; +import com.mile.post.domain.Post; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public record BestMoimListResponse(List moim) { + public static BestMoimListResponse of(Map> BestMoimAndPost) { + List bestMoims = new ArrayList<>(); + for (Moim moim : BestMoimAndPost.keySet()) { + bestMoims.add(BestMoimInfoResponse.of(moim, BestMoimAndPost.get(moim))); + } + return new BestMoimListResponse(bestMoims); + + } +} diff --git a/module-domain/src/main/java/com/mile/moim/service/dto/BestMoimPostResponse.java b/module-domain/src/main/java/com/mile/moim/service/dto/BestMoimPostResponse.java new file mode 100644 index 00000000..fce246e2 --- /dev/null +++ b/module-domain/src/main/java/com/mile/moim/service/dto/BestMoimPostResponse.java @@ -0,0 +1,31 @@ +package com.mile.moim.service.dto; + +import com.mile.post.domain.Post; +import org.jsoup.Jsoup; +import org.jsoup.safety.Whitelist; + +public record BestMoimPostResponse(String topicName, String imageUrl, String postTitle, String postContent) { + private static final int SUBSTRING_START = 0; + private static final int SUBSTRING_END = 200; + + public static BestMoimPostResponse of(Post post) { + + return new BestMoimPostResponse( + post.getTopic().getContent(), + post.getImageUrl(), + post.getTitle(), + getSubStringOfCleanContent(post.getContent()) + ); + } + + private static String getSubStringOfCleanContent( + String content + ) { + String cleanContent = Jsoup.clean(content, Whitelist.none()); + if (cleanContent.length() >= SUBSTRING_END) { + return cleanContent.substring(SUBSTRING_START, SUBSTRING_END); + } else { + return cleanContent; + } + } +} diff --git a/module-domain/src/main/java/com/mile/moim/service/dto/MoimMostCuriousPostResponse.java b/module-domain/src/main/java/com/mile/moim/service/dto/MoimMostCuriousPostResponse.java index 06b475a4..4b463861 100644 --- a/module-domain/src/main/java/com/mile/moim/service/dto/MoimMostCuriousPostResponse.java +++ b/module-domain/src/main/java/com/mile/moim/service/dto/MoimMostCuriousPostResponse.java @@ -26,6 +26,11 @@ public static MoimMostCuriousPostResponse of( private static String getSubStringOfCleanContent( String content ) { - return Jsoup.clean(content, Whitelist.none()).substring(SUBSTRING_START, SUBSTRING_END); + String cleanContent = Jsoup.clean(content, Whitelist.none()); + if (cleanContent.length() >= SUBSTRING_END) { + return cleanContent.substring(SUBSTRING_START, SUBSTRING_END); + } else { + return cleanContent; + } } } diff --git a/module-domain/src/main/java/com/mile/post/repository/PostRepository.java b/module-domain/src/main/java/com/mile/post/repository/PostRepository.java index 9bf0c757..509a93c2 100644 --- a/module-domain/src/main/java/com/mile/post/repository/PostRepository.java +++ b/module-domain/src/main/java/com/mile/post/repository/PostRepository.java @@ -2,13 +2,11 @@ import com.mile.post.domain.Post; import com.mile.topic.domain.Topic; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; public interface PostRepository extends JpaRepository, PostRepositoryCustom { boolean existsPostByIdAndWriterNameId(final Long postId, final Long userId); - List findByTopic(final Topic topic); } diff --git a/module-domain/src/main/java/com/mile/post/repository/PostRepositoryCustom.java b/module-domain/src/main/java/com/mile/post/repository/PostRepositoryCustom.java index 812ef93d..89c0af03 100644 --- a/module-domain/src/main/java/com/mile/post/repository/PostRepositoryCustom.java +++ b/module-domain/src/main/java/com/mile/post/repository/PostRepositoryCustom.java @@ -8,6 +8,7 @@ public interface PostRepositoryCustom { List findTop2ByMoimOrderByCuriousCountDesc(final Moim requestMoim); - + List findLatest4PostsByMoim(Moim moim); List findByMoimAndWriterNameWhereIsTemporary(final Moim moim, final WriterName writerName); } + diff --git a/module-domain/src/main/java/com/mile/post/repository/PostRepositoryImpl.java b/module-domain/src/main/java/com/mile/post/repository/PostRepositoryImpl.java index b3df33d8..62dbfe2d 100644 --- a/module-domain/src/main/java/com/mile/post/repository/PostRepositoryImpl.java +++ b/module-domain/src/main/java/com/mile/post/repository/PostRepositoryImpl.java @@ -1,17 +1,17 @@ package com.mile.post.repository; +import static com.mile.moim.domain.QMoim.moim; +import static com.mile.post.domain.QPost.post; +import static com.mile.writerName.domain.QWriterName.writerName; + import com.mile.moim.domain.Moim; import com.mile.post.domain.Post; +import com.mile.post.domain.QPost; import com.mile.writerName.domain.WriterName; import com.querydsl.jpa.impl.JPAQueryFactory; -import lombok.RequiredArgsConstructor; - import java.util.List; import java.util.stream.Collectors; - -import static com.mile.moim.domain.QMoim.moim; -import static com.mile.post.domain.QPost.post; -import static com.mile.writerName.domain.QWriterName.writerName; +import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class PostRepositoryImpl implements PostRepositoryCustom { @@ -26,6 +26,19 @@ public List findTop2ByMoimOrderByCuriousCountDesc(final Moim requestMoim) .stream().limit(2).collect(Collectors.toList()); } + public List findLatest4PostsByMoim(Moim moim) { + + List result = jpaQueryFactory + .select(post) + .from(post) + .where(post.topic.moim.eq(moim)) + .orderBy(post.createdAt.desc()) + .limit(4) + .fetch(); + + return result; + } + public List findByMoimAndWriterNameWhereIsTemporary(final Moim requestMoim, final WriterName requestWriterName) { return jpaQueryFactory.selectFrom(post) .join(moim) diff --git a/module-domain/src/main/java/com/mile/post/service/PostAuthenticateService.java b/module-domain/src/main/java/com/mile/post/service/PostAuthenticateService.java index 43da2b7b..30fdcbe7 100644 --- a/module-domain/src/main/java/com/mile/post/service/PostAuthenticateService.java +++ b/module-domain/src/main/java/com/mile/post/service/PostAuthenticateService.java @@ -3,10 +3,8 @@ import com.mile.exception.message.ErrorMessage; import com.mile.exception.model.ForbiddenException; import com.mile.exception.model.NotFoundException; -import com.mile.moim.service.MoimService; import com.mile.post.domain.Post; import com.mile.post.repository.PostRepository; -import com.mile.writerName.domain.WriterName; import com.mile.writerName.service.WriterNameService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -14,7 +12,6 @@ @Service @RequiredArgsConstructor public class PostAuthenticateService { - private final MoimService moimService; private final PostRepository postRepository; private final WriterNameService writerNameService; @@ -31,7 +28,16 @@ public void authenticateUserWithPost( final Long userId ) { Long moimId = post.getTopic().getMoim().getId(); - moimService.authenticateUserOfMoim(moimId, userId); + authenticateUserOfMoim(moimId, userId); + } + + public void authenticateUserOfMoim( + final Long moimId, + final Long userId + ) { + if (!writerNameService.isUserInMoim(moimId, userId)) { + throw new ForbiddenException(ErrorMessage.USER_AUTHENTICATE_ERROR); + } } private Post findById( diff --git a/module-domain/src/main/java/com/mile/post/service/PostGetService.java b/module-domain/src/main/java/com/mile/post/service/PostGetService.java index 5ee47b58..dfebd1ac 100644 --- a/module-domain/src/main/java/com/mile/post/service/PostGetService.java +++ b/module-domain/src/main/java/com/mile/post/service/PostGetService.java @@ -2,6 +2,7 @@ import com.mile.exception.message.ErrorMessage; import com.mile.exception.model.NotFoundException; +import com.mile.moim.domain.Moim; import com.mile.post.domain.Post; import com.mile.post.repository.PostRepository; import com.mile.topic.domain.Topic; @@ -40,4 +41,8 @@ private void isPostListEmpty( throw new NotFoundException(ErrorMessage.MOIM_TOPIC_NOT_FOUND); } } + + public List getLatestPostsByMoim(Moim moim) { + return postRepository.findLatest4PostsByMoim(moim); + } } diff --git a/module-domain/src/main/java/com/mile/post/service/PostService.java b/module-domain/src/main/java/com/mile/post/service/PostService.java index 75d331c2..580cf0fd 100644 --- a/module-domain/src/main/java/com/mile/post/service/PostService.java +++ b/module-domain/src/main/java/com/mile/post/service/PostService.java @@ -23,6 +23,7 @@ import com.mile.user.service.UserService; import com.mile.writerName.domain.WriterName; import com.mile.writerName.service.WriterNameService; +import java.util.List; import com.mile.writerName.service.dto.WriterNameResponse; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -235,6 +236,7 @@ private boolean checkContainPhoto(String imageUrl) { return imageUrl != null && !imageUrl.isEmpty(); } + @Transactional public WriterNameResponse putFixedPost(final Long userId, final PostPutRequest request, final Long postId) { postAuthenticateService.authenticateWriterWithPost(postId, userId); diff --git a/module-domain/src/main/java/com/mile/post/service/dto/PostListResponse.java b/module-domain/src/main/java/com/mile/post/service/dto/PostListResponse.java index d1ab80cb..5af8b504 100644 --- a/module-domain/src/main/java/com/mile/post/service/dto/PostListResponse.java +++ b/module-domain/src/main/java/com/mile/post/service/dto/PostListResponse.java @@ -26,6 +26,11 @@ public static PostListResponse of(final Post post) { } private static String getSubString(final Post post) { - return Jsoup.clean(post.getContent(), Whitelist.none()).substring(SUBSTRING_START, SUBSTRING_END); + String cleanContent = Jsoup.clean(post.getContent(), Whitelist.none()); + if (cleanContent.length() >= SUBSTRING_END) { + return cleanContent.substring(SUBSTRING_START, SUBSTRING_END); + } else { + return cleanContent; + } } }