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 8958dd1..e1518fd 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -49,8 +49,8 @@ import com.retrip.crew.domain.vo.IntroductionTitle; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; import com.retrip.crew.infra.util.PaginationUtils; -import java.util.UUID; import lombok.RequiredArgsConstructor; +import java.util.UUID; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -163,8 +163,10 @@ private Demand findDemandByIdAndCrewId(UUID demandId, UUID crewId) { @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()); 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..c56b359 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/PostService.java @@ -0,0 +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.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.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.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +@Service +@Transactional +@RequiredArgsConstructor +public class PostService implements ManagePostUseCase, GetPostUseCase { + private final CrewQueryRepository crewQueryRepository; + private final CrewMemberQueryRepository crewMemberQueryRepository; + private final PostQueryRepository postQueryRepository; + + @Override + public CreatePostResponse createPost(UUID crewId, CreatePostRequest request) { + Crew crew = + crewQueryRepository + .findByIdWithPosts(crewId) + .orElseThrow(CrewNotFoundException::new); + Post post = request.to(crew); + crew.getPosts().add(post); + return CreatePostResponse.of(post); + } + + @Override + public UpdatePostResponse updatePost(UUID crewId, UUID postId, UpdatePostRequest request) { + Crew crew = + crewQueryRepository + .findByIdWithPosts(crewId) + .orElseThrow(CrewNotFoundException::new); + Post post = crew.getPosts().updatePost(postId, request.title(), request.content(), request.memberId()); + return UpdatePostResponse.of(post); + } + + @Override + public void deletePost(UUID crewId, UUID postId, UUID memberId) { + Crew crew = + crewQueryRepository + .findByIdWithPosts(crewId) + .orElseThrow(CrewNotFoundException::new); + CrewMember crewMember = findCrewMemberByMemberId(crewId, memberId); + crew.getPosts().deletePost(postId, crewMember); + } + + private CrewMember findCrewMemberByMemberId(UUID crewId, UUID memberId) { + return crewMemberQueryRepository + .findCrewMemberByMemberId(crewId, memberId) + .orElseThrow(CrewMemberNotFoundException::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/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/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/request/UpdatePostRequest.java b/src/main/java/com/retrip/crew/application/in/request/UpdatePostRequest.java new file mode 100644 index 0000000..d40b03d --- /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 memberId +) { + +} 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/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/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/GetPostUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/GetPostUseCase.java new file mode 100644 index 0000000..18e1e43 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/usecase/GetPostUseCase.java @@ -0,0 +1,15 @@ +package com.retrip.crew.application.in.usecase; + +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.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/ManageCrewUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManageCrewUseCase.java index 6131c7f..760fdf0 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.crew.CrewCreateResponse; import com.retrip.crew.application.in.response.crew.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..56c7179 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManagePostUseCase.java @@ -0,0 +1,23 @@ +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.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 crewId, UUID postId, UpdatePostRequest request); + + 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 new file mode 100644 index 0000000..3f18706 --- /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 findCrewMemberByMemberId(UUID crewId, 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 836dcec..bada9a6 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 @@ -2,6 +2,8 @@ import com.retrip.crew.application.in.response.crew.CrewListResponse; import com.retrip.crew.application.in.response.demand.CrewsOfDemandResponse; +import com.retrip.crew.domain.entity.Crew; +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -11,6 +13,6 @@ public interface CrewQueryRepository { Slice getCrews(Pageable pageable, String keyword); Long getCrewCount(String keyword); - + Optional findByIdWithPosts(UUID id); Page findAllContainsMember(Pageable pageable, UUID memberId); } 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/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/Crew.java b/src/main/java/com/retrip/crew/domain/entity/Crew.java index 114f580..dca0285 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Crew.java +++ b/src/main/java/com/retrip/crew/domain/entity/Crew.java @@ -2,16 +2,19 @@ import com.retrip.crew.domain.vo.CrewDescription; import com.retrip.crew.domain.vo.CrewTitle; + import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Version; -import java.util.UUID; + import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.UUID; + @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @@ -20,29 +23,21 @@ public class Crew extends BaseEntity { @Column(columnDefinition = "varbinary(16)") private UUID id; - @Version - private long version; + @Version private long version; - @Embedded - private CrewTitle title; + @Embedded private CrewTitle title; - @Embedded - private CrewDescription description; + @Embedded private CrewDescription description; - @Embedded - private CrewMembers crewMembers; + @Embedded private CrewMembers crewMembers; - @Embedded - private Posts posts; + @Embedded private Posts posts; - @Embedded - private Announcements announcements; + @Embedded private Announcements announcements; - @Embedded - private Introductions introductions; + @Embedded private Introductions introductions; - @Embedded - private Recruitment recruitment; + @Embedded private Recruitment recruitment; private Crew(String name, String description, int maxMembers, UUID leader) { this.id = UUID.randomUUID(); @@ -72,7 +67,7 @@ public void stopRecruitment() { this.recruitment.stop(); } - public void addIntroduction(Introduction introduction){ + public void addIntroduction(Introduction introduction) { this.introductions.addIntroduction(introduction); } @@ -85,7 +80,7 @@ public Demand demand(UUID memberId) { return recruitment.addDemand(memberId, this); } - public String getDescription(){ + public String getDescription() { return description.getValue(); } 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 fd290b2..f8d3e24 100644 --- a/src/main/java/com/retrip/crew/domain/entity/CrewMember.java +++ b/src/main/java/com/retrip/crew/domain/entity/CrewMember.java @@ -40,4 +40,8 @@ public CrewMember(Crew crew, UUID memberId, CrewMemberRole crewMemberRole) { public boolean isLeader() { return CrewMemberRole.isLeaderRole(this.crewMemberRole); } + + 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..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,8 +1,11 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.domain.exception.PostUpdateFailedException; 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; @@ -22,13 +25,41 @@ public class Post extends BaseEntity { @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) { + 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 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 PostUpdateFailedException(); + } + } + + public boolean isDeletable(CrewMember crewMember) { + 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..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,11 +1,14 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.domain.exception.PostDeleteFailedException; +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,27 @@ public Posts() { private List createEmptyValues() { return new ArrayList<>(); } + + public Post updatePost(UUID postId, String title, String content, UUID memberId) { + Post post = findPost(postId); + post.update(title, content, memberId); + return post; + } + + public void deletePost(UUID postId, CrewMember crewMember) { + Post post = findPost(postId); + if (!post.isDeletable(crewMember)) { + throw new PostDeleteFailedException(); + } + this.values.remove(post); + } + + 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/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/PostDeleteFailedException.java b/src/main/java/com/retrip/crew/domain/exception/PostDeleteFailedException.java new file mode 100644 index 0000000..644b87f --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/PostDeleteFailedException.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 PostDeleteFailedException extends BusinessException { + private static final ErrorCode errorCode = ErrorCode.POST_DELETE_FAIL; + + public PostDeleteFailedException() { + 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/PostUpdateFailedException.java b/src/main/java/com/retrip/crew/domain/exception/PostUpdateFailedException.java new file mode 100644 index 0000000..de5744d --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/PostUpdateFailedException.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 PostUpdateFailedException extends BusinessException { + private static final ErrorCode errorCode = ErrorCode.POST_UPDATE_FAIL; + + public PostUpdateFailedException() { + 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 bea0b8c..10635f6 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 @@ -20,6 +20,10 @@ public enum ErrorCode { ILLEGAL_DEMAND_STATE(BAD_REQUEST, "Crew-004", "이미 크루 참여를 요청한 사용자는 다시 요청할 수 없습니다."), INTRODUCTION_NOT_FOUND(BAD_REQUEST, "Crew-005", "크루 자기소개 엔티티를 찾을 수 없습니다."), INVALID_ACCESS(FORBIDDEN, "Crew-006","접근 권한이 존재하지 않습니다."), + CREW_MEMBER_NOT_FOUND(BAD_REQUEST, "Crew-007", "크루원 멤버 엔티티를 찾을 수 없습니다.."), + POST_NOT_FOUND(BAD_REQUEST, "Crew-008", "자유 게시글 엔티티를 찾을 수 없습니다."), + POST_UPDATE_FAIL(FORBIDDEN, "Crew-009", "자유 게시글을 수정할 권한이 없습니다."), + POST_DELETE_FAIL(FORBIDDEN, "Crew-010", "자유 게시글을 삭제할 권한이 없습니다.") ; private final HttpStatus status; 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 new file mode 100644 index 0000000..e7b543d --- /dev/null +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/PostController.java @@ -0,0 +1,69 @@ +package com.retrip.crew.infra.adapter.in.presentation.rest; + +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.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.Pageable; +import org.springframework.data.web.PageableDefault; +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; + private final GetPostUseCase getPostUseCase; + + @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(crewId, postId, request); + return ApiResponse.ok(post); + } + + @DeleteMapping("/{crewId}/posts/{postId}") + @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") + @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 new file mode 100644 index 0000000..fec0204 --- /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 findCrewMemberByMemberId(UUID crewId, UUID memberId) { + return Optional.ofNullable( + query.selectFrom(crewMember) + .join(crew) + .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 1f8f53c..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 @@ -14,6 +14,7 @@ import com.retrip.crew.application.in.response.crew.CrewListResponse; import com.retrip.crew.application.in.response.demand.CrewsOfDemandResponse; import com.retrip.crew.application.out.repository.CrewQueryRepository; +import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.entity.CrewMemberRole; import com.retrip.crew.domain.entity.QCrewMember; import lombok.RequiredArgsConstructor; @@ -25,11 +26,13 @@ import org.springframework.stereotype.Repository; 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 @@ -73,14 +76,22 @@ public Slice getCrews(Pageable pageable, String keyword) { @Override public Long getCrewCount(String keyword) { - return query.select(crew.count()) + return query + .select(crew.count()) .from(crew) - .where( - crewTitleContains(keyword) - ) + .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) { return title == null ? null : crew.title.value.contains(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 new file mode 100644 index 0000000..185ee67 --- /dev/null +++ b/src/test/java/com/retrip/crew/application/in/PostServiceTest.java @@ -0,0 +1,138 @@ +package com.retrip.crew.application.in; + +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.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.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 + 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)); + 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)); + 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(); + } + + @Test + void 자유_게시글을_삭제한다() { + + // given + Crew crew = + crewRepository.save( + Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 100, + MEMBER_ID)); + CreatePostResponse createPostResponse = + postService.createPost( + crew.getId(), + PostFixture.createRequest( + "속초 여행 어디가 좋아요?", "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~")); + ; + + // when + + // then + assertDoesNotThrow( + () -> postService.deletePost(crew.getId(), createPostResponse.id(), MEMBER_ID) + ); + } +} diff --git a/src/test/java/com/retrip/crew/application/in/factory/BasePostServiceTest.java b/src/test/java/com/retrip/crew/application/in/factory/BasePostServiceTest.java new file mode 100644 index 0000000..f2c8b1a --- /dev/null +++ b/src/test/java/com/retrip/crew/application/in/factory/BasePostServiceTest.java @@ -0,0 +1,42 @@ +package com.retrip.crew.application.in.factory; + +import com.querydsl.jpa.impl.JPAQueryFactory; +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; + +@DataJpaTest +@Import(QuerydslConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public abstract class BasePostServiceTest { + @Autowired protected CrewRepository crewRepository; + + @Autowired protected CrewMemberRepository crewMemberRepository; + + @Autowired JPAQueryFactory jpaQueryFactory; + + protected CrewQueryRepository crewQueryRepository; + + protected CrewMemberQueryRepository crewMemberQueryRepository; + protected PostQueryRepository postQueryRepository; + protected PostService postService; + + @BeforeEach + void setUp() { + crewQueryRepository = new CrewQuerydslRepository(jpaQueryFactory); + crewMemberQueryRepository = new CrewMemberQuerydslRepository(jpaQueryFactory); + postQueryRepository = new PostQuerydslRepository(jpaQueryFactory); + postService = + new PostService( + crewQueryRepository, crewMemberQueryRepository, postQueryRepository); + } +} 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 d5c1b22..e7877fb 100644 --- a/src/test/java/com/retrip/crew/common/config/QuerydslConfig.java +++ b/src/test/java/com/retrip/crew/common/config/QuerydslConfig.java @@ -1,9 +1,13 @@ 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 com.retrip.crew.infra.adapter.out.persistence.mysql.query.IntroductionQuerydslRepository; + import jakarta.persistence.EntityManager; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; @@ -13,8 +17,7 @@ @TestConfiguration public class QuerydslConfig { - @Autowired - EntityManager entityManager; + @Autowired EntityManager entityManager; @Bean public JPAQueryFactory jpaQueryFactory() { @@ -27,7 +30,13 @@ public CrewQuerydslRepository crewQuerydslRepository(JPAQueryFactory jpaQueryFac } @Bean - public IntroductionQuerydslRepository introductionQuerydslRepository(JPAQueryFactory jpaQueryFactory) { + public CrewMemberQueryRepository crewMemberQueryRepository(JPAQueryFactory jpaQueryFactory) { + return new CrewMemberQuerydslRepository(jpaQueryFactory); + } + + @Bean + public IntroductionQuerydslRepository introductionQuerydslRepository( + JPAQueryFactory jpaQueryFactory) { return new IntroductionQuerydslRepository(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..9b05ea4 --- /dev/null +++ b/src/test/java/com/retrip/crew/common/fixture/PostFixture.java @@ -0,0 +1,24 @@ +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 UUID MEMBER_ID = UUID.fromString("13c8ab91-76bc-4f70-93e9-89f1a65dc640"); + + 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..ec25d6c --- /dev/null +++ b/src/test/java/com/retrip/crew/domain/entity/PostTest.java @@ -0,0 +1,95 @@ +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.assertThatCode; +import static org.junit.jupiter.api.Assertions.*; + +class PostTest { + @Test + void 자유_게시판을_생성한다() { + //given + Crew crew = Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 5, + UUID.randomUUID()); + + + //then + assertThatCode(() -> + Post.create( + "속초 여행 어디가 좋아요?", + "친구들과 속초 여행 가려고 합니다. 속초 여행지 추천해 주세요~", + crew) + ).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 + 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(), "친구들과 강릉 여행 가려고 합니다. 강릉 여행지 추천해 주세요~"); + } +}