diff --git a/build.gradle b/build.gradle index a0b7664..8b8377c 100644 --- a/build.gradle +++ b/build.gradle @@ -71,6 +71,9 @@ dependencies { // redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + // validate + implementation 'org.springframework.boot:spring-boot-starter-validation' } tasks.named('test') { @@ -93,4 +96,4 @@ sourceSets { // gradle clean 시에 QClass 디렉토리 삭제 clean { delete file(generated) -} +} \ No newline at end of file diff --git a/src/main/java/org/gachon/checkmate/domain/checkList/entity/PostCheckList.java b/src/main/java/org/gachon/checkmate/domain/checkList/entity/PostCheckList.java index 6f2d0f4..abc195e 100644 --- a/src/main/java/org/gachon/checkmate/domain/checkList/entity/PostCheckList.java +++ b/src/main/java/org/gachon/checkmate/domain/checkList/entity/PostCheckList.java @@ -3,6 +3,7 @@ import jakarta.persistence.*; import lombok.*; import org.gachon.checkmate.domain.checkList.converter.*; +import org.gachon.checkmate.domain.checkList.dto.request.CheckListRequestDto; import org.gachon.checkmate.domain.post.entity.Post; import org.gachon.checkmate.global.common.BaseTimeEntity; @@ -31,4 +32,18 @@ public class PostCheckList extends BaseTimeEntity { @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_id") private Post post; + + public static PostCheckList createPostCheckList(CheckListRequestDto checkListRequestDto, Post post) { + PostCheckList checkList = PostCheckList.builder() + .cleanType(checkListRequestDto.cleanType()) + .drinkType(checkListRequestDto.drinkType()) + .homeType(checkListRequestDto.homeType()) + .lifePatterType(checkListRequestDto.lifePatterType()) + .noiseType(checkListRequestDto.noiseType()) + .sleepType(checkListRequestDto.sleepType()) + .post(post) + .build(); + post.setPostCheckList(checkList); + return checkList; + } } \ No newline at end of file diff --git a/src/main/java/org/gachon/checkmate/domain/checkList/repository/PostCheckListRepository.java b/src/main/java/org/gachon/checkmate/domain/checkList/repository/PostCheckListRepository.java new file mode 100644 index 0000000..b338f41 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/checkList/repository/PostCheckListRepository.java @@ -0,0 +1,7 @@ +package org.gachon.checkmate.domain.checkList.repository; + +import org.gachon.checkmate.domain.checkList.entity.PostCheckList; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PostCheckListRepository extends JpaRepository { +} 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 12863c7..dcfb23d 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,7 +5,6 @@ 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.post.converter.RoomTypeConverter; import org.gachon.checkmate.domain.post.entity.Post; import org.gachon.checkmate.domain.scrap.entity.Scrap; import org.gachon.checkmate.global.common.BaseTimeEntity; @@ -43,7 +42,7 @@ public class User extends BaseTimeEntity { @Builder.Default private List scrapList = new ArrayList<>(); - public static User createUser(String email, String storedPassword, String name, String school, String major, MbtiType mbti, GenderType gender, String profile){ + public static User createUser(String email, String storedPassword, String name, String school, String major, MbtiType mbti, GenderType gender, String profile) { return User.builder() .email(email) .password(storedPassword) @@ -64,7 +63,11 @@ public void setCheckList(CheckList checkList) { this.checkList = checkList; } - public void setProfile(String profile){ + public void setProfile(String profile) { this.profile = profile; } + + public void addPost(Post post) { + this.postList.add(post); + } } 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 83eb3f2..c55a770 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 @@ -1,6 +1,8 @@ package org.gachon.checkmate.domain.post.controller; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.gachon.checkmate.domain.post.dto.request.PostCreateRequestDto; import org.gachon.checkmate.domain.post.dto.response.PostDetailResponseDto; import org.gachon.checkmate.domain.post.dto.response.PostSearchResponseDto; import org.gachon.checkmate.domain.post.service.PostService; @@ -46,4 +48,11 @@ public ResponseEntity> searchKeyWordPost(@UserId final Long u final PostSearchResponseDto responseDto = postService.searchKeyWordPost(userId, key, type, pageable); return SuccessResponse.ok(responseDto); } + + @PostMapping + public ResponseEntity> createPost(@UserId final Long userId, + @Valid @RequestBody final PostCreateRequestDto requestDto) { + postService.createPost(userId, requestDto); + return SuccessResponse.ok(null); + } } diff --git a/src/main/java/org/gachon/checkmate/domain/post/dto/request/PostCreateRequestDto.java b/src/main/java/org/gachon/checkmate/domain/post/dto/request/PostCreateRequestDto.java new file mode 100644 index 0000000..8f0d5e3 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/post/dto/request/PostCreateRequestDto.java @@ -0,0 +1,23 @@ +package org.gachon.checkmate.domain.post.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import lombok.NonNull; +import org.gachon.checkmate.domain.checkList.dto.request.CheckListRequestDto; +import org.gachon.checkmate.domain.post.entity.ImportantKeyType; +import org.gachon.checkmate.domain.post.entity.RoomType; +import org.gachon.checkmate.domain.post.entity.SimilarityKeyType; + +import java.time.LocalDate; + +public record PostCreateRequestDto( + @NotBlank(message = "제목을 입력해주세요") String title, + @NotBlank(message = "내용을 입력해주세요") String content, + @NotNull(message = "중요 키워드를 입력해주세요") ImportantKeyType importantKey, + @NotNull(message = "유사도를 입력해주세요") SimilarityKeyType similarityKey, + @NotNull(message = "기숙사 유형을 입력해주세요") RoomType roomType, + @NotNull(message = "모집 마감기간을 입력해주세요") LocalDate endDate, + @NotNull(message = "체크리스트를 입력해주세요") CheckListRequestDto checkList +) { +} 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 3533425..e38dfe1 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 @@ -3,10 +3,11 @@ import jakarta.persistence.*; import lombok.*; import org.gachon.checkmate.domain.checkList.entity.PostCheckList; -import org.gachon.checkmate.domain.post.converter.RoomTypeConverter; import org.gachon.checkmate.domain.member.entity.User; import org.gachon.checkmate.domain.post.converter.ImportantKeyTypeConverter; +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; import org.gachon.checkmate.domain.scrap.entity.Scrap; import org.gachon.checkmate.global.common.BaseTimeEntity; @@ -41,4 +42,22 @@ public class Post extends BaseTimeEntity { @OneToMany(mappedBy = "post") @Builder.Default private List scrapList = new ArrayList<>(); + + public static Post createPost(PostCreateRequestDto postCreateRequestDto, User user) { + Post post = Post.builder() + .title(postCreateRequestDto.title()) + .content(postCreateRequestDto.content()) + .endDate(postCreateRequestDto.endDate()) + .roomType(postCreateRequestDto.roomType()) + .importantKeyType(postCreateRequestDto.importantKey()) + .similarityKeyType(postCreateRequestDto.similarityKey()) + .user(user) + .build(); + user.addPost(post); + return post; + } + + public void setPostCheckList(PostCheckList postCheckList) { + this.postCheckList = postCheckList; + } } 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 index 615e421..197cb9b 100644 --- a/src/main/java/org/gachon/checkmate/domain/post/repository/PostQuerydslRepository.java +++ b/src/main/java/org/gachon/checkmate/domain/post/repository/PostQuerydslRepository.java @@ -42,7 +42,7 @@ public Optional findPostDetail(Long postId) { .leftJoin(post.postCheckList, postCheckList) .leftJoin(post.user, user) .where( - containPostId(postId) + eqPostId(postId) ) .fetchOne()); } @@ -123,7 +123,7 @@ public Page searchTextPost(String text, Pageable pageable) { return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchCount); } - private BooleanExpression containPostId(Long postId) { + private BooleanExpression eqPostId(Long postId) { return post.id.eq(postId); } diff --git a/src/main/java/org/gachon/checkmate/domain/post/repository/PostRepository.java b/src/main/java/org/gachon/checkmate/domain/post/repository/PostRepository.java new file mode 100644 index 0000000..6441740 --- /dev/null +++ b/src/main/java/org/gachon/checkmate/domain/post/repository/PostRepository.java @@ -0,0 +1,10 @@ +package org.gachon.checkmate.domain.post.repository; + +import org.gachon.checkmate.domain.post.entity.Post; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface PostRepository extends JpaRepository { + boolean existsByTitle(String title); +} 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 a15ee79..1109ccc 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 @@ -1,19 +1,27 @@ package org.gachon.checkmate.domain.post.service; import lombok.RequiredArgsConstructor; +import org.gachon.checkmate.domain.checkList.dto.request.CheckListRequestDto; import org.gachon.checkmate.domain.checkList.dto.response.CheckListResponseDto; 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.checkList.repository.PostCheckListRepository; +import org.gachon.checkmate.domain.member.entity.User; +import org.gachon.checkmate.domain.member.repository.UserRepository; +import org.gachon.checkmate.domain.post.dto.request.PostCreateRequestDto; import org.gachon.checkmate.domain.post.dto.response.PostDetailResponseDto; 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.PostSearchDto; import org.gachon.checkmate.domain.post.entity.ImportantKeyType; +import org.gachon.checkmate.domain.post.entity.Post; import org.gachon.checkmate.domain.post.entity.SortType; import org.gachon.checkmate.domain.post.repository.PostQuerydslRepository; +import org.gachon.checkmate.domain.post.repository.PostRepository; import org.gachon.checkmate.global.error.exception.EntityNotFoundException; +import org.gachon.checkmate.global.error.exception.InvalidValueException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -26,8 +34,7 @@ import java.util.Objects; import java.util.stream.Collectors; -import static org.gachon.checkmate.global.error.ErrorCode.CHECK_LIST_NOT_FOUND; -import static org.gachon.checkmate.global.error.ErrorCode.POST_NOT_FOUND; +import static org.gachon.checkmate.global.error.ErrorCode.*; import static org.gachon.checkmate.global.utils.EnumValueUtils.toEntityCode; import static org.gachon.checkmate.global.utils.PagingUtils.convertPaging; @@ -36,8 +43,18 @@ @Transactional @Service public class PostService { + private final UserRepository userRepository; private final CheckListRepository checkListRepository; + private final PostRepository postRepository; private final PostQuerydslRepository postQuerydslRepository; + private final PostCheckListRepository postCheckListRepository; + + public void createPost(Long userId, PostCreateRequestDto requestDto) { + validateDuplicateTitle(requestDto.title()); + User user = getUserOrThrow(userId); + Post post = createPostAndSave(requestDto, user); + PostCheckList postCheckList = createPostCheckListAndSave(requestDto.checkList(), post); + } public PostSearchResponseDto getAllPosts(Long userId, String type, Pageable pageable) { CheckList checkList = getCheckList(userId); @@ -130,6 +147,23 @@ private int getRemainDate(LocalDate endDate) { return (int) endDate.until(LocalDate.now(), ChronoUnit.DAYS); } + private void validateDuplicateTitle(String title) { + if (postRepository.existsByTitle(title)) + throw new InvalidValueException(INVALID_POST_TITLE); + } + + private Post createPostAndSave(PostCreateRequestDto postCreateRequestDto, User user) { + Post post = Post.createPost(postCreateRequestDto, user); + postRepository.save(post); + return post; + } + + private PostCheckList createPostCheckListAndSave(CheckListRequestDto checkListRequestDto, Post post) { + PostCheckList postCheckList = PostCheckList.createPostCheckList(checkListRequestDto, post); + postCheckListRepository.save(postCheckList); + return postCheckList; + } + private Page getAllPostsResults(Pageable pageable) { return postQuerydslRepository.findAllPosts(pageable); } @@ -151,4 +185,9 @@ private PostDetailDto getPostDetailDto(Long postId) { return postQuerydslRepository.findPostDetail(postId) .orElseThrow(() -> new EntityNotFoundException(POST_NOT_FOUND)); } + + private User getUserOrThrow(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException(USER_NOT_FOUND)); + } } 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 f35f849..02fdbe8 100644 --- a/src/main/java/org/gachon/checkmate/global/error/ErrorCode.java +++ b/src/main/java/org/gachon/checkmate/global/error/ErrorCode.java @@ -15,6 +15,7 @@ public enum ErrorCode { INVALID_ENUM_CODE(HttpStatus.BAD_REQUEST, "잘못된 Enum class code 입니다."), INVALID_PAGING_SIZE(HttpStatus.BAD_REQUEST, "잘못된 Paging 크기입니다."), INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "비밀번호는 8~20자 대소문자 영문, 숫자, 특수문자의 조합이어야 합니다."), + INVALID_POST_TITLE(HttpStatus.BAD_REQUEST, "이미 존재하는 게시물입니다."), /** * 401 Unauthorized