diff --git a/src/main/java/org/gachon/checkmate/domain/member/converter/UserStateConverter.java b/src/main/java/org/gachon/checkmate/domain/member/converter/UserStateConverter.java new file mode 100644 index 0000000..23ae85e --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/member/converter/UserStateConverter.java @@ -0,0 +1,10 @@ +package org.gachon.checkmate.domain.member.converter; + +import org.gachon.checkmate.domain.member.entity.UserState; +import org.gachon.checkmate.global.utils.AbstractEnumCodeAttributeConverter; + +public class UserStateConverter extends AbstractEnumCodeAttributeConverter { + public UserStateConverter() { + super(UserState.class); + } +} 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 dcfb23d..855e362 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 @@ -5,6 +5,7 @@ import org.gachon.checkmate.domain.checkList.entity.CheckList; import org.gachon.checkmate.domain.member.converter.GenderTypeConverter; import org.gachon.checkmate.domain.member.converter.MbtiTypeConverter; +import org.gachon.checkmate.domain.member.converter.UserStateConverter; import org.gachon.checkmate.domain.post.entity.Post; import org.gachon.checkmate.domain.scrap.entity.Scrap; import org.gachon.checkmate.global.common.BaseTimeEntity; @@ -29,6 +30,8 @@ public class User extends BaseTimeEntity { private String profile; private String school; private String major; + @Convert(converter = UserStateConverter.class) + private UserState userState; @Convert(converter = MbtiTypeConverter.class) private MbtiType mbtiType; @Convert(converter = GenderTypeConverter.class) @@ -70,4 +73,8 @@ public void setProfile(String profile) { public void addPost(Post post) { this.postList.add(post); } + + public void addScrap(Scrap scrap) { + this.scrapList.add(scrap); + } } diff --git a/src/main/java/org/gachon/checkmate/domain/member/entity/UserState.java b/src/main/java/org/gachon/checkmate/domain/member/entity/UserState.java new file mode 100644 index 0000000..385253c --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/member/entity/UserState.java @@ -0,0 +1,16 @@ +package org.gachon.checkmate.domain.member.entity; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.gachon.checkmate.global.utils.EnumField; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public enum UserState implements EnumField { + JOIN("1", "가입"), + WITHDRAW("2", "탈퇴"); + + private final String code; + private final String desc; +} 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 index ff131fa..eeb88fd 100644 --- a/src/main/java/org/gachon/checkmate/domain/post/controller/PostController.java +++ b/src/main/java/org/gachon/checkmate/domain/post/controller/PostController.java @@ -29,8 +29,9 @@ public ResponseEntity> getAllPosts(@UserId final Long userId, } @GetMapping("/{id}") - public ResponseEntity> getPostDetails(@PathVariable("id") final Long postId) { - final PostDetailResponseDto responseDto = postService.getPostDetails(postId); + public ResponseEntity> getPostDetails(@UserId final Long userId, + @PathVariable("id") final Long postId) { + final PostDetailResponseDto responseDto = postService.getPostDetails(userId, postId); return SuccessResponse.ok(responseDto); } @@ -42,10 +43,17 @@ public ResponseEntity> searchTextPost(@UserId final Long user return SuccessResponse.ok(responseDto); } + @GetMapping("/my") + public ResponseEntity> getMyPosts(@UserId final Long userId, + final Pageable pageable) { + final PostSearchResponseDto responseDto = postService.getMyPosts(userId, pageable); + return SuccessResponse.ok(responseDto); + } + @PostMapping public ResponseEntity> createPost(@UserId final Long userId, @RequestBody @Valid final PostCreateRequestDto requestDto) { postService.createPost(userId, requestDto); - return SuccessResponse.ok(null); + return SuccessResponse.created(null); } } diff --git a/src/main/java/org/gachon/checkmate/domain/post/converter/PostStateConverter.java b/src/main/java/org/gachon/checkmate/domain/post/converter/PostStateConverter.java new file mode 100644 index 0000000..b95d093 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/post/converter/PostStateConverter.java @@ -0,0 +1,10 @@ +package org.gachon.checkmate.domain.post.converter; + +import org.gachon.checkmate.domain.post.entity.PostState; +import org.gachon.checkmate.global.utils.AbstractEnumCodeAttributeConverter; + +public class PostStateConverter extends AbstractEnumCodeAttributeConverter { + public PostStateConverter() { + super(PostState.class); + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/post/dto/response/PostDetailResponseDto.java b/src/main/java/org/gachon/checkmate/domain/post/dto/response/PostDetailResponseDto.java index be6166d..d6148f7 100644 --- a/src/main/java/org/gachon/checkmate/domain/post/dto/response/PostDetailResponseDto.java +++ b/src/main/java/org/gachon/checkmate/domain/post/dto/response/PostDetailResponseDto.java @@ -11,15 +11,17 @@ public record PostDetailResponseDto( String profile, String gender, String mbti, + boolean isScrap, CheckListResponseDto checkList ) { - public static PostDetailResponseDto of(PostDetailDto postDetailDto, CheckListResponseDto checkList) { + public static PostDetailResponseDto of(PostDetailDto postDetailDto, CheckListResponseDto checkList, boolean isScrap) { return PostDetailResponseDto.builder() .name(postDetailDto.name()) .major(postDetailDto.major()) .profile(postDetailDto.profile()) .gender(postDetailDto.gender().getDesc()) .mbti(postDetailDto.mbti().getDesc()) + .isScrap(isScrap) .checkList(checkList) .build(); } diff --git a/src/main/java/org/gachon/checkmate/domain/post/dto/response/PostSearchElementResponseDto.java b/src/main/java/org/gachon/checkmate/domain/post/dto/response/PostSearchElementResponseDto.java index 20314e6..6bec711 100644 --- a/src/main/java/org/gachon/checkmate/domain/post/dto/response/PostSearchElementResponseDto.java +++ b/src/main/java/org/gachon/checkmate/domain/post/dto/response/PostSearchElementResponseDto.java @@ -13,7 +13,8 @@ public record PostSearchElementResponseDto( int scrapCount, int remainDate, int accuracy, - String gender + String gender, + String postState ) { public static PostSearchElementResponseDto of(PostSearchDto postSearchDto, int remainDate, @@ -28,6 +29,7 @@ public static PostSearchElementResponseDto of(PostSearchDto postSearchDto, .remainDate(remainDate) .accuracy(accuracy) .gender(postSearchDto.genderType().getDesc()) + .postState(postSearchDto.postState().getDesc()) .build(); } } diff --git a/src/main/java/org/gachon/checkmate/domain/post/dto/support/PostPagingSearchCondition.java b/src/main/java/org/gachon/checkmate/domain/post/dto/support/PostPagingSearchCondition.java new file mode 100644 index 0000000..1a40ca5 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/post/dto/support/PostPagingSearchCondition.java @@ -0,0 +1,27 @@ +package org.gachon.checkmate.domain.post.dto.support; + +import lombok.Builder; +import org.springframework.data.domain.Pageable; + +@Builder +public record PostPagingSearchCondition( + String text, + Long selectedUser, + Pageable pageable +) { + public static PostPagingSearchCondition searchText(String text, Pageable pageable) { + return PostPagingSearchCondition.builder() + .text(text) + .selectedUser(null) + .pageable(pageable) + .build(); + } + + public static PostPagingSearchCondition searchSelectedUser(Long userId, Pageable pageable) { + return PostPagingSearchCondition.builder() + .text(null) + .selectedUser(userId) + .pageable(pageable) + .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 index 9c003e0..daebfa4 100644 --- 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 @@ -4,6 +4,7 @@ import org.gachon.checkmate.domain.checkList.entity.PostCheckList; import org.gachon.checkmate.domain.member.entity.GenderType; import org.gachon.checkmate.domain.post.entity.ImportantKeyType; +import org.gachon.checkmate.domain.post.entity.PostState; import org.gachon.checkmate.domain.post.entity.SimilarityKeyType; import java.time.LocalDate; @@ -17,10 +18,11 @@ public record PostSearchDto( LocalDate endDate, int scrapCount, PostCheckList postCheckList, - GenderType genderType + GenderType genderType, + PostState postState ) { @QueryProjection - public PostSearchDto(Long postId, String title, String content, ImportantKeyType importantKey, SimilarityKeyType similarityKey, LocalDate endDate, int scrapCount, PostCheckList postCheckList, GenderType genderType) { + public PostSearchDto(Long postId, String title, String content, ImportantKeyType importantKey, SimilarityKeyType similarityKey, LocalDate endDate, int scrapCount, PostCheckList postCheckList, GenderType genderType, PostState postState) { this.postId = postId; this.title = title; this.content = content; @@ -30,5 +32,6 @@ public PostSearchDto(Long postId, String title, String content, ImportantKeyType this.scrapCount = scrapCount; this.postCheckList = postCheckList; this.genderType = genderType; + this.postState = postState; } } 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 e38dfe1..07518df 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.entity.User; import org.gachon.checkmate.domain.post.converter.ImportantKeyTypeConverter; +import org.gachon.checkmate.domain.post.converter.PostStateConverter; import org.gachon.checkmate.domain.post.converter.RoomTypeConverter; import org.gachon.checkmate.domain.post.converter.SimilarityKeyTypeConverter; import org.gachon.checkmate.domain.post.dto.request.PostCreateRequestDto; @@ -28,6 +29,8 @@ public class Post extends BaseTimeEntity { private String title; private String content; private LocalDate endDate; + @Convert(converter = PostStateConverter.class) + private PostState postState; @Convert(converter = RoomTypeConverter.class) private RoomType roomType; @Convert(converter = ImportantKeyTypeConverter.class) @@ -60,4 +63,8 @@ public static Post createPost(PostCreateRequestDto postCreateRequestDto, User us public void setPostCheckList(PostCheckList postCheckList) { this.postCheckList = postCheckList; } + + public void addScrap(Scrap scrap) { + this.scrapList.add(scrap); + } } diff --git a/src/main/java/org/gachon/checkmate/domain/post/entity/PostState.java b/src/main/java/org/gachon/checkmate/domain/post/entity/PostState.java new file mode 100644 index 0000000..56adf09 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/post/entity/PostState.java @@ -0,0 +1,16 @@ +package org.gachon.checkmate.domain.post.entity; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.gachon.checkmate.global.utils.EnumField; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public enum PostState implements EnumField { + RECRUITING("1", "모집중"), + COMPLETE("2", "모집완료"); + + private String code; + private String desc; +} diff --git a/src/main/java/org/gachon/checkmate/domain/post/repository/PostCustomRepository.java b/src/main/java/org/gachon/checkmate/domain/post/repository/PostCustomRepository.java index 3db1f83..9429d88 100644 --- a/src/main/java/org/gachon/checkmate/domain/post/repository/PostCustomRepository.java +++ b/src/main/java/org/gachon/checkmate/domain/post/repository/PostCustomRepository.java @@ -1,15 +1,15 @@ package org.gachon.checkmate.domain.post.repository; import org.gachon.checkmate.domain.post.dto.support.PostDetailDto; +import org.gachon.checkmate.domain.post.dto.support.PostPagingSearchCondition; import org.gachon.checkmate.domain.post.dto.support.PostSearchCondition; import org.gachon.checkmate.domain.post.dto.support.PostSearchDto; import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import java.util.Optional; public interface PostCustomRepository { Optional findPostDetail(Long postId); Page searchPosts(PostSearchCondition condition); - Page searchTextPost(String text, Pageable pageable); + Page searchPostsWithPaging(PostPagingSearchCondition condition); } diff --git a/src/main/java/org/gachon/checkmate/domain/post/repository/PostCustomRepositoryImpl.java b/src/main/java/org/gachon/checkmate/domain/post/repository/PostCustomRepositoryImpl.java index 08742b9..a219a0a 100644 --- a/src/main/java/org/gachon/checkmate/domain/post/repository/PostCustomRepositoryImpl.java +++ b/src/main/java/org/gachon/checkmate/domain/post/repository/PostCustomRepositoryImpl.java @@ -5,14 +5,13 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.gachon.checkmate.domain.member.entity.GenderType; +import org.gachon.checkmate.domain.member.entity.UserState; import org.gachon.checkmate.domain.post.dto.support.*; import org.gachon.checkmate.domain.post.entity.ImportantKeyType; 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 java.time.LocalDate; import java.util.List; import java.util.Optional; @@ -57,7 +56,8 @@ public Page searchPosts(PostSearchCondition condition) { post.endDate, post.scrapList.size(), postCheckList, - user.gender + user.gender, + post.postState )) .from(post) .leftJoin(post.postCheckList, postCheckList) @@ -65,17 +65,24 @@ public Page searchPosts(PostSearchCondition condition) { .where( eqImportantKey(condition.importantKeyType()), eqGenderType(condition.genderType()), - validatePostDate() + validateUserState() ) .fetch(); JPAQuery countQuery = queryFactory - .selectFrom(post); + .selectFrom(post) + .leftJoin(post.postCheckList, postCheckList) + .leftJoin(post.user, user) + .where( + eqImportantKey(condition.importantKeyType()), + eqGenderType(condition.genderType()), + validateUserState() + ); return PageableExecutionUtils.getPage(content, condition.pageable(), countQuery::fetchCount); } @Override - public Page searchTextPost(String text, Pageable pageable) { + public Page searchPostsWithPaging(PostPagingSearchCondition condition) { List content = queryFactory .select(new QPostSearchDto( post.id, @@ -86,22 +93,36 @@ public Page searchTextPost(String text, Pageable pageable) { post.endDate, post.scrapList.size(), postCheckList, - user.gender + user.gender, + post.postState )) .from(post) .leftJoin(post.postCheckList, postCheckList) .leftJoin(post.user, user) .where( - containTextCondition(text), - validatePostDate() + containTextCondition(condition.text()), + eqUserId(condition.selectedUser()), + validateUserState() ) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) + .offset(condition.pageable().getOffset()) + .limit(condition.pageable().getPageSize()) .fetch(); JPAQuery countQuery = queryFactory - .selectFrom(post); - return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchCount); + .selectFrom(post) + .from(post) + .leftJoin(post.postCheckList, postCheckList) + .leftJoin(post.user, user) + .where( + containTextCondition(condition.text()), + eqUserId(condition.selectedUser()), + validateUserState() + ); + return PageableExecutionUtils.getPage(content, condition.pageable(), countQuery::fetchCount); + } + + private BooleanExpression eqUserId(Long userId) { + return userId != null ? user.id.eq(userId) : null; } private BooleanExpression eqPostId(Long postId) { @@ -120,7 +141,7 @@ private BooleanExpression containTextCondition(String text) { return hasText(text) ? post.title.contains(text) : null; } - private BooleanExpression validatePostDate() { - return post.endDate.after(LocalDate.now()); + private BooleanExpression validateUserState() { + return user.userState.eq(UserState.JOIN); } } 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 index 68494a4..831bcef 100644 --- a/src/main/java/org/gachon/checkmate/domain/post/service/PostService.java +++ b/src/main/java/org/gachon/checkmate/domain/post/service/PostService.java @@ -14,11 +14,13 @@ import org.gachon.checkmate.domain.post.dto.response.PostSearchElementResponseDto; import org.gachon.checkmate.domain.post.dto.response.PostSearchResponseDto; import org.gachon.checkmate.domain.post.dto.support.PostDetailDto; +import org.gachon.checkmate.domain.post.dto.support.PostPagingSearchCondition; import org.gachon.checkmate.domain.post.dto.support.PostSearchCondition; import org.gachon.checkmate.domain.post.dto.support.PostSearchDto; import org.gachon.checkmate.domain.post.entity.Post; import org.gachon.checkmate.domain.post.entity.SortType; import org.gachon.checkmate.domain.post.repository.PostRepository; +import org.gachon.checkmate.domain.scrap.repository.ScrapRepository; import org.gachon.checkmate.global.error.exception.EntityNotFoundException; import org.gachon.checkmate.global.error.exception.InvalidValueException; import org.springframework.data.domain.Page; @@ -44,6 +46,7 @@ public class PostService { private final CheckListRepository checkListRepository; private final PostRepository postRepository; private final PostCheckListRepository postCheckListRepository; + private final ScrapRepository scrapRepository; public void createPost(Long userId, PostCreateRequestDto requestDto) { validateDuplicateTitle(requestDto.title()); @@ -53,6 +56,14 @@ public void createPost(Long userId, PostCreateRequestDto requestDto) { createPostCheckListAndSave(requestDto.checkList(), post); } + public PostSearchResponseDto getMyPosts(Long userId, Pageable pageable) { + CheckList checkList = getCheckList(userId); + PostPagingSearchCondition condition = PostPagingSearchCondition.searchSelectedUser(userId, pageable); + Page postSearchList = getTextSearchResults(condition); + List searchResults = createPostSearchResponseDto(postSearchList, checkList); + return PostSearchResponseDto.of(searchResults, postSearchList.getTotalPages(), postSearchList.getTotalElements()); + } + public PostSearchResponseDto getAllPosts(Long userId, String key, String type, String gender, Pageable pageable) { CheckList checkList = getCheckList(userId); PostSearchCondition condition = PostSearchCondition.of(type, key, gender, pageable); @@ -64,15 +75,17 @@ public PostSearchResponseDto getAllPosts(Long userId, String key, String type, S return PostSearchResponseDto.of(pagingSearchResults, postSearchList.getTotalPages(), postSearchList.getTotalElements()); } - public PostDetailResponseDto getPostDetails(Long postId) { + public PostDetailResponseDto getPostDetails(Long userId, Long postId) { PostDetailDto postDetailDto = getPostDetailDto(postId); CheckListResponseDto checkListResponseDto = createCheckListResponseDto(postDetailDto.postCheckList()); - return PostDetailResponseDto.of(postDetailDto, checkListResponseDto); + boolean isScrapPost = existPostInScrap(postId, userId); + return PostDetailResponseDto.of(postDetailDto, checkListResponseDto, isScrapPost); } public PostSearchResponseDto searchTextPost(Long userId, String text, Pageable pageable) { CheckList checkList = getCheckList(userId); - Page postSearchList = getTextSearchResults(text, pageable); + PostPagingSearchCondition condition = PostPagingSearchCondition.searchText(text, pageable); + Page postSearchList = getTextSearchResults(condition); List searchResults = createPostSearchResponseDto(postSearchList, checkList); return PostSearchResponseDto.of(searchResults, postSearchList.getTotalPages(), postSearchList.getTotalElements()); } @@ -149,18 +162,17 @@ private Post createPostAndSave(PostCreateRequestDto postCreateRequestDto, User u return post; } - private PostCheckList createPostCheckListAndSave(CheckListRequestDto checkListRequestDto, Post post) { + private void createPostCheckListAndSave(CheckListRequestDto checkListRequestDto, Post post) { PostCheckList postCheckList = PostCheckList.createPostCheckList(checkListRequestDto, post); postCheckListRepository.save(postCheckList); - return postCheckList; } private Page getSearchResults(PostSearchCondition condition) { return postRepository.searchPosts(condition); } - private Page getTextSearchResults(String text, Pageable pageable) { - return postRepository.searchTextPost(text, pageable); + private Page getTextSearchResults(PostPagingSearchCondition condition) { + return postRepository.searchPostsWithPaging(condition); } private CheckList getCheckList(Long userId) { @@ -177,4 +189,8 @@ private User getUserOrThrow(Long userId) { return userRepository.findById(userId) .orElseThrow(() -> new EntityNotFoundException(USER_NOT_FOUND)); } + + private boolean existPostInScrap(Long postId, Long userId) { + return scrapRepository.existsByPostIdAndUserId(postId, userId); + } } diff --git a/src/main/java/org/gachon/checkmate/domain/scrap/controller/ScrapController.java b/src/main/java/org/gachon/checkmate/domain/scrap/controller/ScrapController.java new file mode 100644 index 0000000..b317418 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/scrap/controller/ScrapController.java @@ -0,0 +1,39 @@ +package org.gachon.checkmate.domain.scrap.controller; + +import lombok.RequiredArgsConstructor; +import org.gachon.checkmate.domain.post.dto.response.PostSearchResponseDto; +import org.gachon.checkmate.domain.scrap.dto.request.ScrapRequestDto; +import org.gachon.checkmate.domain.scrap.service.ScrapService; +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.*; + +@RequiredArgsConstructor +@RequestMapping("/api/scrap") +@RestController +public class ScrapController { + private final ScrapService scrapService; + + @GetMapping + public ResponseEntity> getScrapPosts(@UserId final Long userId, + final Pageable pageable) { + final PostSearchResponseDto responseDto = scrapService.getScrapPosts(userId, pageable); + return SuccessResponse.ok(responseDto); + } + + @PostMapping + public ResponseEntity> createScrapPost(@UserId final Long userId, + @RequestBody final ScrapRequestDto requestDto) { + scrapService.creatScrapPost(userId, requestDto); + return SuccessResponse.created(null); + } + + @DeleteMapping("/{id}") + public ResponseEntity> deleteScrapPost(@UserId final Long userId, + @PathVariable("id") final Long scrapId) { + scrapService.deleteScrapPost(userId, scrapId); + return SuccessResponse.ok(null); + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/scrap/dto/request/ScrapRequestDto.java b/src/main/java/org/gachon/checkmate/domain/scrap/dto/request/ScrapRequestDto.java new file mode 100644 index 0000000..2cab650 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/scrap/dto/request/ScrapRequestDto.java @@ -0,0 +1,6 @@ +package org.gachon.checkmate.domain.scrap.dto.request; + +public record ScrapRequestDto( + Long postId +) { +} diff --git a/src/main/java/org/gachon/checkmate/domain/scrap/dto/support/ScrapSearchCondition.java b/src/main/java/org/gachon/checkmate/domain/scrap/dto/support/ScrapSearchCondition.java new file mode 100644 index 0000000..d18ed1d --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/scrap/dto/support/ScrapSearchCondition.java @@ -0,0 +1,17 @@ +package org.gachon.checkmate.domain.scrap.dto.support; + +import lombok.Builder; +import org.springframework.data.domain.Pageable; + +@Builder +public record ScrapSearchCondition( + Long userId, + Pageable pageable +) { + public static ScrapSearchCondition of(Long userId, Pageable pageable) { + return ScrapSearchCondition.builder() + .userId(userId) + .pageable(pageable) + .build(); + } +} diff --git a/src/main/java/org/gachon/checkmate/domain/scrap/entity/Scrap.java b/src/main/java/org/gachon/checkmate/domain/scrap/entity/Scrap.java index b4b4212..fbc0206 100644 --- a/src/main/java/org/gachon/checkmate/domain/scrap/entity/Scrap.java +++ b/src/main/java/org/gachon/checkmate/domain/scrap/entity/Scrap.java @@ -22,4 +22,14 @@ public class Scrap extends BaseTimeEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_id") private Post post; + + public static Scrap createScrap(User user, Post post) { + Scrap scrap = Scrap.builder() + .user(user) + .post(post) + .build(); + user.addScrap(scrap); + post.addScrap(scrap); + return scrap; + } } diff --git a/src/main/java/org/gachon/checkmate/domain/scrap/repository/ScrapCustomRepository.java b/src/main/java/org/gachon/checkmate/domain/scrap/repository/ScrapCustomRepository.java new file mode 100644 index 0000000..ec98b3c --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/scrap/repository/ScrapCustomRepository.java @@ -0,0 +1,9 @@ +package org.gachon.checkmate.domain.scrap.repository; + +import org.gachon.checkmate.domain.post.dto.support.PostSearchDto; +import org.gachon.checkmate.domain.scrap.dto.support.ScrapSearchCondition; +import org.springframework.data.domain.Page; + +public interface ScrapCustomRepository { + Page searchMyScrapPosts(ScrapSearchCondition condition); +} diff --git a/src/main/java/org/gachon/checkmate/domain/scrap/repository/ScrapCustomRepositoryImpl.java b/src/main/java/org/gachon/checkmate/domain/scrap/repository/ScrapCustomRepositoryImpl.java new file mode 100644 index 0000000..47799b8 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/scrap/repository/ScrapCustomRepositoryImpl.java @@ -0,0 +1,74 @@ +package org.gachon.checkmate.domain.scrap.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.member.entity.UserState; +import org.gachon.checkmate.domain.post.dto.support.PostSearchDto; +import org.gachon.checkmate.domain.post.dto.support.QPostSearchDto; +import org.gachon.checkmate.domain.scrap.dto.support.ScrapSearchCondition; +import org.gachon.checkmate.domain.scrap.entity.Scrap; +import org.springframework.data.domain.Page; +import org.springframework.data.support.PageableExecutionUtils; + +import java.util.List; + +import static org.gachon.checkmate.domain.checkList.entity.QPostCheckList.postCheckList; +import static org.gachon.checkmate.domain.member.entity.QUser.user; +import static org.gachon.checkmate.domain.post.entity.QPost.post; +import static org.gachon.checkmate.domain.scrap.entity.QScrap.scrap; + +@RequiredArgsConstructor +public class ScrapCustomRepositoryImpl implements ScrapCustomRepository { + private final JPAQueryFactory queryFactory; + + @Override + public Page searchMyScrapPosts(ScrapSearchCondition condition) { + List content = queryFactory + .select(new QPostSearchDto( + post.id, + post.title, + post.content, + post.importantKeyType, + post.similarityKeyType, + post.endDate, + post.scrapList.size(), + postCheckList, + user.gender, + post.postState + )) + .from(scrap) + .leftJoin(scrap.post, post) + .leftJoin(scrap.post.postCheckList, postCheckList) + .leftJoin(scrap.user, user) + .where( + eqUserId(condition.userId()), + validateUserState() + ) + .offset(condition.pageable().getOffset()) + .limit(condition.pageable().getPageSize()) + .fetch(); + + JPAQuery countQuery = queryFactory + .selectFrom(scrap) + .leftJoin(scrap.post, post) + .leftJoin(scrap.post.postCheckList, postCheckList) + .leftJoin(scrap.user, user) + .where( + eqUserId(condition.userId()), + validateUserState() + ); + + return PageableExecutionUtils.getPage(content, condition.pageable(), countQuery::fetchCount); + } + + private BooleanExpression eqUserId(Long userId) { + return userId != null ? user.id.eq(userId) : null; + } + + private BooleanExpression validateUserState() { + return user.userState.eq(UserState.JOIN); + } + +} diff --git a/src/main/java/org/gachon/checkmate/domain/scrap/repository/ScrapRepository.java b/src/main/java/org/gachon/checkmate/domain/scrap/repository/ScrapRepository.java new file mode 100644 index 0000000..f413ab9 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/scrap/repository/ScrapRepository.java @@ -0,0 +1,9 @@ +package org.gachon.checkmate.domain.scrap.repository; + +import org.gachon.checkmate.domain.scrap.entity.Scrap; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ScrapRepository extends JpaRepository, ScrapCustomRepository { + void deleteByIdAndUserId(Long scrapId, Long userId); + boolean existsByPostIdAndUserId(Long postId, Long userId); +} diff --git a/src/main/java/org/gachon/checkmate/domain/scrap/service/ScrapService.java b/src/main/java/org/gachon/checkmate/domain/scrap/service/ScrapService.java new file mode 100644 index 0000000..e352f31 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/scrap/service/ScrapService.java @@ -0,0 +1,110 @@ +package org.gachon.checkmate.domain.scrap.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.member.entity.User; +import org.gachon.checkmate.domain.member.repository.UserRepository; +import org.gachon.checkmate.domain.post.dto.response.PostSearchElementResponseDto; +import org.gachon.checkmate.domain.post.dto.response.PostSearchResponseDto; +import org.gachon.checkmate.domain.post.dto.support.PostSearchDto; +import org.gachon.checkmate.domain.post.entity.Post; +import org.gachon.checkmate.domain.post.repository.PostRepository; +import org.gachon.checkmate.domain.scrap.dto.request.ScrapRequestDto; +import org.gachon.checkmate.domain.scrap.dto.support.ScrapSearchCondition; +import org.gachon.checkmate.domain.scrap.entity.Scrap; +import org.gachon.checkmate.domain.scrap.repository.ScrapRepository; +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.*; + +@RequiredArgsConstructor +@Transactional +@Service +public class ScrapService { + private final ScrapRepository scrapRepository; + private final UserRepository userRepository; + private final PostRepository postRepository; + private final CheckListRepository checkListRepository; + + public PostSearchResponseDto getScrapPosts(Long userId, Pageable pageable) { + CheckList checkList = getCheckList(userId); + ScrapSearchCondition condition = ScrapSearchCondition.of(userId, pageable); + Page myScrapPosts = findMyScrapPosts(condition); + List searchResults = createPostSearchResponseDto(myScrapPosts, checkList); + return PostSearchResponseDto.of(searchResults, myScrapPosts.getTotalPages(), myScrapPosts.getTotalElements()); + } + + public void creatScrapPost(Long userId, ScrapRequestDto scrapRequestDto) { + User user = findUserOrThrow(userId); + Post post = findPostOrThrow(scrapRequestDto.postId()); + createScrapAndSave(user, post); + } + + public void deleteScrapPost(Long userId, Long scrapId) { + scrapRepository.deleteByIdAndUserId(scrapId, userId); + } + + private List createPostSearchResponseDto(Page postSearchDtoList, CheckList checkList) { + return postSearchDtoList.stream() + .map(postSearchDto -> + PostSearchElementResponseDto.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) LocalDate.now().until(endDate, ChronoUnit.DAYS); + } + + private void createScrapAndSave(User user, Post post) { + Scrap scrap = Scrap.createScrap(user, post); + scrapRepository.save(scrap); + } + + private CheckList getCheckList(Long userId) { + return checkListRepository.findByUserId(userId) + .orElseThrow(() -> new EntityNotFoundException(CHECK_LIST_NOT_FOUND)); + } + + private Page findMyScrapPosts(ScrapSearchCondition condition) { + return scrapRepository.searchMyScrapPosts(condition); + } + + private User findUserOrThrow(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException(USER_NOT_FOUND)); + } + + private Post findPostOrThrow(Long postId) { + return postRepository.findById(postId) + .orElseThrow(() -> new EntityNotFoundException(POST_NOT_FOUND)); + } +}