From d04c17ca0a1b373a6bf7bdff977c3cb2e10a6809 Mon Sep 17 00:00:00 2001 From: junhokim Date: Fri, 28 Mar 2025 22:26:01 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EC=9E=90=EC=9C=A0=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=ED=8C=90=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/application/in/CrewService.java | 2 +- .../crew/application/in/PostService.java | 70 +++++++++++++ .../in/request/CreatePostRequest.java | 23 +++++ .../in/request/UpdatePostRequest.java | 27 +++++ .../in/response/CreatePostResponse.java | 30 ++++++ .../in/response/DeletePostResponse.java | 17 ++++ .../in/response/UpdatePostResponse.java | 29 ++++++ .../in/usecase/ManageCrewUseCase.java | 2 + .../in/usecase/ManagePostUseCase.java | 21 ++++ .../repository/CrewMemberQueryRepository.java | 10 ++ .../out/repository/PostRepository.java | 12 +++ .../retrip/crew/domain/entity/BaseEntity.java | 2 + .../retrip/crew/domain/entity/CrewMember.java | 8 ++ .../com/retrip/crew/domain/entity/Post.java | 39 +++++++- .../CrewMemberNotFoundException.java | 12 +++ .../domain/exception/PostDeleteException.java | 13 +++ .../exception/PostNotFoundException.java | 12 +++ .../domain/exception/PostUpdateException.java | 12 +++ .../domain/exception/common/ErrorCode.java | 6 +- .../in/presentation/rest/PostController.java | 55 +++++++++++ .../query/CrewMemberQuerydslRepository.java | 28 ++++++ .../crew/application/in/PostServiceTest.java | 99 +++++++++++++++++++ .../in/factory/PostServiceTestFactory.java | 50 ++++++++++ .../crew/common/config/QuerydslConfig.java | 8 ++ .../crew/common/fixture/PostFixture.java | 22 +++++ .../retrip/crew/domain/entity/PostTest.java | 71 +++++++++++++ 26 files changed, 677 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/retrip/crew/application/in/PostService.java create mode 100644 src/main/java/com/retrip/crew/application/in/request/CreatePostRequest.java create mode 100644 src/main/java/com/retrip/crew/application/in/request/UpdatePostRequest.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/CreatePostResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/DeletePostResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/UpdatePostResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/usecase/ManagePostUseCase.java create mode 100644 src/main/java/com/retrip/crew/application/out/repository/CrewMemberQueryRepository.java create mode 100644 src/main/java/com/retrip/crew/application/out/repository/PostRepository.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/CrewMemberNotFoundException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/PostDeleteException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/PostNotFoundException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/PostUpdateException.java create mode 100644 src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/PostController.java create mode 100644 src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewMemberQuerydslRepository.java create mode 100644 src/test/java/com/retrip/crew/application/in/PostServiceTest.java create mode 100644 src/test/java/com/retrip/crew/application/in/factory/PostServiceTestFactory.java create mode 100644 src/test/java/com/retrip/crew/common/fixture/PostFixture.java create mode 100644 src/test/java/com/retrip/crew/domain/entity/PostTest.java diff --git a/src/main/java/com/retrip/crew/application/in/CrewService.java b/src/main/java/com/retrip/crew/application/in/CrewService.java index ed978f0..9a7453f 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -92,7 +92,7 @@ public CrewDetailResponse getCrewDetail(UUID crewId) { return CrewDetailResponse.of(crew, memberCount); } - private Crew findById(UUID crewId){ + public Crew findById(UUID crewId){ return crewRepository.findById(crewId) .orElseThrow(CrewNotFoundException::new); } diff --git a/src/main/java/com/retrip/crew/application/in/PostService.java b/src/main/java/com/retrip/crew/application/in/PostService.java new file mode 100644 index 0000000..9dd0eb2 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/PostService.java @@ -0,0 +1,70 @@ +package com.retrip.crew.application.in; + +import com.retrip.crew.application.in.request.CreatePostRequest; +import com.retrip.crew.application.in.request.UpdatePostRequest; +import com.retrip.crew.application.in.response.CreatePostResponse; +import com.retrip.crew.application.in.response.DeletePostResponse; +import com.retrip.crew.application.in.response.UpdatePostResponse; +import com.retrip.crew.application.in.usecase.ManagePostUseCase; +import com.retrip.crew.application.out.repository.CrewMemberQueryRepository; +import com.retrip.crew.application.out.repository.PostRepository; +import com.retrip.crew.domain.entity.Crew; +import com.retrip.crew.domain.entity.CrewMember; +import com.retrip.crew.domain.entity.Post; +import com.retrip.crew.domain.exception.CrewMemberNotFoundException; +import com.retrip.crew.domain.exception.PostNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class PostService implements ManagePostUseCase { + private final CrewService crewService; + private final PostRepository postRepository; + private final CrewMemberQueryRepository crewMemberQueryRepository; + + + + @Override + @Transactional + public CreatePostResponse createPost(UUID crewId, CreatePostRequest request) { + Crew crew = crewService.findById(crewId); + Post post = postRepository.save(request.to(crew)); + return CreatePostResponse.of(post); + } + + @Override + @Transactional + public UpdatePostResponse updatePost(UUID postId, UpdatePostRequest request) { + Post post = postRepository.findById(postId).orElseThrow(PostNotFoundException::new); + post.update(request.title(), request.content(), request.userId()); + return UpdatePostResponse.of(post); + } + + @Override + @Transactional + public DeletePostResponse deletePost(UUID crewId, UUID postId, UUID userId) { + CrewMember crewMember = findCrewMemberByUserId(crewId, userId); + Post post = findPostById(postId); + if (post.isDeletable(crewMember)) { + postRepository.deleteById(postId); + return DeletePostResponse.of(postId); + } + throw new PostNotFoundException(); + } + + private CrewMember findCrewMemberByUserId(UUID crewId, UUID userId) { + return crewMemberQueryRepository.findCrewMemberByUserId(crewId, userId) + .orElseThrow(CrewMemberNotFoundException::new); + } + + + private Post findPostById(UUID postId) { + return postRepository.findById(postId) + .orElseThrow(PostNotFoundException::new); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/request/CreatePostRequest.java b/src/main/java/com/retrip/crew/application/in/request/CreatePostRequest.java new file mode 100644 index 0000000..d466e25 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/request/CreatePostRequest.java @@ -0,0 +1,23 @@ +package com.retrip.crew.application.in.request; + +import com.retrip.crew.domain.entity.Crew; +import com.retrip.crew.domain.entity.Post; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; + + +@Schema(description = "자유 게시글 생성 Request") +public record CreatePostRequest( + @Schema(description = "게시글 제목") + @Size(min = 1, max = 30) + String title, + + @Schema(description = "게시글 내용") + @Size(min = 1, max = 500) + String content +) { + + public Post to(Crew crew) { + return Post.create(title, content, crew); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/request/UpdatePostRequest.java b/src/main/java/com/retrip/crew/application/in/request/UpdatePostRequest.java new file mode 100644 index 0000000..7b08d9f --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/request/UpdatePostRequest.java @@ -0,0 +1,27 @@ +package com.retrip.crew.application.in.request; + +import com.retrip.crew.domain.entity.Crew; +import com.retrip.crew.domain.entity.Post; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +import java.util.UUID; + + +@Schema(description = "자유 게시글 생성 Request") +public record UpdatePostRequest( + @Schema(description = "게시글 제목") + @Size(min = 1, max = 30) + String title, + + @Schema(description = "게시글 내용") + @Size(min = 1, max = 500) + String content, + + @Schema(description = "수정자") + @NotNull + UUID userId +) { + +} diff --git a/src/main/java/com/retrip/crew/application/in/response/CreatePostResponse.java b/src/main/java/com/retrip/crew/application/in/response/CreatePostResponse.java new file mode 100644 index 0000000..99f2e4f --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/CreatePostResponse.java @@ -0,0 +1,30 @@ +package com.retrip.crew.application.in.response; + +import com.retrip.crew.domain.entity.Crew; +import com.retrip.crew.domain.entity.Post; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.UUID; + +public record CreatePostResponse( + @Schema(description = "자유 게시글 ID") + UUID id, + + @Schema(description = "작성자") + UUID createAt, + + @Schema(description = "게시글 제목") + String title, + + @Schema(description = "게시글 내용") + String content +) { + public static CreatePostResponse of(Post post) { + return new CreatePostResponse( + post.getId(), + UUID.randomUUID(), //todo: CreateAt 생성시, 변경 예정 + post.getTitle().getValue(), + post.getContent().getValue() + ); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/response/DeletePostResponse.java b/src/main/java/com/retrip/crew/application/in/response/DeletePostResponse.java new file mode 100644 index 0000000..cb3fa4e --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/DeletePostResponse.java @@ -0,0 +1,17 @@ +package com.retrip.crew.application.in.response; + +import com.retrip.crew.domain.entity.Post; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.UUID; + +public record DeletePostResponse( + @Schema(description = "자유 게시글 ID") + UUID id +) { + public static DeletePostResponse of(UUID id) { + return new DeletePostResponse( + id + ); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/response/UpdatePostResponse.java b/src/main/java/com/retrip/crew/application/in/response/UpdatePostResponse.java new file mode 100644 index 0000000..728398e --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/UpdatePostResponse.java @@ -0,0 +1,29 @@ +package com.retrip.crew.application.in.response; + +import com.retrip.crew.domain.entity.Post; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.UUID; + +public record UpdatePostResponse( + @Schema(description = "자유 게시글 ID") + UUID id, + + @Schema(description = "작성자") + UUID createAt, + + @Schema(description = "게시글 제목") + String title, + + @Schema(description = "게시글 내용") + String content +) { + public static UpdatePostResponse of(Post post) { + return new UpdatePostResponse( + post.getId(), + UUID.randomUUID(), //todo: CreateAt 생성시, 변경 예정 + post.getTitle().getValue(), + post.getContent().getValue() + ); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/usecase/ManageCrewUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManageCrewUseCase.java index b8fb910..517a61a 100644 --- a/src/main/java/com/retrip/crew/application/in/usecase/ManageCrewUseCase.java +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManageCrewUseCase.java @@ -5,6 +5,8 @@ import com.retrip.crew.application.in.response.CrewCreateResponse; import com.retrip.crew.application.in.response.CrewUpdateResponse; +import com.retrip.crew.domain.entity.Crew; + import java.util.UUID; public interface ManageCrewUseCase { diff --git a/src/main/java/com/retrip/crew/application/in/usecase/ManagePostUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManagePostUseCase.java new file mode 100644 index 0000000..9b3a6ae --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManagePostUseCase.java @@ -0,0 +1,21 @@ +package com.retrip.crew.application.in.usecase; + +import com.retrip.crew.application.in.request.CreatePostRequest; +import com.retrip.crew.application.in.request.UpdatePostRequest; +import com.retrip.crew.application.in.response.CreatePostResponse; + +import com.retrip.crew.application.in.response.DeletePostResponse; + +import com.retrip.crew.application.in.response.UpdatePostResponse; + +import java.util.UUID; + +public interface ManagePostUseCase { + CreatePostResponse createPost(UUID crewId, CreatePostRequest request); + + UpdatePostResponse updatePost(UUID postId, UpdatePostRequest request); + + DeletePostResponse deletePost(UUID crewId, UUID postId, UUID userId); + + +} diff --git a/src/main/java/com/retrip/crew/application/out/repository/CrewMemberQueryRepository.java b/src/main/java/com/retrip/crew/application/out/repository/CrewMemberQueryRepository.java new file mode 100644 index 0000000..84093fc --- /dev/null +++ b/src/main/java/com/retrip/crew/application/out/repository/CrewMemberQueryRepository.java @@ -0,0 +1,10 @@ +package com.retrip.crew.application.out.repository; + +import com.retrip.crew.domain.entity.CrewMember; + +import java.util.Optional; +import java.util.UUID; + +public interface CrewMemberQueryRepository { + Optional findCrewMemberByUserId(UUID crewId, UUID userId); +} diff --git a/src/main/java/com/retrip/crew/application/out/repository/PostRepository.java b/src/main/java/com/retrip/crew/application/out/repository/PostRepository.java new file mode 100644 index 0000000..a71e060 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/out/repository/PostRepository.java @@ -0,0 +1,12 @@ +package com.retrip.crew.application.out.repository; + +import com.retrip.crew.domain.entity.Crew; +import com.retrip.crew.domain.entity.Post; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface PostRepository extends JpaRepository { + + void deleteById(UUID postId); +} diff --git a/src/main/java/com/retrip/crew/domain/entity/BaseEntity.java b/src/main/java/com/retrip/crew/domain/entity/BaseEntity.java index ea01df4..1a20fbc 100644 --- a/src/main/java/com/retrip/crew/domain/entity/BaseEntity.java +++ b/src/main/java/com/retrip/crew/domain/entity/BaseEntity.java @@ -2,6 +2,7 @@ import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; +import lombok.Getter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -9,6 +10,7 @@ import java.time.LocalDateTime; @EntityListeners(AuditingEntityListener.class) +@Getter @MappedSuperclass public abstract class BaseEntity { @CreatedDate diff --git a/src/main/java/com/retrip/crew/domain/entity/CrewMember.java b/src/main/java/com/retrip/crew/domain/entity/CrewMember.java index 7a2c6f1..3f0b209 100644 --- a/src/main/java/com/retrip/crew/domain/entity/CrewMember.java +++ b/src/main/java/com/retrip/crew/domain/entity/CrewMember.java @@ -37,4 +37,12 @@ public CrewMember(Crew crew, UUID memberId, CrewMemberRole crewMemberRole) { this.memberId = memberId; this.crewMemberRole = CrewMemberRole.valueOf(crewMemberRole.name()); } + + public boolean isLeader() { + return crewMemberRole == CrewMemberRole.LEADER; + } + + public boolean isCreatedByMe(UUID postCreatedBy) { + return postCreatedBy == memberId; + } } diff --git a/src/main/java/com/retrip/crew/domain/entity/Post.java b/src/main/java/com/retrip/crew/domain/entity/Post.java index 8bcf102..5a86fe2 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Post.java +++ b/src/main/java/com/retrip/crew/domain/entity/Post.java @@ -1,5 +1,8 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.domain.exception.PostDeleteException; +import com.retrip.crew.domain.exception.PostNotFoundException; +import com.retrip.crew.domain.exception.PostUpdateException; import com.retrip.crew.domain.vo.PostContent; import com.retrip.crew.domain.vo.PostTitle; import jakarta.persistence.*; @@ -22,7 +25,7 @@ public class Post extends BaseEntity { @Embedded private PostContent content; - + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn( name = "crew_id", @@ -31,4 +34,38 @@ public class Post extends BaseEntity { foreignKey = @ForeignKey(name = "fk_free_board_to_crew") ) private Crew crew; + + private Post(String title, String content, Crew crew) { + this.id = UUID.randomUUID(); + this.title = new PostTitle(title); + this.content = new PostContent(content); + this.crew = crew; + } + + public static Post create(String title, String content, Crew crew) { + return new Post(title, content, crew); + } + + public void update(String title, String content, UUID userId) { + //validate(this.getCreatedBy(), userId); + this.title = new PostTitle(title); + this.content = new PostContent(content); + } + + private void validate(UUID createBy, UUID updateBy) { + if (createBy != updateBy){ + throw new PostUpdateException(); + } + } + + public boolean isDeletable(CrewMember crewMember) { + if (crewMember.isLeader()) + return true; + + //Todo: 작성자만 제거 가능 + //return crewMember.isCreatedBy(this.getCreatedBy()); + return true; + } + + } diff --git a/src/main/java/com/retrip/crew/domain/exception/CrewMemberNotFoundException.java b/src/main/java/com/retrip/crew/domain/exception/CrewMemberNotFoundException.java new file mode 100644 index 0000000..cbd0a94 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/CrewMemberNotFoundException.java @@ -0,0 +1,12 @@ +package com.retrip.crew.domain.exception; + +import com.retrip.crew.domain.exception.common.EntityNotFoundException; +import com.retrip.crew.domain.exception.common.ErrorCode; + +public class CrewMemberNotFoundException extends EntityNotFoundException { + private static final ErrorCode errorCode = ErrorCode.CREW_MEMBER_NOT_FOUND; + + public CrewMemberNotFoundException() { + super(errorCode); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/PostDeleteException.java b/src/main/java/com/retrip/crew/domain/exception/PostDeleteException.java new file mode 100644 index 0000000..5c8758b --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/PostDeleteException.java @@ -0,0 +1,13 @@ +package com.retrip.crew.domain.exception; + +import com.retrip.crew.domain.exception.common.BusinessException; +import com.retrip.crew.domain.exception.common.EntityNotFoundException; +import com.retrip.crew.domain.exception.common.ErrorCode; + +public class PostDeleteException extends BusinessException { + private static final ErrorCode errorCode = ErrorCode.POST_DELETE_FAIL; + + public PostDeleteException() { + super(errorCode); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/PostNotFoundException.java b/src/main/java/com/retrip/crew/domain/exception/PostNotFoundException.java new file mode 100644 index 0000000..bd4d0ea --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/PostNotFoundException.java @@ -0,0 +1,12 @@ +package com.retrip.crew.domain.exception; + +import com.retrip.crew.domain.exception.common.EntityNotFoundException; +import com.retrip.crew.domain.exception.common.ErrorCode; + +public class PostNotFoundException extends EntityNotFoundException { + private static final ErrorCode errorCode = ErrorCode.POST_NOT_FOUND; + + public PostNotFoundException() { + super(errorCode); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/PostUpdateException.java b/src/main/java/com/retrip/crew/domain/exception/PostUpdateException.java new file mode 100644 index 0000000..923d5ed --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/PostUpdateException.java @@ -0,0 +1,12 @@ +package com.retrip.crew.domain.exception; + +import com.retrip.crew.domain.exception.common.BusinessException; +import com.retrip.crew.domain.exception.common.ErrorCode; + +public class PostUpdateException extends BusinessException { + private static final ErrorCode errorCode = ErrorCode.POST_UPDATE_FAIL; + + public PostUpdateException() { + super(errorCode); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/common/ErrorCode.java b/src/main/java/com/retrip/crew/domain/exception/common/ErrorCode.java index 4d05d8e..8f66c1c 100644 --- a/src/main/java/com/retrip/crew/domain/exception/common/ErrorCode.java +++ b/src/main/java/com/retrip/crew/domain/exception/common/ErrorCode.java @@ -13,7 +13,11 @@ public enum ErrorCode { ENTITY_NOT_FOUND(BAD_REQUEST, "Common-004", "Entity not found"), ILLEGAL_STATE(BAD_REQUEST, "Common-005", "Illegal state"), - CREW_NOT_FOUND(BAD_REQUEST, "Crew-001", "크루 엔티티를 찾을 수 없습니다.") + CREW_NOT_FOUND(BAD_REQUEST, "Crew-001", "크루 엔티티를 찾을 수 없습니다."), + CREW_MEMBER_NOT_FOUND(BAD_REQUEST, "Crew-002", "크루원 멤버 엔티티를 찾을 수 없습니다.."), + POST_NOT_FOUND(BAD_REQUEST, "Crew-003", "자유 게시글 엔티티를 찾을 수 없습니다."), + POST_UPDATE_FAIL(FORBIDDEN, "Crew-004", "자유 게시글을 수정할 권한이 없습니다."), + POST_DELETE_FAIL(FORBIDDEN, "Crew-005", "자유 게시글을 삭제할 권한이 없습니다.") ; private final HttpStatus status; diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/PostController.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/PostController.java new file mode 100644 index 0000000..3e2f868 --- /dev/null +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/PostController.java @@ -0,0 +1,55 @@ +package com.retrip.crew.infra.adapter.in.presentation.rest; + +import com.retrip.crew.application.in.request.CreatePostRequest; +import com.retrip.crew.application.in.request.UpdatePostRequest; +import com.retrip.crew.application.in.response.CreatePostResponse; +import com.retrip.crew.application.in.response.DeletePostResponse; +import com.retrip.crew.application.in.response.UpdatePostResponse; +import com.retrip.crew.application.in.usecase.ManagePostUseCase; +import com.retrip.crew.infra.adapter.in.presentation.rest.common.ApiResponse; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.UUID; + +@RequiredArgsConstructor +@RequestMapping("/crews") +@RestController +@Tag(name = "Post", description = "자유 게시판 서비스") +public class PostController { + private final ManagePostUseCase managePostUseCase; + + @PostMapping("/{crewId}/posts") + @Schema(description = "크루 자유 게시판 생성") + public ApiResponse createPost( + @PathVariable UUID crewId, + @RequestBody CreatePostRequest request + ) { + CreatePostResponse post = managePostUseCase.createPost(crewId, request); + return ApiResponse.created(post); + } + + @PutMapping("/{crewId}/posts/{postId}") + @Schema(description = "크루 자유 게시판 수정") + public ApiResponse updatePost( + @PathVariable UUID crewId, + @PathVariable UUID postId, + @RequestBody UpdatePostRequest request + ) { + UpdatePostResponse post = managePostUseCase.updatePost(postId, request); + return ApiResponse.ok(post); + } + + @DeleteMapping("/{crewId}/posts/{postId}") + @Schema(description = "크루 자유 게시판 삭제") + public ApiResponse deletePost( + @PathVariable UUID crewId, + @PathVariable UUID postId, + @RequestParam UUID userId + ) { + DeletePostResponse post = managePostUseCase.deletePost(crewId, postId, userId); + return ApiResponse.created(post); + } +} diff --git a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewMemberQuerydslRepository.java b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewMemberQuerydslRepository.java new file mode 100644 index 0000000..85468c8 --- /dev/null +++ b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewMemberQuerydslRepository.java @@ -0,0 +1,28 @@ +package com.retrip.crew.infra.adapter.out.persistence.mysql.query; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.retrip.crew.application.out.repository.CrewMemberQueryRepository; +import com.retrip.crew.domain.entity.CrewMember; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.Optional; +import java.util.UUID; + +import static com.retrip.crew.domain.entity.QCrew.crew; +import static com.retrip.crew.domain.entity.QCrewMember.crewMember; + +@Repository +@RequiredArgsConstructor +public class CrewMemberQuerydslRepository implements CrewMemberQueryRepository { + private final JPAQueryFactory query; + + @Override + public Optional findCrewMemberByUserId(UUID crewId, UUID userId) { + return Optional.ofNullable(query.select(crewMember) + .from(crew) + .join(crewMember).on(crewMember.crew.id.eq(crew.id)) + .where(crewMember.memberId.eq(userId), crew.id.eq(crewId)) + .fetchOne()); + } +} diff --git a/src/test/java/com/retrip/crew/application/in/PostServiceTest.java b/src/test/java/com/retrip/crew/application/in/PostServiceTest.java new file mode 100644 index 0000000..f764e5c --- /dev/null +++ b/src/test/java/com/retrip/crew/application/in/PostServiceTest.java @@ -0,0 +1,99 @@ +package com.retrip.crew.application.in; + +import com.retrip.crew.application.in.factory.PostServiceTestFactory; +import com.retrip.crew.application.in.request.CreatePostRequest; +import com.retrip.crew.application.in.request.UpdatePostRequest; +import com.retrip.crew.application.in.response.CreatePostResponse; +import com.retrip.crew.application.in.response.DeletePostResponse; +import com.retrip.crew.application.in.response.UpdatePostResponse; +import com.retrip.crew.common.fixture.PostFixture; +import com.retrip.crew.domain.entity.Crew; +import com.retrip.crew.domain.entity.Post; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class PostServiceTest extends PostServiceTestFactory { + + @Test + void 자유_게시글을_작성한다() { + + //given + Crew crew = crewRepository.save(Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 100, + MEMBER_ID + )); + crewRepository.save(crew); + + CreatePostRequest request = PostFixture.createRequest( + "속초 여행 어디가 좋아요?", + "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~" + ); + + //when + CreatePostResponse response = postService.createPost(crew.getId(), request); + + //then + assertThat(response.id()).isNotNull(); + assertThat(response.title()).isNotNull(); + assertThat(response.content()).isNotNull(); + } + @Test + void 자유_게시글을_수정한다() { + + //given + Crew crew = crewRepository.save(Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 100, + MEMBER_ID + )); + Post post = postRepository.save( + Post.create( + "속초 여행 어디가 좋아요?", + "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~", + crew) + ); + + UpdatePostRequest request = PostFixture.updateRequest( + "강릉 여행 어디가 좋아요?", + "친구들과 강릉 여행 가려고 합니다. 강릉 여행지 추천해 주세요~", + MEMBER_ID + ); + //when + UpdatePostResponse response = postService.updatePost(post.getId(), request); + + //then + assertThat(response.id()).isNotNull(); + assertThat(response.title()).isNotNull(); + } + + @Test + void 자유_게시글을_삭제한다() { + + //given + Crew crew = crewRepository.save(Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 100, + MEMBER_ID + )); + Post post = postRepository.save( + Post.create( + "속초 여행 어디가 좋아요?", + "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~", + crew) + ); + + + //when + DeletePostResponse response = postService.deletePost(crew.getId(), post.getId(), MEMBER_ID); + + //then + assertThat(response.id()).isNotNull(); + } + + +} diff --git a/src/test/java/com/retrip/crew/application/in/factory/PostServiceTestFactory.java b/src/test/java/com/retrip/crew/application/in/factory/PostServiceTestFactory.java new file mode 100644 index 0000000..53075c2 --- /dev/null +++ b/src/test/java/com/retrip/crew/application/in/factory/PostServiceTestFactory.java @@ -0,0 +1,50 @@ +package com.retrip.crew.application.in.factory; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.retrip.crew.application.in.CrewService; +import com.retrip.crew.application.in.PostService; +import com.retrip.crew.application.out.repository.*; +import com.retrip.crew.common.config.QuerydslConfig; +import com.retrip.crew.infra.adapter.out.persistence.mysql.query.CrewMemberQuerydslRepository; +import com.retrip.crew.infra.adapter.out.persistence.mysql.query.CrewQuerydslRepository; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +import java.util.UUID; + +@DataJpaTest +@Import(QuerydslConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public abstract class PostServiceTestFactory { + @Autowired + protected CrewRepository crewRepository; + + @Autowired + protected CrewMemberRepository crewMemberRepository; + + @Autowired + JPAQueryFactory jpaQueryFactory; + + protected CrewQueryRepository crewQueryRepository; + + protected CrewMemberQueryRepository crewMemberQueryRepository; + + protected CrewService crewService; + protected PostService postService; + + protected UUID MEMBER_ID = UUID.fromString("13c8ab91-76bc-4f70-93e9-89f1a65dc640"); + + @Autowired + protected PostRepository postRepository; + + @BeforeEach + void setUp() { + crewQueryRepository = new CrewQuerydslRepository(jpaQueryFactory); + crewMemberQueryRepository = new CrewMemberQuerydslRepository(jpaQueryFactory); + crewService = new CrewService(crewRepository, crewMemberRepository, crewQueryRepository); + postService = new PostService(crewService, postRepository, crewMemberQueryRepository); + } +} diff --git a/src/test/java/com/retrip/crew/common/config/QuerydslConfig.java b/src/test/java/com/retrip/crew/common/config/QuerydslConfig.java index 7331886..02aa38c 100644 --- a/src/test/java/com/retrip/crew/common/config/QuerydslConfig.java +++ b/src/test/java/com/retrip/crew/common/config/QuerydslConfig.java @@ -1,6 +1,8 @@ package com.retrip.crew.common.config; import com.querydsl.jpa.impl.JPAQueryFactory; +import com.retrip.crew.application.out.repository.CrewMemberQueryRepository; +import com.retrip.crew.infra.adapter.out.persistence.mysql.query.CrewMemberQuerydslRepository; import com.retrip.crew.infra.adapter.out.persistence.mysql.query.CrewQuerydslRepository; import jakarta.persistence.EntityManager; import org.springframework.beans.factory.annotation.Autowired; @@ -24,4 +26,10 @@ public JPAQueryFactory jpaQueryFactory() { public CrewQuerydslRepository crewQuerydslRepository(JPAQueryFactory jpaQueryFactory) { return new CrewQuerydslRepository(jpaQueryFactory); } + + + @Bean + public CrewMemberQueryRepository crewMemberQueryRepository(JPAQueryFactory jpaQueryFactory) { + return new CrewMemberQuerydslRepository(jpaQueryFactory); + } } diff --git a/src/test/java/com/retrip/crew/common/fixture/PostFixture.java b/src/test/java/com/retrip/crew/common/fixture/PostFixture.java new file mode 100644 index 0000000..842b9b6 --- /dev/null +++ b/src/test/java/com/retrip/crew/common/fixture/PostFixture.java @@ -0,0 +1,22 @@ +package com.retrip.crew.common.fixture; + +import com.retrip.crew.application.in.request.CreatePostRequest; +import com.retrip.crew.application.in.request.UpdatePostRequest; + +import java.util.UUID; + +public class PostFixture { + public static CreatePostRequest createRequest(String title, String content) { + return new CreatePostRequest( + title, + content + ); + } + public static UpdatePostRequest updateRequest(String title, String content, UUID userId) { + return new UpdatePostRequest( + title, + content, + userId + ); + } +} diff --git a/src/test/java/com/retrip/crew/domain/entity/PostTest.java b/src/test/java/com/retrip/crew/domain/entity/PostTest.java new file mode 100644 index 0000000..91a151f --- /dev/null +++ b/src/test/java/com/retrip/crew/domain/entity/PostTest.java @@ -0,0 +1,71 @@ +package com.retrip.crew.domain.entity; + + +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PostTest { + @Test + void 자유_게시판을_생성한다() { + //given + Crew crew = Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 5, + UUID.randomUUID()); + + + //then + assertThatCode(() -> + Post.create( + "속초 여행 어디가 좋아요?", + "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~", + crew) + ).doesNotThrowAnyException(); + } + + @Test + void 자유_게시판을_리더는_삭제할_수_있다() { + //given + Crew crew = Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 5, + UUID.randomUUID()); + Post post = Post.create( + "속초 여행 어디가 좋아요?", + "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~", + crew); + + boolean deletable = post.isDeletable(crew.getLeader()); + + //then + assertTrue(deletable); + } + + @Test + void 자유_게시판을_수정할_수_있다() { + //given + Crew crew = Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 5, + UUID.randomUUID()); + Post post = Post.create( + "속초 여행 어디가 좋아요?", + "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~", + crew); + + post.update("강릉 여행 어디가 좋아요?", "친구들과 강릉 여행 가려고 합니다. 강릉 여행지 추천해 주세요~",crew.getLeader().getMemberId()); + + //then + assertEquals(post.getTitle().getValue(), "강릉 여행 어디가 좋아요?"); + assertEquals(post.getContent().getValue(), "친구들과 강릉 여행 가려고 합니다. 강릉 여행지 추천해 주세요~"); + } +} From 2fee5ba873c2ba5d1fe15cd02d85d31b23e39c7e Mon Sep 17 00:00:00 2001 From: junhokim Date: Sun, 13 Apr 2025 15:51:48 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=EC=9E=90=EC=9C=A0=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=ED=8C=90=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 자유 게시판 조회 기능 추가 --- .../crew/application/in/CrewService.java | 19 +- .../crew/application/in/PostService.java | 70 +++++--- .../application/in/request/PostOrder.java | 12 ++ .../application/in/response/PostResponse.java | 11 ++ .../in/usecase/GetPostUseCase.java | 17 ++ .../in/usecase/ManagePostUseCase.java | 7 +- .../out/repository/CrewQueryRepository.java | 7 + .../out/repository/PostQueryRepository.java | 16 ++ .../out/repository/PostRepository.java | 12 -- .../com/retrip/crew/domain/entity/Crew.java | 12 ++ .../com/retrip/crew/domain/entity/Post.java | 30 ++-- .../com/retrip/crew/domain/entity/Posts.java | 24 ++- .../in/presentation/rest/PostController.java | 37 ++-- .../query/CrewMemberQuerydslRepository.java | 10 +- .../mysql/query/CrewQuerydslRepository.java | 74 ++++---- .../mysql/query/PostQuerydslRepository.java | 56 ++++++ .../crew/application/in/PostServiceTest.java | 166 +++++++++++------- ...tFactory.java => BasePostServiceTest.java} | 30 ++-- .../crew/common/fixture/PostFixture.java | 2 + 19 files changed, 418 insertions(+), 194 deletions(-) create mode 100644 src/main/java/com/retrip/crew/application/in/request/PostOrder.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/PostResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/usecase/GetPostUseCase.java create mode 100644 src/main/java/com/retrip/crew/application/out/repository/PostQueryRepository.java delete mode 100644 src/main/java/com/retrip/crew/application/out/repository/PostRepository.java create mode 100644 src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/PostQuerydslRepository.java rename src/test/java/com/retrip/crew/application/in/factory/{PostServiceTestFactory.java => BasePostServiceTest.java} (64%) diff --git a/src/main/java/com/retrip/crew/application/in/CrewService.java b/src/main/java/com/retrip/crew/application/in/CrewService.java index 9a7453f..ae61523 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -20,7 +20,9 @@ import com.retrip.crew.domain.vo.CrewTitle; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; import com.retrip.crew.infra.util.PaginationUtils; + import lombok.RequiredArgsConstructor; + import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; @@ -31,7 +33,11 @@ @Service @Transactional @RequiredArgsConstructor -public class CrewService implements ManageCrewUseCase, UpdateRecruitmentUseCase, ManageDemandUseCase, GetCrewUseCase { +public class CrewService + implements ManageCrewUseCase, + UpdateRecruitmentUseCase, + ManageDemandUseCase, + GetCrewUseCase { private final CrewRepository crewRepository; private final CrewMemberRepository crewMemberRepository; private final CrewQueryRepository crewQueryRepository; @@ -77,8 +83,10 @@ public CreateDemandResponse createDemand(UUID crewId, CreateDemandRequest reques @Override @Transactional(readOnly = true) - public ScrollPageResponse getCrews(Pageable pageable, String keyword, CrewOrder order, String sort) { - Pageable orderPageable = PaginationUtils.createPageRequest(pageable, order.getField(), sort); + public ScrollPageResponse getCrews( + Pageable pageable, String keyword, CrewOrder order, String sort) { + Pageable orderPageable = + PaginationUtils.createPageRequest(pageable, order.getField(), sort); Slice result = crewQueryRepository.getCrews(orderPageable, keyword); Long totalCount = crewQueryRepository.getCrewCount(keyword); return ScrollPageResponse.of(totalCount, result.hasNext(), result.getContent()); @@ -92,8 +100,7 @@ public CrewDetailResponse getCrewDetail(UUID crewId) { return CrewDetailResponse.of(crew, memberCount); } - public Crew findById(UUID crewId){ - return crewRepository.findById(crewId) - .orElseThrow(CrewNotFoundException::new); + private Crew findById(UUID crewId) { + return crewRepository.findById(crewId).orElseThrow(CrewNotFoundException::new); } } diff --git a/src/main/java/com/retrip/crew/application/in/PostService.java b/src/main/java/com/retrip/crew/application/in/PostService.java index 9dd0eb2..d1c5148 100644 --- a/src/main/java/com/retrip/crew/application/in/PostService.java +++ b/src/main/java/com/retrip/crew/application/in/PostService.java @@ -1,70 +1,86 @@ package com.retrip.crew.application.in; import com.retrip.crew.application.in.request.CreatePostRequest; +import com.retrip.crew.application.in.request.PostOrder; import com.retrip.crew.application.in.request.UpdatePostRequest; import com.retrip.crew.application.in.response.CreatePostResponse; import com.retrip.crew.application.in.response.DeletePostResponse; +import com.retrip.crew.application.in.response.PostResponse; import com.retrip.crew.application.in.response.UpdatePostResponse; +import com.retrip.crew.application.in.usecase.GetPostUseCase; import com.retrip.crew.application.in.usecase.ManagePostUseCase; import com.retrip.crew.application.out.repository.CrewMemberQueryRepository; -import com.retrip.crew.application.out.repository.PostRepository; +import com.retrip.crew.application.out.repository.CrewQueryRepository; +import com.retrip.crew.application.out.repository.PostQueryRepository; import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.entity.CrewMember; import com.retrip.crew.domain.entity.Post; import com.retrip.crew.domain.exception.CrewMemberNotFoundException; -import com.retrip.crew.domain.exception.PostNotFoundException; + +import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; +import com.retrip.crew.infra.util.PaginationUtils; import lombok.RequiredArgsConstructor; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.UUID; @Service -@Transactional(readOnly = true) +@Transactional @RequiredArgsConstructor -public class PostService implements ManagePostUseCase { - private final CrewService crewService; - private final PostRepository postRepository; +public class PostService implements ManagePostUseCase, GetPostUseCase { + private final CrewQueryRepository crewQueryRepository; private final CrewMemberQueryRepository crewMemberQueryRepository; - - + private final PostQueryRepository postQueryRepository; @Override - @Transactional public CreatePostResponse createPost(UUID crewId, CreatePostRequest request) { - Crew crew = crewService.findById(crewId); - Post post = postRepository.save(request.to(crew)); + Crew crew = + crewQueryRepository + .findByIdWithPosts(crewId) + .orElseThrow(CrewMemberNotFoundException::new); + Post post = request.to(crew); + crew.addPost(post); return CreatePostResponse.of(post); } @Override - @Transactional - public UpdatePostResponse updatePost(UUID postId, UpdatePostRequest request) { - Post post = postRepository.findById(postId).orElseThrow(PostNotFoundException::new); - post.update(request.title(), request.content(), request.userId()); + public UpdatePostResponse updatePost(UUID crewId, UUID postId, UpdatePostRequest request) { + Crew crew = + crewQueryRepository + .findByIdWithPosts(crewId) + .orElseThrow(CrewMemberNotFoundException::new); + Post post = crew.updatePost(postId, request.title(), request.content(), request.userId()); return UpdatePostResponse.of(post); } @Override - @Transactional public DeletePostResponse deletePost(UUID crewId, UUID postId, UUID userId) { + Crew crew = + crewQueryRepository + .findByIdWithPosts(crewId) + .orElseThrow(CrewMemberNotFoundException::new); CrewMember crewMember = findCrewMemberByUserId(crewId, userId); - Post post = findPostById(postId); - if (post.isDeletable(crewMember)) { - postRepository.deleteById(postId); - return DeletePostResponse.of(postId); - } - throw new PostNotFoundException(); + UUID deleteId = crew.deletePost(postId, crewMember); + return DeletePostResponse.of(deleteId); } private CrewMember findCrewMemberByUserId(UUID crewId, UUID userId) { - return crewMemberQueryRepository.findCrewMemberByUserId(crewId, userId) + return crewMemberQueryRepository + .findCrewMemberByUserId(crewId, userId) .orElseThrow(CrewMemberNotFoundException::new); } - - private Post findPostById(UUID postId) { - return postRepository.findById(postId) - .orElseThrow(PostNotFoundException::new); + @Override + public ScrollPageResponse getPosts(UUID crewId, Pageable page, String keyword, PostOrder order, String sort) { + Pageable orderPageable = + PaginationUtils.createPageRequest(page, order.getField(), sort); + Slice result = postQueryRepository.findPosts(crewId, orderPageable, keyword); + Long totalCount = postQueryRepository.getPostCount(crewId, keyword); + return ScrollPageResponse.of(totalCount, result.hasNext(), result.getContent()); } } diff --git a/src/main/java/com/retrip/crew/application/in/request/PostOrder.java b/src/main/java/com/retrip/crew/application/in/request/PostOrder.java new file mode 100644 index 0000000..1baae4d --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/request/PostOrder.java @@ -0,0 +1,12 @@ +package com.retrip.crew.application.in.request; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum PostOrder { + DATE("createdAt"); + + private final String field; +} diff --git a/src/main/java/com/retrip/crew/application/in/response/PostResponse.java b/src/main/java/com/retrip/crew/application/in/response/PostResponse.java new file mode 100644 index 0000000..a1a9741 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/PostResponse.java @@ -0,0 +1,11 @@ +package com.retrip.crew.application.in.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.UUID; + +@Schema(description = "자유 게시글 조회 Response") +public record PostResponse( + @Schema(description = "자유 게시글 Id") UUID id, + @Schema(description = "자유 게시글 Title") String title, + @Schema(description = "자유 게시글 Contents") String Contents) {} diff --git a/src/main/java/com/retrip/crew/application/in/usecase/GetPostUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/GetPostUseCase.java new file mode 100644 index 0000000..0b046b1 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/usecase/GetPostUseCase.java @@ -0,0 +1,17 @@ +package com.retrip.crew.application.in.usecase; + +import com.retrip.crew.application.in.request.CrewOrder; +import com.retrip.crew.application.in.request.PostOrder; +import com.retrip.crew.application.in.response.PostResponse; + +import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; + +import java.util.UUID; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface GetPostUseCase { + + ScrollPageResponse getPosts(UUID crewId, Pageable page, String keyword, PostOrder order, String sort); +} diff --git a/src/main/java/com/retrip/crew/application/in/usecase/ManagePostUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManagePostUseCase.java index 9b3a6ae..d7c4849 100644 --- a/src/main/java/com/retrip/crew/application/in/usecase/ManagePostUseCase.java +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManagePostUseCase.java @@ -6,16 +6,21 @@ import com.retrip.crew.application.in.response.DeletePostResponse; +import com.retrip.crew.application.in.response.PostResponse; import com.retrip.crew.application.in.response.UpdatePostResponse; import java.util.UUID; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + public interface ManagePostUseCase { CreatePostResponse createPost(UUID crewId, CreatePostRequest request); - UpdatePostResponse updatePost(UUID postId, UpdatePostRequest request); + UpdatePostResponse updatePost(UUID crewId, UUID postId, UpdatePostRequest request); DeletePostResponse deletePost(UUID crewId, UUID postId, UUID userId); + } diff --git a/src/main/java/com/retrip/crew/application/out/repository/CrewQueryRepository.java b/src/main/java/com/retrip/crew/application/out/repository/CrewQueryRepository.java index 6a1c089..966a8df 100644 --- a/src/main/java/com/retrip/crew/application/out/repository/CrewQueryRepository.java +++ b/src/main/java/com/retrip/crew/application/out/repository/CrewQueryRepository.java @@ -1,10 +1,17 @@ package com.retrip.crew.application.out.repository; import com.retrip.crew.application.in.response.CrewListResponse; +import com.retrip.crew.domain.entity.Crew; + +import java.util.Optional; + +import java.util.UUID; + import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; public interface CrewQueryRepository { Slice getCrews(Pageable pageable, String keyword); Long getCrewCount(String keyword); + Optional findByIdWithPosts(UUID id); } diff --git a/src/main/java/com/retrip/crew/application/out/repository/PostQueryRepository.java b/src/main/java/com/retrip/crew/application/out/repository/PostQueryRepository.java new file mode 100644 index 0000000..bdc2ee7 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/out/repository/PostQueryRepository.java @@ -0,0 +1,16 @@ +package com.retrip.crew.application.out.repository; + +import com.retrip.crew.application.in.response.PostResponse; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.UUID; + +import org.springframework.data.domain.Slice; + +public interface PostQueryRepository { + Slice findPosts(UUID crewId, Pageable page, String keyword); + + Long getPostCount(UUID crewId, String keyword); +} diff --git a/src/main/java/com/retrip/crew/application/out/repository/PostRepository.java b/src/main/java/com/retrip/crew/application/out/repository/PostRepository.java deleted file mode 100644 index a71e060..0000000 --- a/src/main/java/com/retrip/crew/application/out/repository/PostRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.retrip.crew.application.out.repository; - -import com.retrip.crew.domain.entity.Crew; -import com.retrip.crew.domain.entity.Post; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.UUID; - -public interface PostRepository extends JpaRepository { - - void deleteById(UUID postId); -} diff --git a/src/main/java/com/retrip/crew/domain/entity/Crew.java b/src/main/java/com/retrip/crew/domain/entity/Crew.java index fd2180e..caedfd3 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Crew.java +++ b/src/main/java/com/retrip/crew/domain/entity/Crew.java @@ -1,5 +1,6 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.application.in.response.DeletePostResponse; import com.retrip.crew.domain.vo.CrewDescription; import com.retrip.crew.domain.vo.CrewTitle; import jakarta.persistence.*; @@ -82,4 +83,15 @@ public String getDescription(){ return description.getValue(); } + public void addPost(Post post) { + this.posts.getValues().add(post); + } + + public Post updatePost(UUID postId, String title, String content, UUID userId) { + return this.posts.updatePost(postId, title, content, userId); + } + + public UUID deletePost(UUID postId, CrewMember crewMember) { + return this.posts.deletePost(postId, crewMember); + } } diff --git a/src/main/java/com/retrip/crew/domain/entity/Post.java b/src/main/java/com/retrip/crew/domain/entity/Post.java index 5a86fe2..4157575 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Post.java +++ b/src/main/java/com/retrip/crew/domain/entity/Post.java @@ -1,11 +1,11 @@ package com.retrip.crew.domain.entity; -import com.retrip.crew.domain.exception.PostDeleteException; -import com.retrip.crew.domain.exception.PostNotFoundException; import com.retrip.crew.domain.exception.PostUpdateException; import com.retrip.crew.domain.vo.PostContent; import com.retrip.crew.domain.vo.PostTitle; + import jakarta.persistence.*; + import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -20,19 +20,16 @@ public class Post extends BaseEntity { @Column(columnDefinition = "varbinary(16)") private UUID id; - @Embedded - private PostTitle title; + @Embedded private PostTitle title; - @Embedded - private PostContent content; + @Embedded private PostContent content; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn( name = "crew_id", nullable = false, columnDefinition = "varbinary(16)", - foreignKey = @ForeignKey(name = "fk_free_board_to_crew") - ) + foreignKey = @ForeignKey(name = "fk_free_board_to_crew")) private Crew crew; private Post(String title, String content, Crew crew) { @@ -47,25 +44,20 @@ public static Post create(String title, String content, Crew crew) { } public void update(String title, String content, UUID userId) { - //validate(this.getCreatedBy(), userId); + // isUpdatable(this.getCreatedBy(), userId); this.title = new PostTitle(title); this.content = new PostContent(content); } - private void validate(UUID createBy, UUID updateBy) { - if (createBy != updateBy){ + private void isUpdatable(UUID createBy, UUID updateBy) { + if (createBy != updateBy) { throw new PostUpdateException(); } } public boolean isDeletable(CrewMember crewMember) { - if (crewMember.isLeader()) - return true; - - //Todo: 작성자만 제거 가능 - //return crewMember.isCreatedBy(this.getCreatedBy()); - return true; + return crewMember.isLeader(); + // Todo: 작성자만 제거 가능 + // || crewMember.isCreatedBy(this.getCreatedBy()); } - - } diff --git a/src/main/java/com/retrip/crew/domain/entity/Posts.java b/src/main/java/com/retrip/crew/domain/entity/Posts.java index 39b9b9b..b9d73d8 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Posts.java +++ b/src/main/java/com/retrip/crew/domain/entity/Posts.java @@ -1,11 +1,14 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.domain.exception.PostDeleteException; +import com.retrip.crew.domain.exception.PostNotFoundException; import jakarta.persistence.CascadeType; import jakarta.persistence.Embeddable; import jakarta.persistence.OneToMany; -import lombok.AccessLevel; + +import java.util.UUID; + import lombok.Getter; -import lombok.NoArgsConstructor; import java.util.ArrayList; import java.util.List; @@ -23,4 +26,21 @@ public Posts() { private List createEmptyValues() { return new ArrayList<>(); } + + public Post updatePost(UUID postId, String title, String content, UUID userId) { + Post post = this.values.stream().filter(p -> p.getId().equals(postId)).findAny() + .orElseThrow(PostNotFoundException::new); + post.update(title, content, userId); + return post; + } + + public UUID deletePost(UUID postId, CrewMember crewMember) { + Post post = this.values.stream().filter(p -> p.getId().equals(postId)).findAny() + .orElseThrow(PostNotFoundException::new); + if (!post.isDeletable(crewMember)) { + throw new PostDeleteException(); + } + this.values.remove(post); + return post.getId(); + } } diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/PostController.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/PostController.java index 3e2f868..d833a49 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/PostController.java +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/PostController.java @@ -1,15 +1,24 @@ package com.retrip.crew.infra.adapter.in.presentation.rest; import com.retrip.crew.application.in.request.CreatePostRequest; +import com.retrip.crew.application.in.request.CrewOrder; +import com.retrip.crew.application.in.request.PostOrder; import com.retrip.crew.application.in.request.UpdatePostRequest; import com.retrip.crew.application.in.response.CreatePostResponse; import com.retrip.crew.application.in.response.DeletePostResponse; +import com.retrip.crew.application.in.response.PostResponse; import com.retrip.crew.application.in.response.UpdatePostResponse; +import com.retrip.crew.application.in.usecase.GetPostUseCase; import com.retrip.crew.application.in.usecase.ManagePostUseCase; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ApiResponse; +import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.UUID; @@ -20,13 +29,12 @@ @Tag(name = "Post", description = "자유 게시판 서비스") public class PostController { private final ManagePostUseCase managePostUseCase; + private final GetPostUseCase getPostUseCase; @PostMapping("/{crewId}/posts") @Schema(description = "크루 자유 게시판 생성") public ApiResponse createPost( - @PathVariable UUID crewId, - @RequestBody CreatePostRequest request - ) { + @PathVariable UUID crewId, @RequestBody CreatePostRequest request) { CreatePostResponse post = managePostUseCase.createPost(crewId, request); return ApiResponse.created(post); } @@ -36,20 +44,29 @@ public ApiResponse createPost( public ApiResponse updatePost( @PathVariable UUID crewId, @PathVariable UUID postId, - @RequestBody UpdatePostRequest request - ) { - UpdatePostResponse post = managePostUseCase.updatePost(postId, request); + @RequestBody UpdatePostRequest request) { + UpdatePostResponse post = managePostUseCase.updatePost(crewId, postId, request); return ApiResponse.ok(post); } @DeleteMapping("/{crewId}/posts/{postId}") @Schema(description = "크루 자유 게시판 삭제") public ApiResponse deletePost( - @PathVariable UUID crewId, - @PathVariable UUID postId, - @RequestParam UUID userId - ) { + @PathVariable UUID crewId, @PathVariable UUID postId, @RequestParam UUID userId) { DeletePostResponse post = managePostUseCase.deletePost(crewId, postId, userId); return ApiResponse.created(post); } + + @GetMapping("/{crewId}/posts") + @Schema(description = "크루 자유 게시판 조회") + public ApiResponse> getPosts( + @PathVariable UUID crewId, + @RequestParam String keyword, + @RequestParam(name = "order", defaultValue = "DATE") PostOrder order, + @RequestParam(name = "sort", defaultValue = "asc") String sort, + @PageableDefault(size = 10) Pageable page) { + ScrollPageResponse posts = + getPostUseCase.getPosts(crewId, page, keyword, order, sort); + return ApiResponse.ok(posts); + } } diff --git a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewMemberQuerydslRepository.java b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewMemberQuerydslRepository.java index 85468c8..721e33a 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewMemberQuerydslRepository.java +++ b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewMemberQuerydslRepository.java @@ -19,10 +19,10 @@ public class CrewMemberQuerydslRepository implements CrewMemberQueryRepository { @Override public Optional findCrewMemberByUserId(UUID crewId, UUID userId) { - return Optional.ofNullable(query.select(crewMember) - .from(crew) - .join(crewMember).on(crewMember.crew.id.eq(crew.id)) - .where(crewMember.memberId.eq(userId), crew.id.eq(crewId)) - .fetchOne()); + return Optional.ofNullable( + query.selectFrom(crewMember) + .join(crew) + .on(crewMember.crew.id.eq(crewId), crewMember.memberId.eq(userId)) + .fetchOne()); } } diff --git a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java index 13e7a09..b936fe0 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java +++ b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java @@ -1,5 +1,11 @@ package com.retrip.crew.infra.adapter.out.persistence.mysql.query; +import static com.querydsl.jpa.JPAExpressions.select; +import static com.retrip.crew.domain.entity.QCrew.crew; +import static com.retrip.crew.domain.entity.QCrewMember.crewMember; +import static com.retrip.crew.domain.entity.QPost.post; +import static com.retrip.crew.infra.util.PaginationUtils.checkEndPage; + import com.querydsl.core.types.ExpressionUtils; import com.querydsl.core.types.Order; import com.querydsl.core.types.OrderSpecifier; @@ -11,19 +17,20 @@ import com.retrip.crew.application.in.request.CrewOrder; import com.retrip.crew.application.in.response.CrewListResponse; import com.retrip.crew.application.out.repository.CrewQueryRepository; +import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.entity.QCrewMember; + +import java.util.UUID; + import lombok.RequiredArgsConstructor; + import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Repository; import java.util.List; - -import static com.querydsl.jpa.JPAExpressions.select; -import static com.retrip.crew.domain.entity.QCrew.crew; -import static com.retrip.crew.domain.entity.QCrewMember.crewMember; -import static com.retrip.crew.infra.util.PaginationUtils.checkEndPage; +import java.util.Optional; @Repository @RequiredArgsConstructor @@ -36,37 +43,42 @@ public Slice getCrews(Pageable pageable, String keyword) { NumberPath MemberCountAlias = Expressions.numberPath(Long.class, "memberCount"); OrderSpecifier orderSpecifier = createCrewOrderSpecifier(pageable); - List fetch = query.select(Projections.constructor(CrewListResponse.class, - crew.id.as("id"), - crew.title.value.as("title"), - crewMember.id.as("leaderId"), - ExpressionUtils.as( - select(subCrewMember.count()) - .from(subCrewMember) - .where(subCrewMember.crew.id.eq(crew.id)) ,MemberCountAlias), - crew.recruitment.maxMembers.as("maxMemberCount") - ) - ) - .from(crew) - .join(crewMember).on(crewMember.crew.id.eq(crew.id)) - .where( - crewTitleContains(keyword) - ) - .limit(pageable.getPageSize() + 1) - .offset(pageable.getOffset()) - .orderBy(orderSpecifier) - .fetch(); + List fetch = + query.select( + Projections.constructor( + CrewListResponse.class, + crew.id.as("id"), + crew.title.value.as("title"), + crewMember.id.as("leaderId"), + ExpressionUtils.as( + select(subCrewMember.count()) + .from(subCrewMember) + .where(subCrewMember.crew.id.eq(crew.id)), + MemberCountAlias), + crew.recruitment.maxMembers.as("maxMemberCount"))) + .from(crew) + .join(crewMember) + .on(crewMember.crew.id.eq(crew.id)) + .where(crewTitleContains(keyword)) + .limit(pageable.getPageSize() + 1) + .offset(pageable.getOffset()) + .orderBy(orderSpecifier) + .fetch(); return checkEndPage(pageable, fetch); } @Override public Long getCrewCount(String keyword) { - return query.select(crew.count()) - .from(crew) - .where( - crewTitleContains(keyword) - ) - .fetchOne(); + return query.select(crew.count()).from(crew).where(crewTitleContains(keyword)).fetchOne(); + } + + @Override + public Optional findByIdWithPosts(UUID id) { + return Optional.ofNullable( + query.selectFrom(crew) + .leftJoin(crew.posts.values, post).fetchJoin() + .where(crew.id.eq(crew.id)) + .fetchOne()); } private BooleanExpression crewTitleContains(String title) { diff --git a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/PostQuerydslRepository.java b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/PostQuerydslRepository.java new file mode 100644 index 0000000..063027d --- /dev/null +++ b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/PostQuerydslRepository.java @@ -0,0 +1,56 @@ +package com.retrip.crew.infra.adapter.out.persistence.mysql.query; + +import static com.retrip.crew.domain.entity.QCrew.crew; +import static com.retrip.crew.domain.entity.QPost.post; +import static com.retrip.crew.infra.util.PaginationUtils.checkEndPage; + +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.retrip.crew.application.in.response.PostResponse; +import com.retrip.crew.application.out.repository.PostQueryRepository; + +import lombok.RequiredArgsConstructor; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.UUID; + +@Repository +@RequiredArgsConstructor +public class PostQuerydslRepository implements PostQueryRepository { + private final JPAQueryFactory query; + + @Override + public Slice findPosts(UUID crewId, Pageable page, String keyword) { + List fetch = + query.select( + Projections.constructor( + PostResponse.class, + post.id, + post.title.value, + post.content.value)) + .from(post) + .join(crew) + .on(crew.id.eq(crewId)) + .where(postTitleContains(keyword)) + .offset(page.getOffset()) + .limit(page.getPageSize()) + .orderBy(post.createdAt.desc()) + .fetch(); + + return checkEndPage(page, fetch); + } + + @Override + public Long getPostCount(UUID crewId, String keyword) { + return query.select(post.count()).from(post).where(postTitleContains(keyword)).fetchOne(); + } + + private BooleanExpression postTitleContains(String title) { + return title == null ? null : post.title.value.contains(title); + } +} diff --git a/src/test/java/com/retrip/crew/application/in/PostServiceTest.java b/src/test/java/com/retrip/crew/application/in/PostServiceTest.java index f764e5c..b3b0af1 100644 --- a/src/test/java/com/retrip/crew/application/in/PostServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/PostServiceTest.java @@ -1,71 +1,114 @@ package com.retrip.crew.application.in; -import com.retrip.crew.application.in.factory.PostServiceTestFactory; +import static com.retrip.crew.common.fixture.PostFixture.MEMBER_ID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.retrip.crew.application.in.factory.BasePostServiceTest; import com.retrip.crew.application.in.request.CreatePostRequest; +import com.retrip.crew.application.in.request.PostOrder; import com.retrip.crew.application.in.request.UpdatePostRequest; import com.retrip.crew.application.in.response.CreatePostResponse; import com.retrip.crew.application.in.response.DeletePostResponse; +import com.retrip.crew.application.in.response.PostResponse; import com.retrip.crew.application.in.response.UpdatePostResponse; import com.retrip.crew.common.fixture.PostFixture; import com.retrip.crew.domain.entity.Crew; -import com.retrip.crew.domain.entity.Post; + +import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; -import static org.assertj.core.api.Assertions.assertThat; +class PostServiceTest extends BasePostServiceTest { -class PostServiceTest extends PostServiceTestFactory { + @Test + void 자유_게시글을_조회한다() { + + // given + Crew crew = + crewRepository.save( + Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 100, + MEMBER_ID)); + postService.createPost( + crew.getId(), + PostFixture.createRequest("속초 여행 어디가 좋아요?", "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~")); + postService.createPost( + crew.getId(), + PostFixture.createRequest("강릉 여행 어디가 좋아요?", "친구들과 강릉 여행 가려고 합니다. 강릉 여행지 추천해 주세요~")); + postService.createPost( + crew.getId(), PostFixture.createRequest("속초 중앙 시장, 오징어 순대", "속초 오징어 순대 맛있나요?")); + + // when + PageRequest pageable = PageRequest.of(0, 10); + ScrollPageResponse response = + postService.getPosts(crew.getId(), pageable, "속초", PostOrder.DATE, "desc"); + + // then + assertAll( + "크루 검색 및 정렬 검증", + () -> assertThat(response).isNotNull(), + () -> assertThat(response.getList()).hasSize(2), + () -> assertThat(response.getList().getFirst().title()).isEqualTo("속초 중앙 시장, 오징어 순대"), + () -> + assertThat(response.getList().getFirst().Contents()) + .isEqualTo("속초 오징어 순대 맛있나요?"), + () -> assertThat(response.isHasNext()).isFalse()); + } @Test void 자유_게시글을_작성한다() { - //given - Crew crew = crewRepository.save(Crew.create( - "속초 크루원 구함", - "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", - 100, - MEMBER_ID - )); + // given + Crew crew = + crewRepository.save( + Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 100, + MEMBER_ID)); crewRepository.save(crew); - CreatePostRequest request = PostFixture.createRequest( - "속초 여행 어디가 좋아요?", - "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~" - ); + CreatePostRequest request = + PostFixture.createRequest("속초 여행 어디가 좋아요?", "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~"); - //when + // when CreatePostResponse response = postService.createPost(crew.getId(), request); - //then + // then assertThat(response.id()).isNotNull(); assertThat(response.title()).isNotNull(); assertThat(response.content()).isNotNull(); } + @Test void 자유_게시글을_수정한다() { - - //given - Crew crew = crewRepository.save(Crew.create( - "속초 크루원 구함", - "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", - 100, - MEMBER_ID - )); - Post post = postRepository.save( - Post.create( - "속초 여행 어디가 좋아요?", - "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~", - crew) - ); - - UpdatePostRequest request = PostFixture.updateRequest( - "강릉 여행 어디가 좋아요?", - "친구들과 강릉 여행 가려고 합니다. 강릉 여행지 추천해 주세요~", - MEMBER_ID - ); - //when - UpdatePostResponse response = postService.updatePost(post.getId(), request); - - //then + // given + Crew crew = + crewRepository.save( + Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 100, + MEMBER_ID)); + CreatePostResponse createPostResponse = + postService.createPost( + crew.getId(), + PostFixture.createRequest( + "속초 여행 어디가 좋아요?", "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~")); + + UpdatePostRequest request = + PostFixture.updateRequest( + "강릉 여행 어디가 좋아요?", "친구들과 강릉 여행 가려고 합니다. 강릉 여행지 추천해 주세요~", MEMBER_ID); + // when + UpdatePostResponse response = + postService.updatePost(crew.getId(), createPostResponse.id(), request); + + // then assertThat(response.id()).isNotNull(); assertThat(response.title()).isNotNull(); } @@ -73,27 +116,26 @@ class PostServiceTest extends PostServiceTestFactory { @Test void 자유_게시글을_삭제한다() { - //given - Crew crew = crewRepository.save(Crew.create( - "속초 크루원 구함", - "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", - 100, - MEMBER_ID - )); - Post post = postRepository.save( - Post.create( - "속초 여행 어디가 좋아요?", - "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~", - crew) - ); - - - //when - DeletePostResponse response = postService.deletePost(crew.getId(), post.getId(), MEMBER_ID); - - //then + // given + Crew crew = + crewRepository.save( + Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 100, + MEMBER_ID)); + CreatePostResponse createPostResponse = + postService.createPost( + crew.getId(), + PostFixture.createRequest( + "속초 여행 어디가 좋아요?", "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~")); + ; + + // when + DeletePostResponse response = + postService.deletePost(crew.getId(), createPostResponse.id(), MEMBER_ID); + + // then assertThat(response.id()).isNotNull(); } - - } diff --git a/src/test/java/com/retrip/crew/application/in/factory/PostServiceTestFactory.java b/src/test/java/com/retrip/crew/application/in/factory/BasePostServiceTest.java similarity index 64% rename from src/test/java/com/retrip/crew/application/in/factory/PostServiceTestFactory.java rename to src/test/java/com/retrip/crew/application/in/factory/BasePostServiceTest.java index 53075c2..f2c8b1a 100644 --- a/src/test/java/com/retrip/crew/application/in/factory/PostServiceTestFactory.java +++ b/src/test/java/com/retrip/crew/application/in/factory/BasePostServiceTest.java @@ -1,50 +1,42 @@ package com.retrip.crew.application.in.factory; import com.querydsl.jpa.impl.JPAQueryFactory; -import com.retrip.crew.application.in.CrewService; import com.retrip.crew.application.in.PostService; import com.retrip.crew.application.out.repository.*; import com.retrip.crew.common.config.QuerydslConfig; import com.retrip.crew.infra.adapter.out.persistence.mysql.query.CrewMemberQuerydslRepository; import com.retrip.crew.infra.adapter.out.persistence.mysql.query.CrewQuerydslRepository; +import com.retrip.crew.infra.adapter.out.persistence.mysql.query.PostQuerydslRepository; + import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; -import java.util.UUID; - @DataJpaTest @Import(QuerydslConfig.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -public abstract class PostServiceTestFactory { - @Autowired - protected CrewRepository crewRepository; +public abstract class BasePostServiceTest { + @Autowired protected CrewRepository crewRepository; - @Autowired - protected CrewMemberRepository crewMemberRepository; + @Autowired protected CrewMemberRepository crewMemberRepository; - @Autowired - JPAQueryFactory jpaQueryFactory; + @Autowired JPAQueryFactory jpaQueryFactory; protected CrewQueryRepository crewQueryRepository; protected CrewMemberQueryRepository crewMemberQueryRepository; - - protected CrewService crewService; + protected PostQueryRepository postQueryRepository; protected PostService postService; - protected UUID MEMBER_ID = UUID.fromString("13c8ab91-76bc-4f70-93e9-89f1a65dc640"); - - @Autowired - protected PostRepository postRepository; - @BeforeEach void setUp() { crewQueryRepository = new CrewQuerydslRepository(jpaQueryFactory); crewMemberQueryRepository = new CrewMemberQuerydslRepository(jpaQueryFactory); - crewService = new CrewService(crewRepository, crewMemberRepository, crewQueryRepository); - postService = new PostService(crewService, postRepository, crewMemberQueryRepository); + postQueryRepository = new PostQuerydslRepository(jpaQueryFactory); + postService = + new PostService( + crewQueryRepository, crewMemberQueryRepository, postQueryRepository); } } diff --git a/src/test/java/com/retrip/crew/common/fixture/PostFixture.java b/src/test/java/com/retrip/crew/common/fixture/PostFixture.java index 842b9b6..9b05ea4 100644 --- a/src/test/java/com/retrip/crew/common/fixture/PostFixture.java +++ b/src/test/java/com/retrip/crew/common/fixture/PostFixture.java @@ -6,6 +6,8 @@ import java.util.UUID; public class PostFixture { + public static UUID MEMBER_ID = UUID.fromString("13c8ab91-76bc-4f70-93e9-89f1a65dc640"); + public static CreatePostRequest createRequest(String title, String content) { return new CreatePostRequest( title, From a6f4c10bada63f8940e27912a74e6608363b5efb Mon Sep 17 00:00:00 2001 From: junhokim Date: Sun, 4 May 2025 17:31:07 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=EC=9E=90=EC=9C=A0=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=ED=8C=90=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 자유 게시판 PR 수정 --- .../crew/application/in/PostService.java | 24 ++++++------- .../in/request/UpdatePostRequest.java | 2 +- .../in/usecase/ManagePostUseCase.java | 5 +-- .../repository/CrewMemberQueryRepository.java | 2 +- .../com/retrip/crew/domain/entity/Crew.java | 12 ------- .../com/retrip/crew/domain/entity/Post.java | 16 +++++---- .../com/retrip/crew/domain/entity/Posts.java | 26 ++++++++------ ...on.java => PostDeleteFailedException.java} | 5 ++- ...on.java => PostUpdateFailedException.java} | 4 +-- .../retrip/crew/domain/vo/PostContent.java | 2 +- .../in/presentation/rest/PostController.java | 14 ++++---- .../query/CrewMemberQuerydslRepository.java | 4 +-- .../mysql/query/CrewQuerydslRepository.java | 22 ++++++------ .../crew/application/in/PostServiceTest.java | 19 +++++------ .../retrip/crew/domain/entity/PostTest.java | 34 ++++++++++++++++--- 15 files changed, 103 insertions(+), 88 deletions(-) rename src/main/java/com/retrip/crew/domain/exception/{PostDeleteException.java => PostDeleteFailedException.java} (62%) rename src/main/java/com/retrip/crew/domain/exception/{PostUpdateException.java => PostUpdateFailedException.java} (72%) diff --git a/src/main/java/com/retrip/crew/application/in/PostService.java b/src/main/java/com/retrip/crew/application/in/PostService.java index d1c5148..c56b359 100644 --- a/src/main/java/com/retrip/crew/application/in/PostService.java +++ b/src/main/java/com/retrip/crew/application/in/PostService.java @@ -17,11 +17,12 @@ import com.retrip.crew.domain.entity.Post; import com.retrip.crew.domain.exception.CrewMemberNotFoundException; +import com.retrip.crew.domain.exception.CrewNotFoundException; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; import com.retrip.crew.infra.util.PaginationUtils; + import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; @@ -42,9 +43,9 @@ public CreatePostResponse createPost(UUID crewId, CreatePostRequest request) { Crew crew = crewQueryRepository .findByIdWithPosts(crewId) - .orElseThrow(CrewMemberNotFoundException::new); + .orElseThrow(CrewNotFoundException::new); Post post = request.to(crew); - crew.addPost(post); + crew.getPosts().add(post); return CreatePostResponse.of(post); } @@ -53,25 +54,24 @@ public UpdatePostResponse updatePost(UUID crewId, UUID postId, UpdatePostRequest Crew crew = crewQueryRepository .findByIdWithPosts(crewId) - .orElseThrow(CrewMemberNotFoundException::new); - Post post = crew.updatePost(postId, request.title(), request.content(), request.userId()); + .orElseThrow(CrewNotFoundException::new); + Post post = crew.getPosts().updatePost(postId, request.title(), request.content(), request.memberId()); return UpdatePostResponse.of(post); } @Override - public DeletePostResponse deletePost(UUID crewId, UUID postId, UUID userId) { + public void deletePost(UUID crewId, UUID postId, UUID memberId) { Crew crew = crewQueryRepository .findByIdWithPosts(crewId) - .orElseThrow(CrewMemberNotFoundException::new); - CrewMember crewMember = findCrewMemberByUserId(crewId, userId); - UUID deleteId = crew.deletePost(postId, crewMember); - return DeletePostResponse.of(deleteId); + .orElseThrow(CrewNotFoundException::new); + CrewMember crewMember = findCrewMemberByMemberId(crewId, memberId); + crew.getPosts().deletePost(postId, crewMember); } - private CrewMember findCrewMemberByUserId(UUID crewId, UUID userId) { + private CrewMember findCrewMemberByMemberId(UUID crewId, UUID memberId) { return crewMemberQueryRepository - .findCrewMemberByUserId(crewId, userId) + .findCrewMemberByMemberId(crewId, memberId) .orElseThrow(CrewMemberNotFoundException::new); } diff --git a/src/main/java/com/retrip/crew/application/in/request/UpdatePostRequest.java b/src/main/java/com/retrip/crew/application/in/request/UpdatePostRequest.java index 7b08d9f..d40b03d 100644 --- a/src/main/java/com/retrip/crew/application/in/request/UpdatePostRequest.java +++ b/src/main/java/com/retrip/crew/application/in/request/UpdatePostRequest.java @@ -21,7 +21,7 @@ public record UpdatePostRequest( @Schema(description = "수정자") @NotNull - UUID userId + UUID memberId ) { } diff --git a/src/main/java/com/retrip/crew/application/in/usecase/ManagePostUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManagePostUseCase.java index d7c4849..56c7179 100644 --- a/src/main/java/com/retrip/crew/application/in/usecase/ManagePostUseCase.java +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManagePostUseCase.java @@ -19,8 +19,5 @@ public interface ManagePostUseCase { UpdatePostResponse updatePost(UUID crewId, UUID postId, UpdatePostRequest request); - DeletePostResponse deletePost(UUID crewId, UUID postId, UUID userId); - - - + void deletePost(UUID crewId, UUID postId, UUID memberId); } diff --git a/src/main/java/com/retrip/crew/application/out/repository/CrewMemberQueryRepository.java b/src/main/java/com/retrip/crew/application/out/repository/CrewMemberQueryRepository.java index 84093fc..3f18706 100644 --- a/src/main/java/com/retrip/crew/application/out/repository/CrewMemberQueryRepository.java +++ b/src/main/java/com/retrip/crew/application/out/repository/CrewMemberQueryRepository.java @@ -6,5 +6,5 @@ import java.util.UUID; public interface CrewMemberQueryRepository { - Optional findCrewMemberByUserId(UUID crewId, UUID userId); + Optional findCrewMemberByMemberId(UUID crewId, UUID userId); } diff --git a/src/main/java/com/retrip/crew/domain/entity/Crew.java b/src/main/java/com/retrip/crew/domain/entity/Crew.java index f93f5a7..dca0285 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Crew.java +++ b/src/main/java/com/retrip/crew/domain/entity/Crew.java @@ -96,16 +96,4 @@ public void approveDemand(Demand demand) { recruitment.approveDemand(demand); crewMembers.addMember(demand, this); } - - public void addPost(Post post) { - this.posts.getValues().add(post); - } - - public Post updatePost(UUID postId, String title, String content, UUID userId) { - return this.posts.updatePost(postId, title, content, userId); - } - - public UUID deletePost(UUID postId, CrewMember crewMember) { - return this.posts.deletePost(postId, crewMember); - } } diff --git a/src/main/java/com/retrip/crew/domain/entity/Post.java b/src/main/java/com/retrip/crew/domain/entity/Post.java index 4157575..1fd8d01 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Post.java +++ b/src/main/java/com/retrip/crew/domain/entity/Post.java @@ -1,6 +1,6 @@ package com.retrip.crew.domain.entity; -import com.retrip.crew.domain.exception.PostUpdateException; +import com.retrip.crew.domain.exception.PostUpdateFailedException; import com.retrip.crew.domain.vo.PostContent; import com.retrip.crew.domain.vo.PostTitle; @@ -20,16 +20,18 @@ public class Post extends BaseEntity { @Column(columnDefinition = "varbinary(16)") private UUID id; - @Embedded private PostTitle title; + @Embedded + private PostTitle title; - @Embedded private PostContent content; + @Embedded + private PostContent content; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn( name = "crew_id", nullable = false, columnDefinition = "varbinary(16)", - foreignKey = @ForeignKey(name = "fk_free_board_to_crew")) + foreignKey = @ForeignKey(name = "fk_post_to_crew")) private Crew crew; private Post(String title, String content, Crew crew) { @@ -43,15 +45,15 @@ public static Post create(String title, String content, Crew crew) { return new Post(title, content, crew); } - public void update(String title, String content, UUID userId) { - // isUpdatable(this.getCreatedBy(), userId); + public void update(String title, String content, UUID memberId) { + // isUpdatable(this.getCreatedBy(), memberId); this.title = new PostTitle(title); this.content = new PostContent(content); } private void isUpdatable(UUID createBy, UUID updateBy) { if (createBy != updateBy) { - throw new PostUpdateException(); + throw new PostUpdateFailedException(); } } diff --git a/src/main/java/com/retrip/crew/domain/entity/Posts.java b/src/main/java/com/retrip/crew/domain/entity/Posts.java index b9d73d8..ebb396f 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Posts.java +++ b/src/main/java/com/retrip/crew/domain/entity/Posts.java @@ -1,6 +1,6 @@ package com.retrip.crew.domain.entity; -import com.retrip.crew.domain.exception.PostDeleteException; +import com.retrip.crew.domain.exception.PostDeleteFailedException; import com.retrip.crew.domain.exception.PostNotFoundException; import jakarta.persistence.CascadeType; import jakarta.persistence.Embeddable; @@ -27,20 +27,26 @@ private List createEmptyValues() { return new ArrayList<>(); } - public Post updatePost(UUID postId, String title, String content, UUID userId) { - Post post = this.values.stream().filter(p -> p.getId().equals(postId)).findAny() - .orElseThrow(PostNotFoundException::new); - post.update(title, content, userId); + public Post updatePost(UUID postId, String title, String content, UUID memberId) { + Post post = findPost(postId); + post.update(title, content, memberId); return post; } - public UUID deletePost(UUID postId, CrewMember crewMember) { - Post post = this.values.stream().filter(p -> p.getId().equals(postId)).findAny() - .orElseThrow(PostNotFoundException::new); + public void deletePost(UUID postId, CrewMember crewMember) { + Post post = findPost(postId); if (!post.isDeletable(crewMember)) { - throw new PostDeleteException(); + throw new PostDeleteFailedException(); } this.values.remove(post); - return post.getId(); + } + + private Post findPost(UUID postId) { + return this.values.stream().filter(p -> p.getId().equals(postId)).findAny() + .orElseThrow(PostNotFoundException::new); + } + + public void add(Post post) { + this.values.add(post); } } diff --git a/src/main/java/com/retrip/crew/domain/exception/PostDeleteException.java b/src/main/java/com/retrip/crew/domain/exception/PostDeleteFailedException.java similarity index 62% rename from src/main/java/com/retrip/crew/domain/exception/PostDeleteException.java rename to src/main/java/com/retrip/crew/domain/exception/PostDeleteFailedException.java index 5c8758b..644b87f 100644 --- a/src/main/java/com/retrip/crew/domain/exception/PostDeleteException.java +++ b/src/main/java/com/retrip/crew/domain/exception/PostDeleteFailedException.java @@ -1,13 +1,12 @@ package com.retrip.crew.domain.exception; import com.retrip.crew.domain.exception.common.BusinessException; -import com.retrip.crew.domain.exception.common.EntityNotFoundException; import com.retrip.crew.domain.exception.common.ErrorCode; -public class PostDeleteException extends BusinessException { +public class PostDeleteFailedException extends BusinessException { private static final ErrorCode errorCode = ErrorCode.POST_DELETE_FAIL; - public PostDeleteException() { + public PostDeleteFailedException() { super(errorCode); } } diff --git a/src/main/java/com/retrip/crew/domain/exception/PostUpdateException.java b/src/main/java/com/retrip/crew/domain/exception/PostUpdateFailedException.java similarity index 72% rename from src/main/java/com/retrip/crew/domain/exception/PostUpdateException.java rename to src/main/java/com/retrip/crew/domain/exception/PostUpdateFailedException.java index 923d5ed..de5744d 100644 --- a/src/main/java/com/retrip/crew/domain/exception/PostUpdateException.java +++ b/src/main/java/com/retrip/crew/domain/exception/PostUpdateFailedException.java @@ -3,10 +3,10 @@ import com.retrip.crew.domain.exception.common.BusinessException; import com.retrip.crew.domain.exception.common.ErrorCode; -public class PostUpdateException extends BusinessException { +public class PostUpdateFailedException extends BusinessException { private static final ErrorCode errorCode = ErrorCode.POST_UPDATE_FAIL; - public PostUpdateException() { + public PostUpdateFailedException() { super(errorCode); } } diff --git a/src/main/java/com/retrip/crew/domain/vo/PostContent.java b/src/main/java/com/retrip/crew/domain/vo/PostContent.java index bc57e38..ec05b06 100644 --- a/src/main/java/com/retrip/crew/domain/vo/PostContent.java +++ b/src/main/java/com/retrip/crew/domain/vo/PostContent.java @@ -13,7 +13,7 @@ @EqualsAndHashCode @NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) public class PostContent { - private static final int CONTENT_LENGTH_LIMIT = 500; + private static final int CONTENT_LENGTH_LIMIT = 200; @Column(name = "content", nullable = false, length = CONTENT_LENGTH_LIMIT) private final String value; diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/PostController.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/PostController.java index 58ffbfe..e7b543d 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/PostController.java +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/PostController.java @@ -29,7 +29,7 @@ public class PostController { private final GetPostUseCase getPostUseCase; @PostMapping("/{crewId}/posts") - @Schema(description = "크루 자유 게시판 생성") + @Schema(description = "자유 게시글 생성") public ApiResponse createPost( @PathVariable UUID crewId, @RequestBody CreatePostRequest request) { CreatePostResponse post = managePostUseCase.createPost(crewId, request); @@ -37,7 +37,7 @@ public ApiResponse createPost( } @PutMapping("/{crewId}/posts/{postId}") - @Schema(description = "크루 자유 게시판 수정") + @Schema(description = "자유 게시글 수정") public ApiResponse updatePost( @PathVariable UUID crewId, @PathVariable UUID postId, @@ -47,11 +47,11 @@ public ApiResponse updatePost( } @DeleteMapping("/{crewId}/posts/{postId}") - @Schema(description = "크루 자유 게시판 삭제") - public ApiResponse deletePost( - @PathVariable UUID crewId, @PathVariable UUID postId, @RequestParam UUID userId) { - DeletePostResponse post = managePostUseCase.deletePost(crewId, postId, userId); - return ApiResponse.created(post); + @Schema(description = "자유 게시글 삭제") + public ApiResponse deletePost( + @PathVariable UUID crewId, @PathVariable UUID postId, @RequestParam UUID memberId) { + managePostUseCase.deletePost(crewId, postId, memberId); + return ApiResponse.noContent(); } @GetMapping("/{crewId}/posts") diff --git a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewMemberQuerydslRepository.java b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewMemberQuerydslRepository.java index 721e33a..fec0204 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewMemberQuerydslRepository.java +++ b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewMemberQuerydslRepository.java @@ -18,11 +18,11 @@ public class CrewMemberQuerydslRepository implements CrewMemberQueryRepository { private final JPAQueryFactory query; @Override - public Optional findCrewMemberByUserId(UUID crewId, UUID userId) { + public Optional findCrewMemberByMemberId(UUID crewId, UUID memberId) { return Optional.ofNullable( query.selectFrom(crewMember) .join(crew) - .on(crewMember.crew.id.eq(crewId), crewMember.memberId.eq(userId)) + .on(crewMember.crew.id.eq(crewId), crewMember.memberId.eq(memberId)) .fetchOne()); } } diff --git a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java index 9434068..22e8696 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java +++ b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java @@ -1,11 +1,5 @@ package com.retrip.crew.infra.adapter.out.persistence.mysql.query; -import static com.querydsl.jpa.JPAExpressions.select; -import static com.retrip.crew.domain.entity.QCrew.crew; -import static com.retrip.crew.domain.entity.QCrewMember.crewMember; -import static com.retrip.crew.domain.entity.QPost.post; -import static com.retrip.crew.infra.util.PaginationUtils.checkEndPage; - import com.querydsl.core.types.ExpressionUtils; import com.querydsl.core.types.Order; import com.querydsl.core.types.OrderSpecifier; @@ -23,9 +17,6 @@ import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.entity.CrewMemberRole; import com.retrip.crew.domain.entity.QCrewMember; - -import java.util.UUID; - import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -36,6 +27,13 @@ import java.util.List; import java.util.Optional; +import java.util.UUID; + +import static com.querydsl.jpa.JPAExpressions.select; +import static com.retrip.crew.domain.entity.QCrew.crew; +import static com.retrip.crew.domain.entity.QCrewMember.crewMember; +import static com.retrip.crew.domain.entity.QPost.post; +import static com.retrip.crew.infra.util.PaginationUtils.checkEndPage; @Repository @RequiredArgsConstructor @@ -78,7 +76,11 @@ public Slice getCrews(Pageable pageable, String keyword) { @Override public Long getCrewCount(String keyword) { - return query.select(crew.count()).from(crew).where(crewTitleContains(keyword)).fetchOne(); + return query + .select(crew.count()) + .from(crew) + .where(crewTitleContains(keyword)) + .fetchOne(); } @Override diff --git a/src/test/java/com/retrip/crew/application/in/PostServiceTest.java b/src/test/java/com/retrip/crew/application/in/PostServiceTest.java index b3b0af1..185ee67 100644 --- a/src/test/java/com/retrip/crew/application/in/PostServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/PostServiceTest.java @@ -1,26 +1,23 @@ package com.retrip.crew.application.in; -import static com.retrip.crew.common.fixture.PostFixture.MEMBER_ID; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - import com.retrip.crew.application.in.factory.BasePostServiceTest; import com.retrip.crew.application.in.request.CreatePostRequest; import com.retrip.crew.application.in.request.PostOrder; import com.retrip.crew.application.in.request.UpdatePostRequest; import com.retrip.crew.application.in.response.CreatePostResponse; -import com.retrip.crew.application.in.response.DeletePostResponse; import com.retrip.crew.application.in.response.PostResponse; import com.retrip.crew.application.in.response.UpdatePostResponse; import com.retrip.crew.common.fixture.PostFixture; import com.retrip.crew.domain.entity.Crew; - import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; import org.junit.jupiter.api.Test; -import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import static com.retrip.crew.common.fixture.PostFixture.MEMBER_ID; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + class PostServiceTest extends BasePostServiceTest { @Test @@ -132,10 +129,10 @@ class PostServiceTest extends BasePostServiceTest { ; // when - DeletePostResponse response = - postService.deletePost(crew.getId(), createPostResponse.id(), MEMBER_ID); // then - assertThat(response.id()).isNotNull(); + assertDoesNotThrow( + () -> postService.deletePost(crew.getId(), createPostResponse.id(), MEMBER_ID) + ); } } diff --git a/src/test/java/com/retrip/crew/domain/entity/PostTest.java b/src/test/java/com/retrip/crew/domain/entity/PostTest.java index 91a151f..ec25d6c 100644 --- a/src/test/java/com/retrip/crew/domain/entity/PostTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/PostTest.java @@ -1,14 +1,15 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.domain.exception.common.InvalidValueException; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import java.util.UUID; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; class PostTest { @Test @@ -30,6 +31,29 @@ class PostTest { ).doesNotThrowAnyException(); } + @ParameterizedTest + @CsvSource(value = { + "속초 여행 코스 추천해주세요! 어디가 제일 좋았나요?&친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~", + "속초 여행 코드 추천&친구들과 오랜만에 시간을 맞춰 속초로 여행을 가려고 해요. 다 함께 즐거운 추억을 만들고 싶은데, 어디를 가야 좋을지 고민 중입니다. 바다도 보고 맛있는 것도 먹고, 예쁜 사진도 많이 찍고 싶어요. 속초에 가보신 분들, 꼭 가봐야 할 명소나 맛집, 숨겨진 핫플레이스가 있다면 추천 좀 부탁드릴게요. 다양한 경험을 들려주시면 여행 계획에 큰 도움이 될 것 같아요!", + }, delimiter = '&') + void 자유_게시판_작성에_실패한다(String title, String content) { + //given + Crew crew = Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 5, + UUID.randomUUID()); + + + //then + assertThrows(InvalidValueException.class, + () -> Post.create( + title, + content, + crew) + ); + } + @Test void 자유_게시판을_리더는_삭제할_수_있다() { //given @@ -50,7 +74,7 @@ class PostTest { } @Test - void 자유_게시판을_수정할_수_있다() { + void 자유_게시판을_수정한다() { //given Crew crew = Crew.create( "속초 크루원 구함", @@ -62,7 +86,7 @@ class PostTest { "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~", crew); - post.update("강릉 여행 어디가 좋아요?", "친구들과 강릉 여행 가려고 합니다. 강릉 여행지 추천해 주세요~",crew.getLeader().getMemberId()); + post.update("강릉 여행 어디가 좋아요?", "친구들과 강릉 여행 가려고 합니다. 강릉 여행지 추천해 주세요~", crew.getLeader().getMemberId()); //then assertEquals(post.getTitle().getValue(), "강릉 여행 어디가 좋아요?");