diff --git a/src/main/java/org/gachon/checkmate/domain/checkList/repository/CheckListRepository.java b/src/main/java/org/gachon/checkmate/domain/checkList/repository/CheckListRepository.java new file mode 100644 index 0000000..ac6f0a3 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/checkList/repository/CheckListRepository.java @@ -0,0 +1,10 @@ +package org.gachon.checkmate.domain.checkList.repository; + +import org.gachon.checkmate.domain.checkList.entity.CheckList; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface CheckListRepository extends JpaRepository { + Optional findByUserId(Long userId); +} diff --git a/src/main/java/org/gachon/checkmate/domain/member/entity/User.java b/src/main/java/org/gachon/checkmate/domain/member/entity/User.java index 105849b..48a8d22 100644 --- a/src/main/java/org/gachon/checkmate/domain/member/entity/User.java +++ b/src/main/java/org/gachon/checkmate/domain/member/entity/User.java @@ -6,7 +6,7 @@ import org.gachon.checkmate.domain.member.converter.GenderTypeConverter; import org.gachon.checkmate.domain.member.converter.MbtiTypeConverter; import org.gachon.checkmate.domain.member.converter.RoomTypeConverter; -import org.gachon.checkmate.domain.post.entity.PostMaker; +import org.gachon.checkmate.domain.post.entity.Post; import org.gachon.checkmate.domain.scrap.entity.Scrap; import org.gachon.checkmate.global.common.BaseTimeEntity; @@ -40,7 +40,7 @@ public class User extends BaseTimeEntity { private CheckList checkList; @OneToMany(mappedBy = "user") @Builder.Default - private List postMakerList = new ArrayList<>(); + private List postList = new ArrayList<>(); @OneToMany(mappedBy = "user") @Builder.Default private List scrapList = new ArrayList<>(); diff --git a/src/main/java/org/gachon/checkmate/domain/post/controller/PostController.java b/src/main/java/org/gachon/checkmate/domain/post/controller/PostController.java new file mode 100644 index 0000000..7641ba2 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/post/controller/PostController.java @@ -0,0 +1,30 @@ +package org.gachon.checkmate.domain.post.controller; + +import lombok.RequiredArgsConstructor; +import org.gachon.checkmate.domain.post.dto.response.PostSearchResponseDto; +import org.gachon.checkmate.domain.post.service.PostService; +import org.gachon.checkmate.global.common.SuccessResponse; +import org.gachon.checkmate.global.config.auth.UserId; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RequiredArgsConstructor +@RequestMapping("/api/post") +@RestController +public class PostController { + private final PostService postService; + + @GetMapping("/search") + public ResponseEntity> searchTextPost(@UserId final Long userId, + @RequestParam final String text, + final Pageable pageable) { + final List responseDto = postService.searchTextPost(userId, text, pageable); + return SuccessResponse.ok(responseDto); + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/post/dto/response/PostSearchResponseDto.java b/src/main/java/org/gachon/checkmate/domain/post/dto/response/PostSearchResponseDto.java new file mode 100644 index 0000000..3bc2399 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/post/dto/response/PostSearchResponseDto.java @@ -0,0 +1,29 @@ +package org.gachon.checkmate.domain.post.dto.response; + +import lombok.Builder; +import org.gachon.checkmate.domain.post.dto.support.PostSearchDto; + +@Builder +public record PostSearchResponseDto( + String title, + String content, + String importantKey, + String similarityKey, + int scrapCount, + int remainDate, + int accuracy +) { + public static PostSearchResponseDto of(PostSearchDto postSearchDto, + int remainDate, + int accuracy) { + return PostSearchResponseDto.builder() + .title(postSearchDto.title()) + .content(postSearchDto.content()) + .importantKey(postSearchDto.importantKey().getDesc()) + .similarityKey(postSearchDto.similarityKey().getDesc()) + .scrapCount(postSearchDto.scrapCount()) + .remainDate(remainDate) + .accuracy(accuracy) + .build(); + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/post/dto/support/PostSearchDto.java b/src/main/java/org/gachon/checkmate/domain/post/dto/support/PostSearchDto.java new file mode 100644 index 0000000..58dc285 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/post/dto/support/PostSearchDto.java @@ -0,0 +1,29 @@ +package org.gachon.checkmate.domain.post.dto.support; + +import com.querydsl.core.annotations.QueryProjection; +import org.gachon.checkmate.domain.checkList.entity.PostCheckList; +import org.gachon.checkmate.domain.post.entity.ImportantKeyType; +import org.gachon.checkmate.domain.post.entity.SimilarityKeyType; + +import java.time.LocalDate; + +public record PostSearchDto( + String title, + String content, + ImportantKeyType importantKey, + SimilarityKeyType similarityKey, + LocalDate endDate, + int scrapCount, + PostCheckList postCheckList +) { + @QueryProjection + public PostSearchDto(String title, String content, ImportantKeyType importantKey, SimilarityKeyType similarityKey, LocalDate endDate, int scrapCount, PostCheckList postCheckList) { + this.title = title; + this.content = content; + this.importantKey = importantKey; + this.similarityKey = similarityKey; + this.endDate = endDate; + this.scrapCount = scrapCount; + this.postCheckList = postCheckList; + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/post/entity/Post.java b/src/main/java/org/gachon/checkmate/domain/post/entity/Post.java index 293e40c..f932a4b 100644 --- a/src/main/java/org/gachon/checkmate/domain/post/entity/Post.java +++ b/src/main/java/org/gachon/checkmate/domain/post/entity/Post.java @@ -5,6 +5,7 @@ import org.gachon.checkmate.domain.checkList.entity.PostCheckList; import org.gachon.checkmate.domain.member.converter.RoomTypeConverter; import org.gachon.checkmate.domain.member.entity.RoomType; +import org.gachon.checkmate.domain.member.entity.User; import org.gachon.checkmate.domain.post.converter.ImportantKeyTypeConverter; import org.gachon.checkmate.domain.post.converter.SimilarityKeyTypeConverter; import org.gachon.checkmate.domain.scrap.entity.Scrap; @@ -26,7 +27,7 @@ public class Post extends BaseTimeEntity { private Long id; private String title; private String content; - private LocalDate EndDate; + private LocalDate endDate; @Convert(converter = RoomTypeConverter.class) private RoomType roomType; @Convert(converter = ImportantKeyTypeConverter.class) @@ -35,9 +36,9 @@ public class Post extends BaseTimeEntity { private SimilarityKeyType similarityKeyType; @OneToOne(mappedBy = "post", fetch = FetchType.LAZY, cascade = CascadeType.ALL) private PostCheckList postCheckList; - @OneToMany(mappedBy = "post") - @Builder.Default - private List postMakerList = new ArrayList<>(); + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; @OneToMany(mappedBy = "post") @Builder.Default private List scrapList = new ArrayList<>(); diff --git a/src/main/java/org/gachon/checkmate/domain/post/entity/PostMaker.java b/src/main/java/org/gachon/checkmate/domain/post/entity/PostMaker.java deleted file mode 100644 index 6136fa7..0000000 --- a/src/main/java/org/gachon/checkmate/domain/post/entity/PostMaker.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.gachon.checkmate.domain.post.entity; - -import jakarta.persistence.*; -import lombok.*; -import org.gachon.checkmate.domain.member.entity.User; -import org.gachon.checkmate.global.common.BaseTimeEntity; - -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@Builder(access = AccessLevel.PRIVATE) -@Getter -@Entity -public class PostMaker extends BaseTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "user_post_id") - private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "post_id") - private Post post; -} diff --git a/src/main/java/org/gachon/checkmate/domain/post/repository/PostQuerydslRepository.java b/src/main/java/org/gachon/checkmate/domain/post/repository/PostQuerydslRepository.java new file mode 100644 index 0000000..e754184 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/post/repository/PostQuerydslRepository.java @@ -0,0 +1,51 @@ +package org.gachon.checkmate.domain.post.repository; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.gachon.checkmate.domain.post.dto.support.PostSearchDto; +import org.gachon.checkmate.domain.post.dto.support.QPostSearchDto; +import org.gachon.checkmate.domain.post.entity.Post; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; +import java.util.List; + +import static org.gachon.checkmate.domain.checkList.entity.QPostCheckList.postCheckList; +import static org.gachon.checkmate.domain.post.entity.QPost.post; + +@RequiredArgsConstructor +@Repository +public class PostQuerydslRepository { + private final JPAQueryFactory queryFactory; + + public Page searchTextPost(String text, Pageable pageable) { + List content = queryFactory + .select(new QPostSearchDto( + post.title, + post.content, + post.importantKeyType, + post.similarityKeyType, + post.endDate, + post.scrapList.size(), + postCheckList + )) + .from(post) + .leftJoin(post.postCheckList, postCheckList) + .where( + post.title.contains(text), + post.endDate.before(LocalDate.now()) + ) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + JPAQuery countQuery = queryFactory + .selectFrom(post); + return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchCount); + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/post/service/PostService.java b/src/main/java/org/gachon/checkmate/domain/post/service/PostService.java new file mode 100644 index 0000000..fa226ac --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/post/service/PostService.java @@ -0,0 +1,75 @@ +package org.gachon.checkmate.domain.post.service; + +import lombok.RequiredArgsConstructor; +import org.gachon.checkmate.domain.checkList.entity.CheckList; +import org.gachon.checkmate.domain.checkList.entity.PostCheckList; +import org.gachon.checkmate.domain.checkList.repository.CheckListRepository; +import org.gachon.checkmate.domain.post.dto.response.PostSearchResponseDto; +import org.gachon.checkmate.domain.post.dto.support.PostSearchDto; +import org.gachon.checkmate.domain.post.repository.PostQuerydslRepository; +import org.gachon.checkmate.global.error.exception.EntityNotFoundException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.stream.Collectors; + +import static org.gachon.checkmate.global.error.ErrorCode.CHECK_LIST_NOT_FOUND; + + +@RequiredArgsConstructor +@Transactional +@Service +public class PostService { + private final CheckListRepository checkListRepository; + private final PostQuerydslRepository postQuerydslRepository; + + public List searchTextPost(Long userId, String text, Pageable pageable) { + CheckList checkList = getCheckList(userId); + Page postSearchDtoList = getPostSearchDto(text, pageable); + return createPostSearchResponseDto(postSearchDtoList, checkList); + } + + private List createPostSearchResponseDto(Page postSearchDtoList, CheckList checkList) { + return postSearchDtoList.stream() + .map(postSearchDto -> + PostSearchResponseDto.of( + postSearchDto, + getRemainDate(postSearchDto.endDate()), + getAccuracy(postSearchDto.postCheckList(), checkList) + )) + .collect(Collectors.toList()); + } + + private int getAccuracy(PostCheckList postCheckList, CheckList checkList) { + int count = 0; + count += getRateForFrequencyElement(postCheckList.getCleanType().getCode(), checkList.getCleanType().getCode(), 4); + count += getRateForFrequencyElement(postCheckList.getDrinkType().getCode(), checkList.getDrinkType().getCode(), 3); + count += getRateForFrequencyElement(postCheckList.getHomeType().getCode(), checkList.getHomeType().getCode(), 3); + count = postCheckList.getLifePatterType().equals(checkList.getLifePatterType()) ? count + 1 : count; + count = postCheckList.getNoiseType().equals(checkList.getNoiseType()) ? count + 1 : count; + count = postCheckList.getSleepType().equals(checkList.getSleepType()) ? count + 1 : count; + return (int) (count / 6) * 100; + } + + private int getRateForFrequencyElement(String firstEnumCode, String secondEnumCode, int size) { + return 1 - Math.abs(Integer.parseInt(firstEnumCode) - Integer.parseInt(secondEnumCode)) / size; + } + + private int getRemainDate(LocalDate endDate) { + return (int) endDate.until(LocalDate.now(), ChronoUnit.DAYS); + } + + private Page getPostSearchDto(String text, Pageable pageable) { + return postQuerydslRepository.searchTextPost(text, pageable); + } + + private CheckList getCheckList(Long userId) { + return checkListRepository.findByUserId(userId) + .orElseThrow(() -> new EntityNotFoundException(CHECK_LIST_NOT_FOUND)); + } +} diff --git a/src/main/java/org/gachon/checkmate/global/config/QuerydslConfig.java b/src/main/java/org/gachon/checkmate/global/config/QuerydslConfig.java new file mode 100644 index 0000000..df27500 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/global/config/QuerydslConfig.java @@ -0,0 +1,17 @@ +package org.gachon.checkmate.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QuerydslConfig { + @Autowired + EntityManager em; + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(em); + } +} diff --git a/src/main/java/org/gachon/checkmate/global/error/ErrorCode.java b/src/main/java/org/gachon/checkmate/global/error/ErrorCode.java index 9705dfa..983ca6a 100644 --- a/src/main/java/org/gachon/checkmate/global/error/ErrorCode.java +++ b/src/main/java/org/gachon/checkmate/global/error/ErrorCode.java @@ -35,6 +35,7 @@ public enum ErrorCode { * 404 Not Found */ ENTITY_NOT_FOUND(HttpStatus.NOT_FOUND, "엔티티를 찾을 수 없습니다."), + CHECK_LIST_NOT_FOUND(HttpStatus.NOT_FOUND, "체크리스트를 찾을 수 없습니다."), /** * 405 Method Not Allowed