From 602d64f8bb36347ac3578c8fd5b6bc899341c7ad Mon Sep 17 00:00:00 2001 From: junhokim Date: Tue, 18 Feb 2025 07:48:24 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20PR=20=ED=85=9C=ED=94=8C=EB=A6=BF?= =?UTF-8?q?=20and=20PR=20Discord=20=EC=95=8C=EB=A6=BC=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/PULL_REQUEST_TEMPLATE.md | 31 +++++++++++++++++++++++++++++++ .github/workflows/PR.yml | 14 ++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/PR.yml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..b85803e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ +## πŸ” PR νƒ€μž… 선택 + +μ•„λž˜ νƒ€μž… 쀑 ν•΄λ‹Ήν•˜λŠ” ν•˜λ‚˜λ₯Ό 선택해 μ£Όμ„Έμš”. λ°˜λ“œμ‹œ ν•˜λ‚˜λ§Œ 선택해 μ£Όμ„Έμš”. + +- [ ] `feat`: μƒˆλ‘œμš΄ κΈ°λŠ₯ μΆ”κ°€ +- [ ] `fix`: 버그 μˆ˜μ • +- [ ] `docs`: λ¬Έμ„œ μˆ˜μ • +- [ ] `style`: μ½”λ“œ ν¬λ§·νŒ…, μ„Έλ―Έμ½œλ‘  λˆ„λ½, μ½”λ“œ 변경이 μ—†λŠ” 경우 +- [ ] `refactor`: μ½”λ“œ λ¦¬νŒ©ν† λ§ +- [ ] `test`: ν…ŒμŠ€νŠΈ μ½”λ“œ μΆ”κ°€ λ˜λŠ” μˆ˜μ • +- [ ] `chore`: λΉŒλ“œ 업무 μˆ˜μ •, νŒ¨ν‚€μ§€ λ§€λ‹ˆμ € μˆ˜μ • λ“± 기타 μž‘μ—… + + +## πŸ“ λ³€κ²½ 사항 μš”μ•½ + +λ³€κ²½ 사항을 κ°„λ‹¨νžˆ μš”μ•½ν•΄ μ£Όμ„Έμš”. + +- 예: μ‚¬μš©μž ν”„λ‘œν•„ νŽ˜μ΄μ§€ μΆ”κ°€ +- 예: 둜그인 버그 μˆ˜μ • + + +## πŸ›  κ΄€λ ¨ 이슈 + +Resolves: #123 +Ref: #456 +Related to: #48, #45 +close: #번호 + +### **μΆ”κ°€ μ„€λͺ… (선택 사항)** + +λ³€κ²½ 사항에 λŒ€ν•œ μΆ”κ°€ μ„€λͺ…을 μž‘μ„±ν•΄ μ£Όμ„Έμš”. diff --git a/.github/workflows/PR.yml b/.github/workflows/PR.yml new file mode 100644 index 0000000..b606d50 --- /dev/null +++ b/.github/workflows/PR.yml @@ -0,0 +1,14 @@ +name: PR to Discord + +on: + pull_request: + branches: [ "develop" ] + types: [opened, reopened] + +jobs: + PR: + runs-on: ubuntu-latest + steps: + - uses: Team-Retrip/PR-to-Discord@main + with: + discord_webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }} From 51e14f1917d096277a9b134571c1c1a94e0b4191 Mon Sep 17 00:00:00 2001 From: Kimjunho <39546306+xjvmdutl@users.noreply.github.com> Date: Sun, 23 Feb 2025 16:22:16 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20Crew=20=EC=83=9D=EC=84=B1=20(#20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Crew 생성 * feat: Code 리뷰 반영, Exception μΆ”κ°€ * feat: 주석 제거 및 μ½”λ“œ μˆ˜μ • --------- Co-authored-by: junhokim --- build.gradle | 1 + .../crew/application/in/CrewService.java | 4 +- .../in/request/CrewCreateRequest.java | 11 ++-- .../in/response/CrewCreateResponse.java | 16 ++++-- .../converter/CrewMemberRoleConverter.java | 24 +++++++++ .../crew/domain/entity/Announcement.java | 35 +++++++++++++ .../crew/domain/entity/Announcements.java | 26 ++++++++++ .../com/retrip/crew/domain/entity/Crew.java | 51 +++++++++++++++---- .../retrip/crew/domain/entity/CrewMember.java | 40 +++++++++++++++ .../crew/domain/entity/CrewMemberRole.java | 25 +++++++++ .../crew/domain/entity/CrewMembers.java | 32 ++++++++++++ .../crew/domain/entity/Introduction.java | 34 +++++++++++++ .../crew/domain/entity/Introductions.java | 24 +++++++++ .../com/retrip/crew/domain/entity/Post.java | 34 +++++++++++++ .../com/retrip/crew/domain/entity/Posts.java | 26 ++++++++++ .../AnnouncementContentException.java | 8 +++ .../exception/AnnouncementTitleException.java | 8 +++ .../exception/CrewDescriptionException.java | 8 +++ .../crew/domain/exception/CrewErrorCode.java | 17 +++++++ .../crew/domain/exception/CrewException.java | 10 ++++ .../domain/exception/CrewTitleException.java | 7 +++ .../IntroductionContentException.java | 9 ++++ .../exception/IntroductionTitleException.java | 9 ++++ .../exception/PostContentException.java | 8 +++ .../domain/exception/PostTitleException.java | 8 +++ .../crew/domain/vo/AnnouncementContent.java | 31 +++++++++++ .../crew/domain/vo/AnnouncementTitle.java | 31 +++++++++++ .../crew/domain/vo/CrewDescription.java | 31 +++++++++++ .../com/retrip/crew/domain/vo/CrewTitle.java | 31 +++++++++++ .../crew/domain/vo/IntroductionContent.java | 31 +++++++++++ .../crew/domain/vo/IntroductionTitle.java | 32 ++++++++++++ .../domain/vo/NotificationBoardContent.java | 38 ++++++++++++++ .../retrip/crew/domain/vo/PostContent.java | 31 +++++++++++ .../com/retrip/crew/domain/vo/PostTitle.java | 31 +++++++++++ .../com/retrip/crew/CrewApplicationTests.java | 9 ++-- .../crew/application/in/CrewServiceTest.java | 44 ++++++++++++++++ .../retrip/crew/domain/entity/CrewTest.java | 15 +++++- .../domain/vo/CrewIntroductionTitleTest.java | 16 ++++++ 38 files changed, 822 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/retrip/crew/domain/converter/CrewMemberRoleConverter.java create mode 100644 src/main/java/com/retrip/crew/domain/entity/Announcement.java create mode 100644 src/main/java/com/retrip/crew/domain/entity/Announcements.java create mode 100644 src/main/java/com/retrip/crew/domain/entity/CrewMember.java create mode 100644 src/main/java/com/retrip/crew/domain/entity/CrewMemberRole.java create mode 100644 src/main/java/com/retrip/crew/domain/entity/CrewMembers.java create mode 100644 src/main/java/com/retrip/crew/domain/entity/Introduction.java create mode 100644 src/main/java/com/retrip/crew/domain/entity/Introductions.java create mode 100644 src/main/java/com/retrip/crew/domain/entity/Post.java create mode 100644 src/main/java/com/retrip/crew/domain/entity/Posts.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/AnnouncementContentException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/AnnouncementTitleException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/CrewDescriptionException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/CrewErrorCode.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/CrewException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/CrewTitleException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/IntroductionContentException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/IntroductionTitleException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/PostContentException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/PostTitleException.java create mode 100644 src/main/java/com/retrip/crew/domain/vo/AnnouncementContent.java create mode 100644 src/main/java/com/retrip/crew/domain/vo/AnnouncementTitle.java create mode 100644 src/main/java/com/retrip/crew/domain/vo/CrewDescription.java create mode 100644 src/main/java/com/retrip/crew/domain/vo/CrewTitle.java create mode 100644 src/main/java/com/retrip/crew/domain/vo/IntroductionContent.java create mode 100644 src/main/java/com/retrip/crew/domain/vo/IntroductionTitle.java create mode 100644 src/main/java/com/retrip/crew/domain/vo/NotificationBoardContent.java create mode 100644 src/main/java/com/retrip/crew/domain/vo/PostContent.java create mode 100644 src/main/java/com/retrip/crew/domain/vo/PostTitle.java create mode 100644 src/test/java/com/retrip/crew/application/in/CrewServiceTest.java create mode 100644 src/test/java/com/retrip/crew/domain/vo/CrewIntroductionTitleTest.java diff --git a/build.gradle b/build.gradle index ea4402d..80325ee 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-webflux' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' 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 4cddfcf..661c355 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -14,9 +14,11 @@ @RequiredArgsConstructor public class CrewService implements CreateCrewUseCase { private final CrewRepository crewRepository; + @Override public CrewCreateResponse createCrew(CrewCreateRequest request) { - Crew crew = crewRepository.save(request.to()); + Crew crew = crewRepository.save(request.to(request.leader())); + return CrewCreateResponse.of(crew); } } diff --git a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java b/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java index d840e89..c987d3d 100644 --- a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java +++ b/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java @@ -3,12 +3,17 @@ import com.retrip.crew.domain.entity.Crew; import jakarta.validation.constraints.Size; +import java.util.UUID; + public record CrewCreateRequest( + UUID leader, @Size(min = 1, max = 30) - String title + String title, + @Size(min = 1, max = 500) + String description ){ - public Crew to() { - return Crew.create(this.title); + public Crew to(UUID leader) { + return Crew.create(this.title, this.description, leader); } } diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java b/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java index b062770..b710eba 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java @@ -4,12 +4,18 @@ import java.util.UUID; -public record CrewCreateResponse ( - UUID id -){ - public static CrewCreateResponse of(Crew crew){ +public record CrewCreateResponse( + UUID id, + String title, + String description, + UUID leaderId +) { + public static CrewCreateResponse of(Crew crew) { return new CrewCreateResponse( - crew.getId() + crew.getId(), + crew.getTitle().getValue(), + crew.getDescription().getValue(), + crew.getLeader().getId() ); } } diff --git a/src/main/java/com/retrip/crew/domain/converter/CrewMemberRoleConverter.java b/src/main/java/com/retrip/crew/domain/converter/CrewMemberRoleConverter.java new file mode 100644 index 0000000..fd4ea2c --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/converter/CrewMemberRoleConverter.java @@ -0,0 +1,24 @@ +package com.retrip.crew.domain.converter; + +import com.retrip.crew.domain.entity.CrewMemberRole; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter(autoApply = true) +public class CrewMemberRoleConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(CrewMemberRole crewMemberRole) { + if(crewMemberRole == null){ + throw new NullPointerException("crewMemberRole을 DB 칼럼으둜 λ³€κ²½ν•˜λŠ” κ³Όμ •μ—μ„œ null이 ν¬ν•¨λ˜μ—ˆμŠ΅λ‹ˆλ‹€."); + } + return crewMemberRole.getCode(); + } + + @Override + public CrewMemberRole convertToEntityAttribute(String dbData) { + if(dbData == null){ + throw new NullPointerException("CrewMember ν…Œμ΄λΈ”μ˜ role 값이 nullμž…λ‹ˆλ‹€."); + } + return CrewMemberRole.codeOf(dbData); + } +} diff --git a/src/main/java/com/retrip/crew/domain/entity/Announcement.java b/src/main/java/com/retrip/crew/domain/entity/Announcement.java new file mode 100644 index 0000000..ca4def9 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/entity/Announcement.java @@ -0,0 +1,35 @@ +package com.retrip.crew.domain.entity; + +import com.retrip.crew.domain.vo.AnnouncementContent; +import com.retrip.crew.domain.vo.AnnouncementTitle; +import com.retrip.crew.domain.vo.NotificationBoardContent; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class Announcement extends BaseEntity { + @Id + @Column(columnDefinition = "varbinary(16)") + private UUID id; + + @Embedded + private AnnouncementTitle title; + + @Embedded + private AnnouncementContent content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn( + name = "crew_id", + nullable = false, + columnDefinition = "varbinary(16)", + foreignKey = @ForeignKey(name = "fk_notification_board_to_crew") + ) + private Crew crew; +} diff --git a/src/main/java/com/retrip/crew/domain/entity/Announcements.java b/src/main/java/com/retrip/crew/domain/entity/Announcements.java new file mode 100644 index 0000000..9a85944 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/entity/Announcements.java @@ -0,0 +1,26 @@ +package com.retrip.crew.domain.entity; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.OneToMany; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Embeddable +public class Announcements { + @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) + private List values = new ArrayList<>(); + + public Announcements() { + this.values = createEmptyValues(); + } + + private List createEmptyValues() { + return new ArrayList<>(); + } +} 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 feb000c..d2c9f28 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Crew.java +++ b/src/main/java/com/retrip/crew/domain/entity/Crew.java @@ -1,9 +1,8 @@ package com.retrip.crew.domain.entity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Version; +import com.retrip.crew.domain.vo.CrewDescription; +import com.retrip.crew.domain.vo.CrewTitle; +import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -12,24 +11,54 @@ @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter public class Crew extends BaseEntity { @Id @Column(columnDefinition = "varbinary(16)") + @Getter private UUID id; - @Column - private String title; + @Getter + @Embedded + private CrewTitle title; + + @Embedded + @Getter + private CrewDescription description; + + + @Embedded + private CrewMembers crewMembers; + + + @Embedded + private Posts posts; + + @Embedded + private Announcements announcements; + + @Embedded + private Introductions introductions; + @Version private long version; - private Crew(String title) { + private Crew(String name, String description, UUID leader) { this.id = UUID.randomUUID(); - this.title = title; + this.title = new CrewTitle(name); + this.description = new CrewDescription(description); + this.crewMembers = new CrewMembers(this, leader); + this.posts = new Posts(); + this.announcements = new Announcements(); + this.introductions = new Introductions(); } - public static Crew create(String title) { - return new Crew(title); + public static Crew create(String title, String description, UUID leader) { + return new Crew(title, description, leader); } + + public CrewMember getLeader() { + return crewMembers.getLeader(); + } + } diff --git a/src/main/java/com/retrip/crew/domain/entity/CrewMember.java b/src/main/java/com/retrip/crew/domain/entity/CrewMember.java new file mode 100644 index 0000000..7a2c6f1 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/entity/CrewMember.java @@ -0,0 +1,40 @@ +package com.retrip.crew.domain.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.UUID; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +@Getter +public class CrewMember extends BaseEntity { + @Id + @Column(columnDefinition = "varbinary(16)") + private UUID id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn( + name = "crew_id", + nullable = false, + columnDefinition = "varbinary(16)", + foreignKey = @ForeignKey(name = "fk_crew_member_to_crew") + ) + private Crew crew; + + @Column(name = "role", length = 50, nullable = false) + private CrewMemberRole crewMemberRole; + + @Column(nullable = false) + private UUID memberId; + + public CrewMember(Crew crew, UUID memberId, CrewMemberRole crewMemberRole) { + this.id = UUID.randomUUID(); + this.crew = crew; + this.memberId = memberId; + this.crewMemberRole = CrewMemberRole.valueOf(crewMemberRole.name()); + } +} diff --git a/src/main/java/com/retrip/crew/domain/entity/CrewMemberRole.java b/src/main/java/com/retrip/crew/domain/entity/CrewMemberRole.java new file mode 100644 index 0000000..86072f2 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/entity/CrewMemberRole.java @@ -0,0 +1,25 @@ +package com.retrip.crew.domain.entity; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +@Getter +@AllArgsConstructor +public enum CrewMemberRole { + LEADER("LEADER", "리더"), + PARTICIPANT("PARTICIPANT", "μ°Έκ°€μž"); + + private final String code; + private final String viewName; + + public static CrewMemberRole codeOf(String code) { + return Arrays.stream(CrewMemberRole.values()) + .filter(crewMemberRole -> crewMemberRole.getCode().equals(code)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” μ½”λ“œμž…λ‹ˆλ‹€.")); + } +} + diff --git a/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java b/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java new file mode 100644 index 0000000..90feba1 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java @@ -0,0 +1,32 @@ +package com.retrip.crew.domain.entity; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.OneToMany; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +@Embeddable +public class CrewMembers { + @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) + private List values = new ArrayList<>(); + + public CrewMembers(Crew crew, UUID member) { + this.values = createLeader(crew, member); + } + + private List createLeader(Crew crew, UUID memberId) { + return List.of(new CrewMember(crew, memberId, CrewMemberRole.LEADER)); + } + + public CrewMember getLeader() { + return this.values.stream().filter(it -> it.getCrewMemberRole() == CrewMemberRole.LEADER).findFirst().orElse(null); + } +} diff --git a/src/main/java/com/retrip/crew/domain/entity/Introduction.java b/src/main/java/com/retrip/crew/domain/entity/Introduction.java new file mode 100644 index 0000000..1753df1 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/entity/Introduction.java @@ -0,0 +1,34 @@ +package com.retrip.crew.domain.entity; + +import com.retrip.crew.domain.vo.IntroductionContent; +import com.retrip.crew.domain.vo.IntroductionTitle; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class Introduction extends BaseEntity { + @Id + @Column(columnDefinition = "varbinary(16)") + private UUID id; + + @Embedded + private IntroductionTitle title; + + @Embedded + private IntroductionContent content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn( + name = "crew_id", + nullable = false, + columnDefinition = "varbinary(16)", + foreignKey = @ForeignKey(name = "fk_self_introduce_board_to_crew") + ) + private Crew crew; +} diff --git a/src/main/java/com/retrip/crew/domain/entity/Introductions.java b/src/main/java/com/retrip/crew/domain/entity/Introductions.java new file mode 100644 index 0000000..6d269aa --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/entity/Introductions.java @@ -0,0 +1,24 @@ +package com.retrip.crew.domain.entity; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.OneToMany; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Embeddable +public class Introductions { + @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) + private List values = new ArrayList<>(); + + public Introductions() { + this.values = createEmptyValues(); + } + + private List createEmptyValues() { + return new ArrayList<>(); + } +} diff --git a/src/main/java/com/retrip/crew/domain/entity/Post.java b/src/main/java/com/retrip/crew/domain/entity/Post.java new file mode 100644 index 0000000..8bcf102 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/entity/Post.java @@ -0,0 +1,34 @@ +package com.retrip.crew.domain.entity; + +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; + +import java.util.UUID; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class Post extends BaseEntity { + @Id + @Column(columnDefinition = "varbinary(16)") + private UUID id; + + @Embedded + private PostTitle title; + + @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") + ) + private Crew crew; +} diff --git a/src/main/java/com/retrip/crew/domain/entity/Posts.java b/src/main/java/com/retrip/crew/domain/entity/Posts.java new file mode 100644 index 0000000..39b9b9b --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/entity/Posts.java @@ -0,0 +1,26 @@ +package com.retrip.crew.domain.entity; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.OneToMany; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Embeddable +public class Posts { + @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) + private List values = new ArrayList<>(); + + public Posts() { + this.values = createEmptyValues(); + } + + private List createEmptyValues() { + return new ArrayList<>(); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/AnnouncementContentException.java b/src/main/java/com/retrip/crew/domain/exception/AnnouncementContentException.java new file mode 100644 index 0000000..554d584 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/AnnouncementContentException.java @@ -0,0 +1,8 @@ +package com.retrip.crew.domain.exception; + +public class AnnouncementContentException extends CrewException { + + public AnnouncementContentException(String message) { + super(CrewErrorCode.ANNOUNCEMENT_CONTENT_ERROR, message); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/AnnouncementTitleException.java b/src/main/java/com/retrip/crew/domain/exception/AnnouncementTitleException.java new file mode 100644 index 0000000..571ae90 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/AnnouncementTitleException.java @@ -0,0 +1,8 @@ +package com.retrip.crew.domain.exception; + +public class AnnouncementTitleException extends CrewException { + + public AnnouncementTitleException(String message) { + super(CrewErrorCode.ANNOUNCEMENT_TITLE_ERROR, message); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/CrewDescriptionException.java b/src/main/java/com/retrip/crew/domain/exception/CrewDescriptionException.java new file mode 100644 index 0000000..04db44e --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/CrewDescriptionException.java @@ -0,0 +1,8 @@ +package com.retrip.crew.domain.exception; + +public class CrewDescriptionException extends CrewException { + + public CrewDescriptionException(String message) { + super(CrewErrorCode.CREW_DESCRIPTION_ERROR, message); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/CrewErrorCode.java b/src/main/java/com/retrip/crew/domain/exception/CrewErrorCode.java new file mode 100644 index 0000000..d494b90 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/CrewErrorCode.java @@ -0,0 +1,17 @@ +package com.retrip.crew.domain.exception; + +public enum CrewErrorCode { + CREW_TITLE_ERROR(1001), + CREW_DESCRIPTION_ERROR(1002), + POST_TITLE_ERROR(1003), + POST_CONTENT_ERROR(1004), + ANNOUNCEMENT_TITLE_ERROR(1005), + ANNOUNCEMENT_CONTENT_ERROR(1006), + INTRODUCTION_TITLE_ERROR(1007), + INTRODUCTION_CONTENT_ERROR(1008); + + private final int code; + CrewErrorCode(int code) { + this.code = code; + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/CrewException.java b/src/main/java/com/retrip/crew/domain/exception/CrewException.java new file mode 100644 index 0000000..6b851e9 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/CrewException.java @@ -0,0 +1,10 @@ +package com.retrip.crew.domain.exception; + +public abstract class CrewException extends RuntimeException { + private final CrewErrorCode errorCode; + + public CrewException(CrewErrorCode errorCode, String message) { + super(message); + this.errorCode = errorCode; + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/CrewTitleException.java b/src/main/java/com/retrip/crew/domain/exception/CrewTitleException.java new file mode 100644 index 0000000..ae1c0c1 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/CrewTitleException.java @@ -0,0 +1,7 @@ +package com.retrip.crew.domain.exception; + +public class CrewTitleException extends CrewException { + public CrewTitleException(String message) { + super(CrewErrorCode.CREW_TITLE_ERROR, message); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/IntroductionContentException.java b/src/main/java/com/retrip/crew/domain/exception/IntroductionContentException.java new file mode 100644 index 0000000..ab07f4b --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/IntroductionContentException.java @@ -0,0 +1,9 @@ +package com.retrip.crew.domain.exception; + +public class IntroductionContentException extends CrewException { + private static final int ERROR_CODE = 1003; + + public IntroductionContentException(String message) { + super(CrewErrorCode.INTRODUCTION_CONTENT_ERROR, message); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/IntroductionTitleException.java b/src/main/java/com/retrip/crew/domain/exception/IntroductionTitleException.java new file mode 100644 index 0000000..6d7af3e --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/IntroductionTitleException.java @@ -0,0 +1,9 @@ +package com.retrip.crew.domain.exception; + +public class IntroductionTitleException extends CrewException { + private static final int ERROR_CODE = 1003; + + public IntroductionTitleException(String message) { + super(CrewErrorCode.INTRODUCTION_TITLE_ERROR, message); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/PostContentException.java b/src/main/java/com/retrip/crew/domain/exception/PostContentException.java new file mode 100644 index 0000000..997f665 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/PostContentException.java @@ -0,0 +1,8 @@ +package com.retrip.crew.domain.exception; + +public class PostContentException extends CrewException { + + public PostContentException(String message) { + super(CrewErrorCode.POST_CONTENT_ERROR, message); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/PostTitleException.java b/src/main/java/com/retrip/crew/domain/exception/PostTitleException.java new file mode 100644 index 0000000..64afe6c --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/PostTitleException.java @@ -0,0 +1,8 @@ +package com.retrip.crew.domain.exception; + +public class PostTitleException extends CrewException { + + public PostTitleException(String message) { + super(CrewErrorCode.POST_TITLE_ERROR, message); + } +} diff --git a/src/main/java/com/retrip/crew/domain/vo/AnnouncementContent.java b/src/main/java/com/retrip/crew/domain/vo/AnnouncementContent.java new file mode 100644 index 0000000..e213e0d --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/vo/AnnouncementContent.java @@ -0,0 +1,31 @@ +package com.retrip.crew.domain.vo; + +import com.retrip.crew.domain.exception.CrewDescriptionException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@EqualsAndHashCode +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +public class AnnouncementContent { + private static final int CONTENT_LENGTH_LIMIT = 300; + + @Column(name = "content", nullable = false, length = CONTENT_LENGTH_LIMIT) + private final String value; + + public AnnouncementContent(String value) { + validate(value); + this.value = value; + } + + private void validate(String value) { + if (value.length() > CONTENT_LENGTH_LIMIT) { + throw new CrewDescriptionException("곡지 κ²Œμ‹œκΈ€ λ‚΄μš©μ€ " + CONTENT_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + } +} diff --git a/src/main/java/com/retrip/crew/domain/vo/AnnouncementTitle.java b/src/main/java/com/retrip/crew/domain/vo/AnnouncementTitle.java new file mode 100644 index 0000000..7aabaf1 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/vo/AnnouncementTitle.java @@ -0,0 +1,31 @@ +package com.retrip.crew.domain.vo; + +import com.retrip.crew.domain.exception.IntroductionTitleException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@EqualsAndHashCode +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +public class AnnouncementTitle { + private static final int TITLE_LENGTH_LIMIT = 20; + + @Column(name = "title", nullable = false, length = TITLE_LENGTH_LIMIT) + private final String value; + + public AnnouncementTitle(String value) { + validate(value); + this.value = value; + } + + private void validate(String value) { + if (value.length() > TITLE_LENGTH_LIMIT) { + throw new IntroductionTitleException("곡지 κ²Œμ‹œκΈ€ 제λͺ©μ€ " + TITLE_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + } +} diff --git a/src/main/java/com/retrip/crew/domain/vo/CrewDescription.java b/src/main/java/com/retrip/crew/domain/vo/CrewDescription.java new file mode 100644 index 0000000..76c0eb9 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/vo/CrewDescription.java @@ -0,0 +1,31 @@ +package com.retrip.crew.domain.vo; + +import com.retrip.crew.domain.exception.CrewDescriptionException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@EqualsAndHashCode +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +public class CrewDescription { + private static final int DESCRIPTION_LENGTH_LIMIT = 200; + + @Column(name = "description", nullable = false, length = DESCRIPTION_LENGTH_LIMIT) + private final String value; + + public CrewDescription(String value) { + validate(value); + this.value = value; + } + + private void validate(String value) { + if (value.length() > DESCRIPTION_LENGTH_LIMIT) { + throw new CrewDescriptionException("크루 상세 μ„€λͺ…은 " + DESCRIPTION_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + } +} diff --git a/src/main/java/com/retrip/crew/domain/vo/CrewTitle.java b/src/main/java/com/retrip/crew/domain/vo/CrewTitle.java new file mode 100644 index 0000000..50cdbac --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/vo/CrewTitle.java @@ -0,0 +1,31 @@ +package com.retrip.crew.domain.vo; + +import com.retrip.crew.domain.exception.CrewTitleException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@EqualsAndHashCode +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +public class CrewTitle { + private static final int TITLE_LENGTH_LIMIT = 30; + + @Column(name = "title", nullable = false, length = TITLE_LENGTH_LIMIT) + private final String value; + + public CrewTitle(String value) { + validate(value); + this.value = value; + } + + private void validate(String value) { + if (value.length() > TITLE_LENGTH_LIMIT) { + throw new CrewTitleException("크루 제λͺ©μ€ " + TITLE_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + } +} diff --git a/src/main/java/com/retrip/crew/domain/vo/IntroductionContent.java b/src/main/java/com/retrip/crew/domain/vo/IntroductionContent.java new file mode 100644 index 0000000..6001175 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/vo/IntroductionContent.java @@ -0,0 +1,31 @@ +package com.retrip.crew.domain.vo; + +import com.retrip.crew.domain.exception.CrewDescriptionException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@EqualsAndHashCode +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +public class IntroductionContent { + private static final int CONTENT_LENGTH_LIMIT = 500; + + @Column(name = "content", nullable = false, length = CONTENT_LENGTH_LIMIT) + private final String value; + + public IntroductionContent(String value) { + validate(value); + this.value = value; + } + + private void validate(String value) { + if (value.length() > CONTENT_LENGTH_LIMIT) { + throw new CrewDescriptionException("자기 μ†Œκ°œ κ²Œμ‹œκΈ€ λ‚΄μš©μ€ " + CONTENT_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + } +} diff --git a/src/main/java/com/retrip/crew/domain/vo/IntroductionTitle.java b/src/main/java/com/retrip/crew/domain/vo/IntroductionTitle.java new file mode 100644 index 0000000..7668e76 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/vo/IntroductionTitle.java @@ -0,0 +1,32 @@ +package com.retrip.crew.domain.vo; + +import com.retrip.crew.domain.exception.CrewTitleException; +import com.retrip.crew.domain.exception.IntroductionTitleException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@EqualsAndHashCode +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +public class IntroductionTitle { + private static final int TITLE_LENGTH_LIMIT = 20; + + @Column(name = "title", nullable = false, length = TITLE_LENGTH_LIMIT) + private final String value; + + public IntroductionTitle(String value) { + validate(value); + this.value = value; + } + + private void validate(String value) { + if (value.length() > TITLE_LENGTH_LIMIT) { + throw new IntroductionTitleException("자기 μ†Œκ°œ κ²Œμ‹œκΈ€ 제λͺ©μ€ " + TITLE_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + } +} diff --git a/src/main/java/com/retrip/crew/domain/vo/NotificationBoardContent.java b/src/main/java/com/retrip/crew/domain/vo/NotificationBoardContent.java new file mode 100644 index 0000000..eeb7017 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/vo/NotificationBoardContent.java @@ -0,0 +1,38 @@ +package com.retrip.crew.domain.vo; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@EqualsAndHashCode +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +public class NotificationBoardContent { + private static final int TITLE_LENGTH_LIMIT = 30; + private static final int CONTENT_LENGTH_LIMIT = 100; + + @Column(name = "title", nullable = false, length = TITLE_LENGTH_LIMIT) + private final String title; + + @Column(name = "content", nullable = false, length = CONTENT_LENGTH_LIMIT) + private final String content; + + public NotificationBoardContent(String title, String content) { + validate(title, content); + this.title = title; + this.content = content; + } + + private void validate(String title, String content) { + if (title.length() > TITLE_LENGTH_LIMIT) { + throw new IllegalArgumentException("곡지 κ²Œμ‹œκΈ€ 제λͺ©μ€ " + TITLE_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + if (content.length() > CONTENT_LENGTH_LIMIT) { + throw new IllegalArgumentException("곡지 κ²Œμ‹œκΈ€ λ‚΄μš©μ€ " + CONTENT_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + } +} diff --git a/src/main/java/com/retrip/crew/domain/vo/PostContent.java b/src/main/java/com/retrip/crew/domain/vo/PostContent.java new file mode 100644 index 0000000..65dcb15 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/vo/PostContent.java @@ -0,0 +1,31 @@ +package com.retrip.crew.domain.vo; + +import com.retrip.crew.domain.exception.CrewDescriptionException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@EqualsAndHashCode +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +public class PostContent { + private static final int CONTENT_LENGTH_LIMIT = 500; + + @Column(name = "content", nullable = false, length = CONTENT_LENGTH_LIMIT) + private final String value; + + public PostContent(String value) { + validate(value); + this.value = value; + } + + private void validate(String value) { + if (value.length() > CONTENT_LENGTH_LIMIT) { + throw new CrewDescriptionException("자유 κ²Œμ‹œνŒ λ‚΄μš©μ€ " + CONTENT_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + } +} diff --git a/src/main/java/com/retrip/crew/domain/vo/PostTitle.java b/src/main/java/com/retrip/crew/domain/vo/PostTitle.java new file mode 100644 index 0000000..5d2f757 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/vo/PostTitle.java @@ -0,0 +1,31 @@ +package com.retrip.crew.domain.vo; + +import com.retrip.crew.domain.exception.IntroductionTitleException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@EqualsAndHashCode +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +public class PostTitle { + private static final int TITLE_LENGTH_LIMIT = 20; + + @Column(name = "title", nullable = false, length = TITLE_LENGTH_LIMIT) + private final String value; + + public PostTitle(String value) { + validate(value); + this.value = value; + } + + private void validate(String value) { + if (value.length() > TITLE_LENGTH_LIMIT) { + throw new IntroductionTitleException("자유 κ²Œμ‹œνŒ 제λͺ©μ€ " + TITLE_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + } +} diff --git a/src/test/java/com/retrip/crew/CrewApplicationTests.java b/src/test/java/com/retrip/crew/CrewApplicationTests.java index 76c2982..405f037 100644 --- a/src/test/java/com/retrip/crew/CrewApplicationTests.java +++ b/src/test/java/com/retrip/crew/CrewApplicationTests.java @@ -3,9 +3,8 @@ import com.retrip.crew.domain.entity.Crew; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import java.time.LocalDate; +import java.util.UUID; import static org.assertj.core.api.Assertions.assertThatCode; @@ -13,7 +12,11 @@ class CrewApplicationTests { @DisplayName("제λͺ©μ„ μž…λ ₯ν•΄ 크루λ₯Ό 생성할 수 μžˆλ‹€.") @Test void create() { - assertThatCode(() -> Crew.create("μ†μ΄ˆ 크루 λͺ¨μ§‘")).doesNotThrowAnyException(); + assertThatCode(() -> Crew.create( + "인천 크루 λͺ¨μ§‘", + "인천 크루원을 λͺ¨μ§‘ν•©λ‹ˆλ‹€.\n κ°€μž…λ‚˜μ΄: 20~29μ‚΄ \n κΈˆμ§€ 사항\n - μ—°μ•  κΈˆμ§€\n - λ§Œλ‚¨ 당일 μ·¨μ†Œ κΈˆμ§€\n - 사적 연락 κΈˆμ§€", + UUID.randomUUID() + )).doesNotThrowAnyException(); } } diff --git a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java new file mode 100644 index 0000000..df9bf29 --- /dev/null +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -0,0 +1,44 @@ +package com.retrip.crew.application.in; + +import com.retrip.crew.application.in.request.CrewCreateRequest; +import com.retrip.crew.application.in.response.CrewCreateResponse; +import com.retrip.crew.application.out.repository.CrewRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +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 java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class CrewServiceTest { + @Autowired + CrewRepository crewRepository; + + CrewService crewService; + UUID memberId = UUID.randomUUID(); + + @BeforeEach + void setUp() { + crewService = new CrewService(crewRepository); + } + + @DisplayName("크루λ₯Ό 생성 ν•œλ‹€.") + @Test + void createCrew() { + CrewCreateRequest request = new CrewCreateRequest( + memberId, + "μ†μ΄ˆ 크루원 ꡬ함", + "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€." + ); + CrewCreateResponse response = crewService.createCrew(request); + + assertThat(response.id()).isNotNull(); + assertThat(response.leaderId()).isNotNull(); + } +} diff --git a/src/test/java/com/retrip/crew/domain/entity/CrewTest.java b/src/test/java/com/retrip/crew/domain/entity/CrewTest.java index eb2edcf..2c89485 100644 --- a/src/test/java/com/retrip/crew/domain/entity/CrewTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/CrewTest.java @@ -1,7 +1,20 @@ package com.retrip.crew.domain.entity; -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; class CrewTest { + @DisplayName("크루 생성 ν…ŒμŠ€νŠΈ") + @Test + public void create() { + assertThatCode(() -> Crew.create( + "μ†μ΄ˆ 크루원 ꡬ함", + "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", + UUID.randomUUID())).doesNotThrowAnyException(); + } } diff --git a/src/test/java/com/retrip/crew/domain/vo/CrewIntroductionTitleTest.java b/src/test/java/com/retrip/crew/domain/vo/CrewIntroductionTitleTest.java new file mode 100644 index 0000000..694e249 --- /dev/null +++ b/src/test/java/com/retrip/crew/domain/vo/CrewIntroductionTitleTest.java @@ -0,0 +1,16 @@ +package com.retrip.crew.domain.vo; + +import com.retrip.crew.domain.exception.CrewTitleException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +class CrewIntroductionTitleTest { + + @Test + @DisplayName("크루 이름은 30자 미만으둜 μ„€μ •ν•΄μ•Όν•œλ‹€.") + public void validateName() { + assertThatThrownBy(() -> new CrewTitle("ν†΅μ²œ, νšŒμ–‘, 평강, 이천, κΉ€ν™”, 철원, 양ꡬ, 인제, κ³ μ„±, 강릉, μ†μ΄ˆ λ“± 크루 인원 λͺ¨μ§‘ν•©λ‹ˆλ‹€.")).isExactlyInstanceOf(CrewTitleException.class); + } +} From 51b9e33e5225182b0562fc2e4f67a06692ea4303 Mon Sep 17 00:00:00 2001 From: mandykr <94955454+mandykr@users.noreply.github.com> Date: Thu, 27 Feb 2025 10:37:20 +0900 Subject: [PATCH 03/11] =?UTF-8?q?feat:=20=EB=B9=84=EC=A6=88=EB=8B=88?= =?UTF-8?q?=EC=8A=A4=20=EC=98=88=EC=99=B8=EC=99=80=20API=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=9D=91=EB=8B=B5=20=EB=AA=A8=EB=93=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: λΉ„μ¦ˆλ‹ˆμŠ€ μ˜ˆμ™Έμ™€ API 곡톡 응닡 λͺ¨λ“ˆ μΆ”κ°€ - vo κ²€μ¦μ—μ„œ λ°œμƒν•˜λŠ” λΉ„μ¦ˆλ‹ˆμŠ€ μ˜ˆμ™Έ μ‚­μ œ 및 InvalidValueException 으둜 λ³€κ²½ --- .../crew/domain/entity/Announcement.java | 1 - .../AnnouncementContentException.java | 8 --- .../exception/AnnouncementTitleException.java | 8 --- .../exception/CrewDescriptionException.java | 8 --- .../crew/domain/exception/CrewErrorCode.java | 17 ------ .../crew/domain/exception/CrewException.java | 10 ---- .../exception/CrewNotFoundException.java | 12 +++++ .../domain/exception/CrewTitleException.java | 7 --- .../IntroductionContentException.java | 9 ---- .../exception/IntroductionTitleException.java | 9 ---- .../exception/PostContentException.java | 8 --- .../domain/exception/PostTitleException.java | 8 --- .../exception/common/BusinessException.java | 18 +++++++ .../common/EntityNotFoundException.java | 16 ++++++ .../domain/exception/common/ErrorCode.java | 27 ++++++++++ .../common/InvalidValueException.java | 17 ++++++ .../crew/domain/vo/AnnouncementContent.java | 4 +- .../crew/domain/vo/AnnouncementTitle.java | 4 +- .../crew/domain/vo/CrewDescription.java | 4 +- .../com/retrip/crew/domain/vo/CrewTitle.java | 4 +- .../crew/domain/vo/IntroductionContent.java | 4 +- .../crew/domain/vo/IntroductionTitle.java | 5 +- .../domain/vo/NotificationBoardContent.java | 38 ------------- .../retrip/crew/domain/vo/PostContent.java | 4 +- .../com/retrip/crew/domain/vo/PostTitle.java | 4 +- .../in/presentation/rest/CrewController.java | 13 +++-- .../presentation/rest/common/ApiResponse.java | 37 +++++++++++++ .../rest/common/ErrorResponse.java | 37 +++++++++++++ .../rest/common/GlobalExceptionHandler.java | 54 +++++++++++++++++++ .../domain/vo/CrewIntroductionTitleTest.java | 5 +- 30 files changed, 246 insertions(+), 154 deletions(-) delete mode 100644 src/main/java/com/retrip/crew/domain/exception/AnnouncementContentException.java delete mode 100644 src/main/java/com/retrip/crew/domain/exception/AnnouncementTitleException.java delete mode 100644 src/main/java/com/retrip/crew/domain/exception/CrewDescriptionException.java delete mode 100644 src/main/java/com/retrip/crew/domain/exception/CrewErrorCode.java delete mode 100644 src/main/java/com/retrip/crew/domain/exception/CrewException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/CrewNotFoundException.java delete mode 100644 src/main/java/com/retrip/crew/domain/exception/CrewTitleException.java delete mode 100644 src/main/java/com/retrip/crew/domain/exception/IntroductionContentException.java delete mode 100644 src/main/java/com/retrip/crew/domain/exception/IntroductionTitleException.java delete mode 100644 src/main/java/com/retrip/crew/domain/exception/PostContentException.java delete mode 100644 src/main/java/com/retrip/crew/domain/exception/PostTitleException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/common/BusinessException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/common/EntityNotFoundException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/common/ErrorCode.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/common/InvalidValueException.java delete mode 100644 src/main/java/com/retrip/crew/domain/vo/NotificationBoardContent.java create mode 100644 src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ApiResponse.java create mode 100644 src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ErrorResponse.java create mode 100644 src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/GlobalExceptionHandler.java diff --git a/src/main/java/com/retrip/crew/domain/entity/Announcement.java b/src/main/java/com/retrip/crew/domain/entity/Announcement.java index ca4def9..694a274 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Announcement.java +++ b/src/main/java/com/retrip/crew/domain/entity/Announcement.java @@ -2,7 +2,6 @@ import com.retrip.crew.domain.vo.AnnouncementContent; import com.retrip.crew.domain.vo.AnnouncementTitle; -import com.retrip.crew.domain.vo.NotificationBoardContent; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; diff --git a/src/main/java/com/retrip/crew/domain/exception/AnnouncementContentException.java b/src/main/java/com/retrip/crew/domain/exception/AnnouncementContentException.java deleted file mode 100644 index 554d584..0000000 --- a/src/main/java/com/retrip/crew/domain/exception/AnnouncementContentException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.retrip.crew.domain.exception; - -public class AnnouncementContentException extends CrewException { - - public AnnouncementContentException(String message) { - super(CrewErrorCode.ANNOUNCEMENT_CONTENT_ERROR, message); - } -} diff --git a/src/main/java/com/retrip/crew/domain/exception/AnnouncementTitleException.java b/src/main/java/com/retrip/crew/domain/exception/AnnouncementTitleException.java deleted file mode 100644 index 571ae90..0000000 --- a/src/main/java/com/retrip/crew/domain/exception/AnnouncementTitleException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.retrip.crew.domain.exception; - -public class AnnouncementTitleException extends CrewException { - - public AnnouncementTitleException(String message) { - super(CrewErrorCode.ANNOUNCEMENT_TITLE_ERROR, message); - } -} diff --git a/src/main/java/com/retrip/crew/domain/exception/CrewDescriptionException.java b/src/main/java/com/retrip/crew/domain/exception/CrewDescriptionException.java deleted file mode 100644 index 04db44e..0000000 --- a/src/main/java/com/retrip/crew/domain/exception/CrewDescriptionException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.retrip.crew.domain.exception; - -public class CrewDescriptionException extends CrewException { - - public CrewDescriptionException(String message) { - super(CrewErrorCode.CREW_DESCRIPTION_ERROR, message); - } -} diff --git a/src/main/java/com/retrip/crew/domain/exception/CrewErrorCode.java b/src/main/java/com/retrip/crew/domain/exception/CrewErrorCode.java deleted file mode 100644 index d494b90..0000000 --- a/src/main/java/com/retrip/crew/domain/exception/CrewErrorCode.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.retrip.crew.domain.exception; - -public enum CrewErrorCode { - CREW_TITLE_ERROR(1001), - CREW_DESCRIPTION_ERROR(1002), - POST_TITLE_ERROR(1003), - POST_CONTENT_ERROR(1004), - ANNOUNCEMENT_TITLE_ERROR(1005), - ANNOUNCEMENT_CONTENT_ERROR(1006), - INTRODUCTION_TITLE_ERROR(1007), - INTRODUCTION_CONTENT_ERROR(1008); - - private final int code; - CrewErrorCode(int code) { - this.code = code; - } -} diff --git a/src/main/java/com/retrip/crew/domain/exception/CrewException.java b/src/main/java/com/retrip/crew/domain/exception/CrewException.java deleted file mode 100644 index 6b851e9..0000000 --- a/src/main/java/com/retrip/crew/domain/exception/CrewException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.retrip.crew.domain.exception; - -public abstract class CrewException extends RuntimeException { - private final CrewErrorCode errorCode; - - public CrewException(CrewErrorCode errorCode, String message) { - super(message); - this.errorCode = errorCode; - } -} diff --git a/src/main/java/com/retrip/crew/domain/exception/CrewNotFoundException.java b/src/main/java/com/retrip/crew/domain/exception/CrewNotFoundException.java new file mode 100644 index 0000000..002f158 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/CrewNotFoundException.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 CrewNotFoundException extends EntityNotFoundException { + private static final ErrorCode errorCode = ErrorCode.CREW_NOT_FOUND; + + public CrewNotFoundException() { + super(errorCode); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/CrewTitleException.java b/src/main/java/com/retrip/crew/domain/exception/CrewTitleException.java deleted file mode 100644 index ae1c0c1..0000000 --- a/src/main/java/com/retrip/crew/domain/exception/CrewTitleException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.retrip.crew.domain.exception; - -public class CrewTitleException extends CrewException { - public CrewTitleException(String message) { - super(CrewErrorCode.CREW_TITLE_ERROR, message); - } -} diff --git a/src/main/java/com/retrip/crew/domain/exception/IntroductionContentException.java b/src/main/java/com/retrip/crew/domain/exception/IntroductionContentException.java deleted file mode 100644 index ab07f4b..0000000 --- a/src/main/java/com/retrip/crew/domain/exception/IntroductionContentException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.retrip.crew.domain.exception; - -public class IntroductionContentException extends CrewException { - private static final int ERROR_CODE = 1003; - - public IntroductionContentException(String message) { - super(CrewErrorCode.INTRODUCTION_CONTENT_ERROR, message); - } -} diff --git a/src/main/java/com/retrip/crew/domain/exception/IntroductionTitleException.java b/src/main/java/com/retrip/crew/domain/exception/IntroductionTitleException.java deleted file mode 100644 index 6d7af3e..0000000 --- a/src/main/java/com/retrip/crew/domain/exception/IntroductionTitleException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.retrip.crew.domain.exception; - -public class IntroductionTitleException extends CrewException { - private static final int ERROR_CODE = 1003; - - public IntroductionTitleException(String message) { - super(CrewErrorCode.INTRODUCTION_TITLE_ERROR, message); - } -} diff --git a/src/main/java/com/retrip/crew/domain/exception/PostContentException.java b/src/main/java/com/retrip/crew/domain/exception/PostContentException.java deleted file mode 100644 index 997f665..0000000 --- a/src/main/java/com/retrip/crew/domain/exception/PostContentException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.retrip.crew.domain.exception; - -public class PostContentException extends CrewException { - - public PostContentException(String message) { - super(CrewErrorCode.POST_CONTENT_ERROR, message); - } -} diff --git a/src/main/java/com/retrip/crew/domain/exception/PostTitleException.java b/src/main/java/com/retrip/crew/domain/exception/PostTitleException.java deleted file mode 100644 index 64afe6c..0000000 --- a/src/main/java/com/retrip/crew/domain/exception/PostTitleException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.retrip.crew.domain.exception; - -public class PostTitleException extends CrewException { - - public PostTitleException(String message) { - super(CrewErrorCode.POST_TITLE_ERROR, message); - } -} diff --git a/src/main/java/com/retrip/crew/domain/exception/common/BusinessException.java b/src/main/java/com/retrip/crew/domain/exception/common/BusinessException.java new file mode 100644 index 0000000..ea05e90 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/common/BusinessException.java @@ -0,0 +1,18 @@ +package com.retrip.crew.domain.exception.common; + +import lombok.Getter; + +@Getter +public class BusinessException extends RuntimeException { + private final ErrorCode errorCode; + + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } + + public BusinessException(ErrorCode errorCode, String message) { + super(message); + this.errorCode = errorCode; + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/common/EntityNotFoundException.java b/src/main/java/com/retrip/crew/domain/exception/common/EntityNotFoundException.java new file mode 100644 index 0000000..2c6d127 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/common/EntityNotFoundException.java @@ -0,0 +1,16 @@ +package com.retrip.crew.domain.exception.common; + +public class EntityNotFoundException extends BusinessException { + private static final ErrorCode errorCode = ErrorCode.ENTITY_NOT_FOUND; + public EntityNotFoundException(ErrorCode errorCode) { + super(errorCode); + } + + public EntityNotFoundException(String message) { + super(errorCode, message); + } + + public EntityNotFoundException(ErrorCode errorCode, String message) { + super(errorCode, message); + } +} 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 new file mode 100644 index 0000000..520a72c --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/common/ErrorCode.java @@ -0,0 +1,27 @@ +package com.retrip.crew.domain.exception.common; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.*; + +@Getter +public enum ErrorCode { + SERVER_ERROR(INTERNAL_SERVER_ERROR, "Common-001", "Server error"), + INVALID_INPUT_VALUE(BAD_REQUEST, "Common-002", "Invalid input value"), + HANDLE_ACCESS_DENIED(FORBIDDEN, "Common-003", "Access is denied"), + ENTITY_NOT_FOUND(BAD_REQUEST, "Common-004", "Entity not found"), + + CREW_NOT_FOUND(BAD_REQUEST, "Crew-001", "크루 μ—”ν‹°ν‹°λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.") + ; + + private final HttpStatus status; + private final String code; + private final String message; + + ErrorCode(HttpStatus status, String code, String message) { + this.status = status; + this.code = code; + this.message = message; + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/common/InvalidValueException.java b/src/main/java/com/retrip/crew/domain/exception/common/InvalidValueException.java new file mode 100644 index 0000000..546de82 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/common/InvalidValueException.java @@ -0,0 +1,17 @@ +package com.retrip.crew.domain.exception.common; + +public class InvalidValueException extends BusinessException { + private static final ErrorCode errorCode = ErrorCode.INVALID_INPUT_VALUE; + public InvalidValueException(ErrorCode errorCode) { + super(errorCode); + } + + public InvalidValueException(String message) { + super(errorCode, message); + } + + public InvalidValueException(ErrorCode errorCode, String message) { + super(errorCode, message); + } + +} diff --git a/src/main/java/com/retrip/crew/domain/vo/AnnouncementContent.java b/src/main/java/com/retrip/crew/domain/vo/AnnouncementContent.java index e213e0d..5d67c0f 100644 --- a/src/main/java/com/retrip/crew/domain/vo/AnnouncementContent.java +++ b/src/main/java/com/retrip/crew/domain/vo/AnnouncementContent.java @@ -1,6 +1,6 @@ package com.retrip.crew.domain.vo; -import com.retrip.crew.domain.exception.CrewDescriptionException; +import com.retrip.crew.domain.exception.common.InvalidValueException; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.AccessLevel; @@ -25,7 +25,7 @@ public AnnouncementContent(String value) { private void validate(String value) { if (value.length() > CONTENT_LENGTH_LIMIT) { - throw new CrewDescriptionException("곡지 κ²Œμ‹œκΈ€ λ‚΄μš©μ€ " + CONTENT_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + throw new InvalidValueException("곡지 κ²Œμ‹œκΈ€ λ‚΄μš©μ€ " + CONTENT_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); } } } diff --git a/src/main/java/com/retrip/crew/domain/vo/AnnouncementTitle.java b/src/main/java/com/retrip/crew/domain/vo/AnnouncementTitle.java index 7aabaf1..db5aa3e 100644 --- a/src/main/java/com/retrip/crew/domain/vo/AnnouncementTitle.java +++ b/src/main/java/com/retrip/crew/domain/vo/AnnouncementTitle.java @@ -1,6 +1,6 @@ package com.retrip.crew.domain.vo; -import com.retrip.crew.domain.exception.IntroductionTitleException; +import com.retrip.crew.domain.exception.common.InvalidValueException; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.AccessLevel; @@ -25,7 +25,7 @@ public AnnouncementTitle(String value) { private void validate(String value) { if (value.length() > TITLE_LENGTH_LIMIT) { - throw new IntroductionTitleException("곡지 κ²Œμ‹œκΈ€ 제λͺ©μ€ " + TITLE_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + throw new InvalidValueException("곡지 κ²Œμ‹œκΈ€ 제λͺ©μ€ " + TITLE_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); } } } diff --git a/src/main/java/com/retrip/crew/domain/vo/CrewDescription.java b/src/main/java/com/retrip/crew/domain/vo/CrewDescription.java index 76c0eb9..e6789b5 100644 --- a/src/main/java/com/retrip/crew/domain/vo/CrewDescription.java +++ b/src/main/java/com/retrip/crew/domain/vo/CrewDescription.java @@ -1,6 +1,6 @@ package com.retrip.crew.domain.vo; -import com.retrip.crew.domain.exception.CrewDescriptionException; +import com.retrip.crew.domain.exception.common.InvalidValueException; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.AccessLevel; @@ -25,7 +25,7 @@ public CrewDescription(String value) { private void validate(String value) { if (value.length() > DESCRIPTION_LENGTH_LIMIT) { - throw new CrewDescriptionException("크루 상세 μ„€λͺ…은 " + DESCRIPTION_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + throw new InvalidValueException("크루 상세 μ„€λͺ…은 " + DESCRIPTION_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); } } } diff --git a/src/main/java/com/retrip/crew/domain/vo/CrewTitle.java b/src/main/java/com/retrip/crew/domain/vo/CrewTitle.java index 50cdbac..149ff6d 100644 --- a/src/main/java/com/retrip/crew/domain/vo/CrewTitle.java +++ b/src/main/java/com/retrip/crew/domain/vo/CrewTitle.java @@ -1,6 +1,6 @@ package com.retrip.crew.domain.vo; -import com.retrip.crew.domain.exception.CrewTitleException; +import com.retrip.crew.domain.exception.common.InvalidValueException; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.AccessLevel; @@ -25,7 +25,7 @@ public CrewTitle(String value) { private void validate(String value) { if (value.length() > TITLE_LENGTH_LIMIT) { - throw new CrewTitleException("크루 제λͺ©μ€ " + TITLE_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + throw new InvalidValueException("크루 제λͺ©μ€ " + TITLE_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); } } } diff --git a/src/main/java/com/retrip/crew/domain/vo/IntroductionContent.java b/src/main/java/com/retrip/crew/domain/vo/IntroductionContent.java index 6001175..0ffd8b3 100644 --- a/src/main/java/com/retrip/crew/domain/vo/IntroductionContent.java +++ b/src/main/java/com/retrip/crew/domain/vo/IntroductionContent.java @@ -1,6 +1,6 @@ package com.retrip.crew.domain.vo; -import com.retrip.crew.domain.exception.CrewDescriptionException; +import com.retrip.crew.domain.exception.common.InvalidValueException; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.AccessLevel; @@ -25,7 +25,7 @@ public IntroductionContent(String value) { private void validate(String value) { if (value.length() > CONTENT_LENGTH_LIMIT) { - throw new CrewDescriptionException("자기 μ†Œκ°œ κ²Œμ‹œκΈ€ λ‚΄μš©μ€ " + CONTENT_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + throw new InvalidValueException("자기 μ†Œκ°œ κ²Œμ‹œκΈ€ λ‚΄μš©μ€ " + CONTENT_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); } } } diff --git a/src/main/java/com/retrip/crew/domain/vo/IntroductionTitle.java b/src/main/java/com/retrip/crew/domain/vo/IntroductionTitle.java index 7668e76..5b06b18 100644 --- a/src/main/java/com/retrip/crew/domain/vo/IntroductionTitle.java +++ b/src/main/java/com/retrip/crew/domain/vo/IntroductionTitle.java @@ -1,7 +1,6 @@ package com.retrip.crew.domain.vo; -import com.retrip.crew.domain.exception.CrewTitleException; -import com.retrip.crew.domain.exception.IntroductionTitleException; +import com.retrip.crew.domain.exception.common.InvalidValueException; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.AccessLevel; @@ -26,7 +25,7 @@ public IntroductionTitle(String value) { private void validate(String value) { if (value.length() > TITLE_LENGTH_LIMIT) { - throw new IntroductionTitleException("자기 μ†Œκ°œ κ²Œμ‹œκΈ€ 제λͺ©μ€ " + TITLE_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + throw new InvalidValueException("자기 μ†Œκ°œ κ²Œμ‹œκΈ€ 제λͺ©μ€ " + TITLE_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); } } } diff --git a/src/main/java/com/retrip/crew/domain/vo/NotificationBoardContent.java b/src/main/java/com/retrip/crew/domain/vo/NotificationBoardContent.java deleted file mode 100644 index eeb7017..0000000 --- a/src/main/java/com/retrip/crew/domain/vo/NotificationBoardContent.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.retrip.crew.domain.vo; - -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import lombok.AccessLevel; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@Embeddable -@EqualsAndHashCode -@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) -public class NotificationBoardContent { - private static final int TITLE_LENGTH_LIMIT = 30; - private static final int CONTENT_LENGTH_LIMIT = 100; - - @Column(name = "title", nullable = false, length = TITLE_LENGTH_LIMIT) - private final String title; - - @Column(name = "content", nullable = false, length = CONTENT_LENGTH_LIMIT) - private final String content; - - public NotificationBoardContent(String title, String content) { - validate(title, content); - this.title = title; - this.content = content; - } - - private void validate(String title, String content) { - if (title.length() > TITLE_LENGTH_LIMIT) { - throw new IllegalArgumentException("곡지 κ²Œμ‹œκΈ€ 제λͺ©μ€ " + TITLE_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); - } - if (content.length() > CONTENT_LENGTH_LIMIT) { - throw new IllegalArgumentException("곡지 κ²Œμ‹œκΈ€ λ‚΄μš©μ€ " + CONTENT_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); - } - } -} 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 65dcb15..bc57e38 100644 --- a/src/main/java/com/retrip/crew/domain/vo/PostContent.java +++ b/src/main/java/com/retrip/crew/domain/vo/PostContent.java @@ -1,6 +1,6 @@ package com.retrip.crew.domain.vo; -import com.retrip.crew.domain.exception.CrewDescriptionException; +import com.retrip.crew.domain.exception.common.InvalidValueException; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.AccessLevel; @@ -25,7 +25,7 @@ public PostContent(String value) { private void validate(String value) { if (value.length() > CONTENT_LENGTH_LIMIT) { - throw new CrewDescriptionException("자유 κ²Œμ‹œνŒ λ‚΄μš©μ€ " + CONTENT_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + throw new InvalidValueException("자유 κ²Œμ‹œνŒ λ‚΄μš©μ€ " + CONTENT_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); } } } diff --git a/src/main/java/com/retrip/crew/domain/vo/PostTitle.java b/src/main/java/com/retrip/crew/domain/vo/PostTitle.java index 5d2f757..7c9d4f6 100644 --- a/src/main/java/com/retrip/crew/domain/vo/PostTitle.java +++ b/src/main/java/com/retrip/crew/domain/vo/PostTitle.java @@ -1,6 +1,6 @@ package com.retrip.crew.domain.vo; -import com.retrip.crew.domain.exception.IntroductionTitleException; +import com.retrip.crew.domain.exception.common.InvalidValueException; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.AccessLevel; @@ -25,7 +25,7 @@ public PostTitle(String value) { private void validate(String value) { if (value.length() > TITLE_LENGTH_LIMIT) { - throw new IntroductionTitleException("자유 κ²Œμ‹œνŒ 제λͺ©μ€ " + TITLE_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + throw new InvalidValueException("자유 κ²Œμ‹œνŒ 제λͺ©μ€ " + TITLE_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); } } } diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java index 30171a9..94d47bd 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java @@ -3,12 +3,17 @@ import com.retrip.crew.application.in.request.CrewCreateRequest; import com.retrip.crew.application.in.response.CrewCreateResponse; import com.retrip.crew.application.in.usecase.CreateCrewUseCase; +import com.retrip.crew.domain.exception.CrewNotFoundException; +import com.retrip.crew.domain.exception.common.ErrorCode; +import com.retrip.crew.domain.exception.common.InvalidValueException; +import com.retrip.crew.infra.adapter.in.presentation.rest.common.ApiResponse; +import jakarta.validation.Valid; 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.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.net.URI; diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ApiResponse.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ApiResponse.java new file mode 100644 index 0000000..b35985d --- /dev/null +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ApiResponse.java @@ -0,0 +1,37 @@ +package com.retrip.crew.infra.adapter.in.presentation.rest.common; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.*; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ApiResponse { + private boolean success; + private int status; + private String message; + private T data; + + public static ApiResponse created(T data) { + return success(data, CREATED); + } + + public static ApiResponse ok(T data) { + return success(data, OK); + } + + public static ApiResponse of(T data, HttpStatus status) { + return success(data, status); + } + + private static ApiResponse success(T data, HttpStatus status) { + return new ApiResponse<>(true, status.value(), status.getReasonPhrase(), data); + } + + public static ApiResponse of(ErrorResponse errorResponse) { + return new ApiResponse<>(false, errorResponse.getStatus(), valueOf(errorResponse.getStatus()).getReasonPhrase(), errorResponse); + } +} diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ErrorResponse.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ErrorResponse.java new file mode 100644 index 0000000..d602a22 --- /dev/null +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ErrorResponse.java @@ -0,0 +1,37 @@ +package com.retrip.crew.infra.adapter.in.presentation.rest.common; + +import com.retrip.crew.domain.exception.common.ErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@AllArgsConstructor +public class ErrorResponse { + private int status; + private String code; + private String message; + private String url = "Not available"; + private String method; + private List errors = new ArrayList<>(); + + private ErrorResponse(int status, String code, String message, String url, String method) { + this.status = status; + this.code = code; + this.message = message; + this.url = url; + this.method = method; + } + + public static ErrorResponse of(ErrorCode code, String url, String method, BindingResult bindingResult) { + return new ErrorResponse(code.getStatus().value(), code.getCode(), code.getMessage(), url, method, bindingResult.getFieldErrors()); + } + + public static ErrorResponse of(ErrorCode code, String url, String method) { + return new ErrorResponse(code.getStatus().value(), code.getCode(), code.getMessage(), url, method); + } +} diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/GlobalExceptionHandler.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/GlobalExceptionHandler.java new file mode 100644 index 0000000..bbd83cd --- /dev/null +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/GlobalExceptionHandler.java @@ -0,0 +1,54 @@ +package com.retrip.crew.infra.adapter.in.presentation.rest.common; + +import com.retrip.crew.domain.exception.common.BusinessException; +import com.retrip.crew.domain.exception.common.ErrorCode; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.BindException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.nio.file.AccessDeniedException; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(AccessDeniedException.class) + public ApiResponse handleAccessDeniedException(HttpServletRequest request, AccessDeniedException e) { + log.error("handleAccessDeniedException: ", e); + return handle(ErrorCode.HANDLE_ACCESS_DENIED, request); + } + + @ExceptionHandler(BindException.class) + public ApiResponse handleBindException(HttpServletRequest request, BindException e) { + log.error("handleBindException: ", e); + return ApiResponse.of( + ErrorResponse.of( + ErrorCode.INVALID_INPUT_VALUE, + request.getRequestURL().toString(), + request.getMethod(), + e.getBindingResult() + )); + } + + @ExceptionHandler(BusinessException.class) + public ApiResponse handleBusinessException(HttpServletRequest request, BusinessException e) { + log.error("handleBusinessException: ", e); + return handle(e.getErrorCode(), request); + } + + + @ExceptionHandler(Exception.class) + public ApiResponse handleException(HttpServletRequest request, Exception e) { + log.error("handleException: ", e); + return handle(ErrorCode.SERVER_ERROR, request); + } + + private static ApiResponse handle(ErrorCode errorCode, HttpServletRequest request) { + return ApiResponse.of( + ErrorResponse.of( + errorCode, request.getRequestURL().toString(), request.getMethod() + )); + } +} diff --git a/src/test/java/com/retrip/crew/domain/vo/CrewIntroductionTitleTest.java b/src/test/java/com/retrip/crew/domain/vo/CrewIntroductionTitleTest.java index 694e249..657c233 100644 --- a/src/test/java/com/retrip/crew/domain/vo/CrewIntroductionTitleTest.java +++ b/src/test/java/com/retrip/crew/domain/vo/CrewIntroductionTitleTest.java @@ -1,6 +1,6 @@ package com.retrip.crew.domain.vo; -import com.retrip.crew.domain.exception.CrewTitleException; +import com.retrip.crew.domain.exception.common.InvalidValueException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -11,6 +11,7 @@ class CrewIntroductionTitleTest { @Test @DisplayName("크루 이름은 30자 미만으둜 μ„€μ •ν•΄μ•Όν•œλ‹€.") public void validateName() { - assertThatThrownBy(() -> new CrewTitle("ν†΅μ²œ, νšŒμ–‘, 평강, 이천, κΉ€ν™”, 철원, 양ꡬ, 인제, κ³ μ„±, 강릉, μ†μ΄ˆ λ“± 크루 인원 λͺ¨μ§‘ν•©λ‹ˆλ‹€.")).isExactlyInstanceOf(CrewTitleException.class); + assertThatThrownBy(() -> new CrewTitle("ν†΅μ²œ, νšŒμ–‘, 평강, 이천, κΉ€ν™”, 철원, 양ꡬ, 인제, κ³ μ„±, 강릉, μ†μ΄ˆ λ“± 크루 인원 λͺ¨μ§‘ν•©λ‹ˆλ‹€.")) + .isExactlyInstanceOf(InvalidValueException.class); } } From 64d5ba7005c9e8f6d9f81447d37e1a21d4f863aa Mon Sep 17 00:00:00 2001 From: Kimjunho <39546306+xjvmdutl@users.noreply.github.com> Date: Mon, 3 Mar 2025 18:06:21 +0900 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=9E=91=EC=97=85=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: junhokim --- build.gradle | 4 +++ .../in/request/CrewCreateRequest.java | 5 +++ .../in/response/CrewCreateResponse.java | 6 ++++ .../in/presentation/rest/CrewController.java | 12 +++---- .../crew/infra/config/SwaggerConfig.java | 32 +++++++++++++++++++ src/main/resources/application.yml | 4 +++ 6 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/retrip/crew/infra/config/SwaggerConfig.java diff --git a/build.gradle b/build.gradle index 80325ee..3c88f9e 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,10 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation "org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.5" + implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.5" + + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java b/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java index c987d3d..ec2afc5 100644 --- a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java +++ b/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java @@ -1,14 +1,19 @@ package com.retrip.crew.application.in.request; import com.retrip.crew.domain.entity.Crew; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Size; import java.util.UUID; +@Schema(description = "크루 생성 Request") public record CrewCreateRequest( + @Schema(description = "리더 ID") UUID leader, + @Schema(description = "크루 타이틀") @Size(min = 1, max = 30) String title, + @Schema(description = "크루 μ„€λͺ…") @Size(min = 1, max = 500) String description ){ diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java b/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java index b710eba..c325d4f 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java @@ -1,13 +1,19 @@ package com.retrip.crew.application.in.response; import com.retrip.crew.domain.entity.Crew; +import io.swagger.v3.oas.annotations.media.Schema; import java.util.UUID; +@Schema(description = "크루 생성 Response") public record CrewCreateResponse( + @Schema(description = "크루 ID") UUID id, + @Schema(description = "크루 타이틀") String title, + @Schema(description = "크루 μ„€λͺ…") String description, + @Schema(description = "리더 ID") UUID leaderId ) { public static CrewCreateResponse of(Crew crew) { diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java index 94d47bd..1b71cfb 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java @@ -3,15 +3,9 @@ import com.retrip.crew.application.in.request.CrewCreateRequest; import com.retrip.crew.application.in.response.CrewCreateResponse; import com.retrip.crew.application.in.usecase.CreateCrewUseCase; -import com.retrip.crew.domain.exception.CrewNotFoundException; -import com.retrip.crew.domain.exception.common.ErrorCode; -import com.retrip.crew.domain.exception.common.InvalidValueException; -import com.retrip.crew.infra.adapter.in.presentation.rest.common.ApiResponse; -import jakarta.validation.Valid; +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.*; @@ -20,10 +14,12 @@ @RequiredArgsConstructor @RequestMapping("/crews") @RestController +@Tag(name = "Crew", description = "크루 μ„œλΉ„μŠ€") public class CrewController { private final CreateCrewUseCase createCrewUseCase; @PostMapping + @Schema(description = "크루 생성") public ResponseEntity createCrew(@RequestBody CrewCreateRequest request) { CrewCreateResponse crew = createCrewUseCase.createCrew(request); return ResponseEntity.created(URI.create("/crews/" + crew.id())).body(crew); diff --git a/src/main/java/com/retrip/crew/infra/config/SwaggerConfig.java b/src/main/java/com/retrip/crew/infra/config/SwaggerConfig.java new file mode 100644 index 0000000..51ea12d --- /dev/null +++ b/src/main/java/com/retrip/crew/infra/config/SwaggerConfig.java @@ -0,0 +1,32 @@ +package com.retrip.crew.infra.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.servers.Server; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + @Bean + public OpenAPI springShopOpenAPI() { + return new OpenAPI() + .addServersItem(new Server().url("/")) + .components( + new Components() + ).info( + new Info() + .title("Crew Application") + .version("v0.0.1") + ); + } + @Bean + public GroupedOpenApi crewApi(){ + return GroupedOpenApi.builder() + .group("crews") + .pathsToMatch("/crews/**") + .build(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index aafba9a..42bce00 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -19,3 +19,7 @@ logging: level: org.hibernate.type.descriptor.sql: trace org.springframework.web.client.RestTemplate: DEBUG + +springdoc: + swagger-ui: + use-root-path: true From 4caf101420a6276b53ea069fdb60766adf112ace Mon Sep 17 00:00:00 2001 From: coPpark Date: Sat, 15 Mar 2025 11:50:30 +0900 Subject: [PATCH 05/11] =?UTF-8?q?[feat]=20=ED=81=AC=EB=A3=A8=20=EC=83=81?= =?UTF-8?q?=EC=84=B8,=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20API=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#25)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [feat] 크루 리슀트 쑰회 API κΈ°λŠ₯ κ΅¬ν˜„ 및 ν…ŒμŠ€νŠΈμ½”λ“œ λ¦¬νŒ©ν† λ§ (#4) * [feat] 크루 상세 쑰회 API κΈ°λŠ₯ κ΅¬ν˜„ (#4) * feat: μŠ€μ›¨κ±° μΆ”κ°€ μž‘μ—… (#24) Co-authored-by: junhokim * [fix] 크루 상세 쑰회 API μ½”λ“œλ¦¬λ·° 반영 (#4) * [fix] λ‹¨μœ„ν…ŒμŠ€νŠΈ SpringBootTest -> DataJpaTest μ‚¬μš©ν•˜λ„λ‘ μˆ˜μ • (#4) --------- Co-authored-by: Kimjunho <39546306+xjvmdutl@users.noreply.github.com> Co-authored-by: junhokim --- .../crew/application/in/CrewService.java | 40 ++++++- .../in/request/CrewCreateRequest.java | 8 +- .../application/in/request/CrewOrder.java | 12 ++ .../in/response/CrewCreateResponse.java | 2 +- .../in/response/CrewDetailResponse.java | 77 +++++++++++++ .../in/response/CrewListResponse.java | 23 ++++ .../in/usecase/GetCrewUseCase.java | 14 +++ .../out/repository/CrewMemberRepository.java | 9 ++ .../out/repository/CrewQueryRepository.java | 10 ++ .../out/repository/CrewRepository.java | 1 - .../com/retrip/crew/domain/entity/Crew.java | 19 ++-- .../in/presentation/rest/CrewController.java | 32 ++++++ .../rest/common/ScrollPageResponse.java | 18 +++ .../mysql/query/CrewQuerydslRepository.java | 88 +++++++++++++++ .../crew/infra/util/PaginationUtils.java | 28 +++++ src/main/resources/application.yml | 1 + .../com/retrip/crew/CrewApplicationTests.java | 1 + .../crew/application/in/CrewServiceTest.java | 103 +++++++++++++----- .../java/com/retrip/crew/common/BaseTest.java | 41 +++++++ .../crew/common/config/QuerydslConfig.java | 27 +++++ .../crew/common/fixture/CrewFixture.java | 29 +++++ .../retrip/crew/domain/entity/CrewTest.java | 6 +- .../domain/vo/CrewIntroductionTitleTest.java | 4 +- src/test/java/resources/application.yml | 25 +++++ 24 files changed, 572 insertions(+), 46 deletions(-) create mode 100644 src/main/java/com/retrip/crew/application/in/request/CrewOrder.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/CrewDetailResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/CrewListResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/usecase/GetCrewUseCase.java create mode 100644 src/main/java/com/retrip/crew/application/out/repository/CrewMemberRepository.java create mode 100644 src/main/java/com/retrip/crew/application/out/repository/CrewQueryRepository.java create mode 100644 src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ScrollPageResponse.java create mode 100644 src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java create mode 100644 src/main/java/com/retrip/crew/infra/util/PaginationUtils.java create mode 100644 src/test/java/com/retrip/crew/common/BaseTest.java create mode 100644 src/test/java/com/retrip/crew/common/config/QuerydslConfig.java create mode 100644 src/test/java/com/retrip/crew/common/fixture/CrewFixture.java create mode 100644 src/test/java/resources/application.yml 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 661c355..0f56285 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -1,19 +1,33 @@ package com.retrip.crew.application.in; import com.retrip.crew.application.in.request.CrewCreateRequest; +import com.retrip.crew.application.in.request.CrewOrder; import com.retrip.crew.application.in.response.CrewCreateResponse; +import com.retrip.crew.application.in.response.CrewDetailResponse; +import com.retrip.crew.application.in.response.CrewListResponse; import com.retrip.crew.application.in.usecase.CreateCrewUseCase; +import com.retrip.crew.application.in.usecase.GetCrewUseCase; +import com.retrip.crew.application.out.repository.CrewMemberRepository; +import com.retrip.crew.application.out.repository.CrewQueryRepository; import com.retrip.crew.application.out.repository.CrewRepository; import com.retrip.crew.domain.entity.Crew; -import jakarta.transaction.Transactional; +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 java.util.UUID; 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; @Service @Transactional @RequiredArgsConstructor -public class CrewService implements CreateCrewUseCase { +public class CrewService implements CreateCrewUseCase, GetCrewUseCase { private final CrewRepository crewRepository; + private final CrewMemberRepository crewMemberRepository; + private final CrewQueryRepository crewQueryRepository; @Override public CrewCreateResponse createCrew(CrewCreateRequest request) { @@ -21,4 +35,26 @@ public CrewCreateResponse createCrew(CrewCreateRequest request) { return CrewCreateResponse.of(crew); } + + @Override + @Transactional(readOnly = true) + 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()); + } + + @Override + @Transactional(readOnly = true) + public CrewDetailResponse getCrewDetail(UUID crewId) { + Crew crew = findById(crewId); + int memberCount = crewMemberRepository.countByCrewId(crewId); + return CrewDetailResponse.of(crew, memberCount); + } + + private Crew findById(UUID crewId){ + return crewRepository.findById(crewId) + .orElseThrow(CrewNotFoundException::new); + } } diff --git a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java b/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java index ec2afc5..0ee1e93 100644 --- a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java +++ b/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java @@ -2,6 +2,7 @@ import com.retrip.crew.domain.entity.Crew; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Size; import java.util.UUID; @@ -15,10 +16,13 @@ public record CrewCreateRequest( String title, @Schema(description = "크루 μ„€λͺ…") @Size(min = 1, max = 500) - String description + String description, + @Schema(description = "크루 μ΅œλŒ€ 인원") + @Min(1) + int maxMembers ){ public Crew to(UUID leader) { - return Crew.create(this.title, this.description, leader); + return Crew.create(this.title, this.description, this.maxMembers, leader); } } diff --git a/src/main/java/com/retrip/crew/application/in/request/CrewOrder.java b/src/main/java/com/retrip/crew/application/in/request/CrewOrder.java new file mode 100644 index 0000000..4baabff --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/request/CrewOrder.java @@ -0,0 +1,12 @@ +package com.retrip.crew.application.in.request; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum CrewOrder { + DATE("createdAt"); + + private final String field; +} diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java b/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java index c325d4f..cdb54e9 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java @@ -20,7 +20,7 @@ public static CrewCreateResponse of(Crew crew) { return new CrewCreateResponse( crew.getId(), crew.getTitle().getValue(), - crew.getDescription().getValue(), + crew.getDescription(), crew.getLeader().getId() ); } diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewDetailResponse.java b/src/main/java/com/retrip/crew/application/in/response/CrewDetailResponse.java new file mode 100644 index 0000000..95d4705 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/CrewDetailResponse.java @@ -0,0 +1,77 @@ +package com.retrip.crew.application.in.response; + +import com.retrip.crew.domain.entity.Crew; +import com.retrip.crew.domain.entity.CrewMember; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import java.util.UUID; +import lombok.Builder; + +@Schema +@Builder +public record CrewDetailResponse( + @Schema(description = "크루 ID") + UUID id, + + @Schema(description = "크루 타이틀") + String title, + + @Schema(description = "크루 μ„€λͺ…") + String description, + + @Schema(description = "크루 리더 ID") + UUID leaderId, + + @Schema(description = "크루원 ν˜„μž¬ μΈμ›μˆ˜") + Integer memberCount, + + @Schema(description = "크루원 μˆ˜μš©κ°€λŠ₯ν•œ μ΅œλŒ€ μΈμ›μˆ˜") + Integer maxMemberCount, + + @Schema(description = "크루원 리슀트") + List members +) { + + public static CrewDetailResponse of(Crew crew, int memberCount){ + return CrewDetailResponse.builder() + .id(crew.getId()) + .title(crew.getTitle().getValue()) + .description(crew.getDescription()) + .leaderId(crew.getLeader().getMemberId()) + .memberCount(memberCount) + .maxMemberCount(crew.getMaxMembers()) + .members(toList(crew.getCrewMembers().getValues())) + .build(); + } + + public static List toList(List crewMembers){ + return crewMembers.stream() + .map(CrewMemberResponse::from) + .toList(); + } + + @Schema + public record CrewMemberResponse( + + @Schema(description = "크루 멀버 고유 ID") + UUID id, + + @Schema(description = "크루원 ID") + UUID memberId, + + @Schema(description = "크루원 μ—­ν• μ½”λ“œ") + String roleCode, + + @Schema(description = "크루원 μ—­ν• λͺ…") + String roleName + ){ + public static CrewMemberResponse from(CrewMember crewMember){ + return new CrewMemberResponse( + crewMember.getId(), + crewMember.getMemberId(), + crewMember.getCrewMemberRole().getCode(), + crewMember.getCrewMemberRole().getViewName() + ); + } + } +} diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewListResponse.java b/src/main/java/com/retrip/crew/application/in/response/CrewListResponse.java new file mode 100644 index 0000000..7376a82 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/CrewListResponse.java @@ -0,0 +1,23 @@ +package com.retrip.crew.application.in.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.UUID; + +@Schema(description = "크루 리슀트 쑰회 Response") +public record CrewListResponse( + @Schema(description = "크루 ID") + UUID id, + + @Schema(description = "크루 타이틀") + String title, + + @Schema(description = "크루 리더 ID") + UUID leaderId, + + @Schema(description = "크루원 ν˜„μž¬ μΈμ›μˆ˜") + Long memberCount, + + @Schema(description = "크루원 μˆ˜μš©κ°€λŠ₯ν•œ μ΅œλŒ€ μΈμ›μˆ˜") + Integer maxMemberCount +) { +} diff --git a/src/main/java/com/retrip/crew/application/in/usecase/GetCrewUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/GetCrewUseCase.java new file mode 100644 index 0000000..34b9bc4 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/usecase/GetCrewUseCase.java @@ -0,0 +1,14 @@ +package com.retrip.crew.application.in.usecase; + +import com.retrip.crew.application.in.request.CrewOrder; +import com.retrip.crew.application.in.response.CrewDetailResponse; +import com.retrip.crew.application.in.response.CrewListResponse; +import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; +import java.util.UUID; +import org.springframework.data.domain.Pageable; + +public interface GetCrewUseCase { + ScrollPageResponse getCrews(Pageable pageable, String keyword, CrewOrder order, String sort); + + CrewDetailResponse getCrewDetail(UUID crewId); +} diff --git a/src/main/java/com/retrip/crew/application/out/repository/CrewMemberRepository.java b/src/main/java/com/retrip/crew/application/out/repository/CrewMemberRepository.java new file mode 100644 index 0000000..c4151cc --- /dev/null +++ b/src/main/java/com/retrip/crew/application/out/repository/CrewMemberRepository.java @@ -0,0 +1,9 @@ +package com.retrip.crew.application.out.repository; + +import com.retrip.crew.domain.entity.CrewMember; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CrewMemberRepository extends JpaRepository { + int countByCrewId(UUID crewId); +} 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 new file mode 100644 index 0000000..6a1c089 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/out/repository/CrewQueryRepository.java @@ -0,0 +1,10 @@ +package com.retrip.crew.application.out.repository; + +import com.retrip.crew.application.in.response.CrewListResponse; +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); +} diff --git a/src/main/java/com/retrip/crew/application/out/repository/CrewRepository.java b/src/main/java/com/retrip/crew/application/out/repository/CrewRepository.java index 54dedff..a709c5a 100644 --- a/src/main/java/com/retrip/crew/application/out/repository/CrewRepository.java +++ b/src/main/java/com/retrip/crew/application/out/repository/CrewRepository.java @@ -1,6 +1,5 @@ package com.retrip.crew.application.out.repository; - import com.retrip.crew.domain.entity.Crew; import org.springframework.data.jpa.repository.JpaRepository; 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 d2c9f28..f5dc7c9 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Crew.java +++ b/src/main/java/com/retrip/crew/domain/entity/Crew.java @@ -10,25 +10,24 @@ import java.util.UUID; @Entity +@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Crew extends BaseEntity { @Id @Column(columnDefinition = "varbinary(16)") - @Getter private UUID id; - @Getter @Embedded private CrewTitle title; @Embedded - @Getter private CrewDescription description; - @Embedded private CrewMembers crewMembers; + @Column(name = "max_members", nullable = false) + private int maxMembers; @Embedded private Posts posts; @@ -39,26 +38,30 @@ public class Crew extends BaseEntity { @Embedded private Introductions introductions; - @Version private long version; - private Crew(String name, String description, UUID leader) { + private Crew(String name, String description, int maxMembers, UUID leader) { this.id = UUID.randomUUID(); this.title = new CrewTitle(name); this.description = new CrewDescription(description); + this.maxMembers = maxMembers; this.crewMembers = new CrewMembers(this, leader); this.posts = new Posts(); this.announcements = new Announcements(); this.introductions = new Introductions(); } - public static Crew create(String title, String description, UUID leader) { - return new Crew(title, description, leader); + public static Crew create(String title, String description, int maxMembers, UUID leader) { + return new Crew(title, description, maxMembers, leader); } public CrewMember getLeader() { return crewMembers.getLeader(); } + public String getDescription(){ + return description.getValue(); + } + } diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java index 1b71cfb..cea499d 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java @@ -1,10 +1,20 @@ package com.retrip.crew.infra.adapter.in.presentation.rest; import com.retrip.crew.application.in.request.CrewCreateRequest; +import com.retrip.crew.application.in.request.CrewOrder; import com.retrip.crew.application.in.response.CrewCreateResponse; +import com.retrip.crew.application.in.response.CrewDetailResponse; +import com.retrip.crew.application.in.response.CrewListResponse; import com.retrip.crew.application.in.usecase.CreateCrewUseCase; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import com.retrip.crew.application.in.usecase.GetCrewUseCase; +import com.retrip.crew.infra.adapter.in.presentation.rest.common.ApiResponse; +import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -17,6 +27,7 @@ @Tag(name = "Crew", description = "크루 μ„œλΉ„μŠ€") public class CrewController { private final CreateCrewUseCase createCrewUseCase; + private final GetCrewUseCase getCrewUseCase; @PostMapping @Schema(description = "크루 생성") @@ -24,4 +35,25 @@ public ResponseEntity createCrew(@RequestBody CrewCreateRequ CrewCreateResponse crew = createCrewUseCase.createCrew(request); return ResponseEntity.created(URI.create("/crews/" + crew.id())).body(crew); } + + @GetMapping + @Schema(description = "크루 리슀트 쑰회") + public ResponseEntity>> getCrews( + @RequestParam(name = "keyword", required = false) String keyword, + @RequestParam(name = "order", defaultValue = "DATE") CrewOrder order, + @RequestParam(name = "sort", defaultValue = "asc") String sort, + @PageableDefault(size = 10) Pageable pageable + ) { + ScrollPageResponse response = getCrewUseCase.getCrews(pageable, keyword, order, sort); + return ResponseEntity.ok().body(ApiResponse.ok(response)); + } + + @GetMapping("/{crewId}") + @Schema(description = "크루 상세 쑰회") + public ResponseEntity> getCrewDetail( + @PathVariable("crewId") UUID crewId + ) { + CrewDetailResponse response = getCrewUseCase.getCrewDetail(crewId); + return ResponseEntity.ok().body(ApiResponse.ok(response)); + } } diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ScrollPageResponse.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ScrollPageResponse.java new file mode 100644 index 0000000..3d40b8c --- /dev/null +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ScrollPageResponse.java @@ -0,0 +1,18 @@ +package com.retrip.crew.infra.adapter.in.presentation.rest.common; + +import java.util.List; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ScrollPageResponse { + private Long totalCount; + private boolean hasNext; + private List list; + + public static ScrollPageResponse of(Long totalCount, boolean hasNext, List list) { + return new ScrollPageResponse<>(totalCount, hasNext, list); + } +} 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 new file mode 100644 index 0000000..a88f942 --- /dev/null +++ b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java @@ -0,0 +1,88 @@ +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.infra.util.PaginationUtils.checkEndPage; + +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.Order; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.jpa.impl.JPAQueryFactory; +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.QCrewMember; +import java.util.List; +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; + +@Repository +@RequiredArgsConstructor +public class CrewQuerydslRepository implements CrewQueryRepository { + private final JPAQueryFactory query; + + @Override + public Slice getCrews(Pageable pageable, String keyword) { + QCrewMember subCrewMember = new QCrewMember("subCrewMember"); + 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.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(); + } + + private BooleanExpression crewTitleContains(String title) { + return title == null ? null : crew.title.value.contains(title); + } + + private OrderSpecifier createCrewOrderSpecifier(Pageable pageable) { + if (!pageable.getSort().isEmpty()) { + for (Sort.Order order : pageable.getSort()) { + Order direction = order.isAscending() ? Order.ASC : Order.DESC; + if (CrewOrder.DATE.getField().equals(order.getProperty())) { + return new OrderSpecifier<>(direction, crew.createdAt); + } else { + return new OrderSpecifier<>(Order.ASC, crew.createdAt); + } + } + } + return new OrderSpecifier<>(Order.ASC, crew.createdAt); + } +} diff --git a/src/main/java/com/retrip/crew/infra/util/PaginationUtils.java b/src/main/java/com/retrip/crew/infra/util/PaginationUtils.java new file mode 100644 index 0000000..82875ce --- /dev/null +++ b/src/main/java/com/retrip/crew/infra/util/PaginationUtils.java @@ -0,0 +1,28 @@ +package com.retrip.crew.infra.util; + +import java.util.List; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.data.domain.Sort; + +public abstract class PaginationUtils { + + public static Pageable createPageRequest(Pageable pageable, String order, String sort) { + return PageRequest.of( + pageable.getPageNumber(), pageable.getPageSize(), + Sort.by(Sort.Direction.fromString(sort), order) + ); + } + + /** λ¬΄ν•œμŠ€ν¬λ‘€ μœ„ν•΄ hasNext 체크 및 μ œκ±°ν•˜λŠ” util */ + public static Slice checkEndPage(Pageable pageable, List results) { + boolean hasNext = false; + if (results.size() > pageable.getPageSize()) { + hasNext = true; + results.remove(pageable.getPageSize()); + } + return new SliceImpl<>(results, pageable, hasNext); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 42bce00..f9bb91b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -13,6 +13,7 @@ spring: show_sql: true format_sql: true dialect: org.hibernate.dialect.MySQL8Dialect + default_batch_fetch_size: 100 open-in-view: false #logging logging: diff --git a/src/test/java/com/retrip/crew/CrewApplicationTests.java b/src/test/java/com/retrip/crew/CrewApplicationTests.java index 405f037..7ecfcc6 100644 --- a/src/test/java/com/retrip/crew/CrewApplicationTests.java +++ b/src/test/java/com/retrip/crew/CrewApplicationTests.java @@ -15,6 +15,7 @@ void create() { assertThatCode(() -> Crew.create( "인천 크루 λͺ¨μ§‘", "인천 크루원을 λͺ¨μ§‘ν•©λ‹ˆλ‹€.\n κ°€μž…λ‚˜μ΄: 20~29μ‚΄ \n κΈˆμ§€ 사항\n - μ—°μ•  κΈˆμ§€\n - λ§Œλ‚¨ 당일 μ·¨μ†Œ κΈˆμ§€\n - 사적 연락 κΈˆμ§€", + 4, UUID.randomUUID() )).doesNotThrowAnyException(); } diff --git a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java index df9bf29..994ac86 100644 --- a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -1,44 +1,95 @@ package com.retrip.crew.application.in; import com.retrip.crew.application.in.request.CrewCreateRequest; +import com.retrip.crew.application.in.request.CrewOrder; import com.retrip.crew.application.in.response.CrewCreateResponse; -import com.retrip.crew.application.out.repository.CrewRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -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 com.retrip.crew.application.in.response.CrewDetailResponse; +import com.retrip.crew.application.in.response.CrewListResponse; +import com.retrip.crew.common.BaseTest; +import com.retrip.crew.domain.entity.CrewMemberRole; +import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; +import java.util.List; import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import static com.retrip.crew.common.fixture.CrewFixture.createCrew; +import static com.retrip.crew.common.fixture.CrewFixture.createMultipleCrews; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; -@DataJpaTest -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -class CrewServiceTest { - @Autowired - CrewRepository crewRepository; - - CrewService crewService; - UUID memberId = UUID.randomUUID(); - - @BeforeEach - void setUp() { - crewService = new CrewService(crewRepository); - } +class CrewServiceTest extends BaseTest { - @DisplayName("크루λ₯Ό 생성 ν•œλ‹€.") @Test - void createCrew() { - CrewCreateRequest request = new CrewCreateRequest( - memberId, + void 크루λ₯Ό_생성_ν•œλ‹€() { + //given + CrewCreateRequest request = createCrew( + μ •μˆ˜_ID, "μ†μ΄ˆ 크루원 ꡬ함", - "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€." + "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", + 5 ); + + //when CrewCreateResponse response = crewService.createCrew(request); + //then assertThat(response.id()).isNotNull(); assertThat(response.leaderId()).isNotNull(); } + + @Test + void 크루λ₯Ό_검색_및_μ •λ ¬_ν•„ν„°λ§ν•˜μ—¬_μ‘°νšŒν•œλ‹€(){ + //given + List requests = createMultipleCrews( + 10, + μ •μˆ˜_ID, + "μ†μ΄ˆ 크루원 ꡬ함", + "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", + 5 + ); + requests.forEach(request -> { + CrewCreateResponse response = crewService.createCrew(request); + }); + Pageable pageable = PageRequest.of(0, 5); + + //when + ScrollPageResponse response = crewService.getCrews(pageable, "μ†μ΄ˆ 크루원", CrewOrder.DATE, "desc"); + + //then + assertAll( + "크루 검색 및 μ •λ ¬ 검증", + () -> assertThat(response).isNotNull(), + () -> assertThat(response.getList()).hasSize(pageable.getPageSize()), + () -> assertThat(response.getList().getFirst().title()).isEqualTo("μ†μ΄ˆ 크루원 ꡬ함 10"), + () -> assertThat(response.getList().getFirst().memberCount()).isEqualTo(1), + () -> assertThat(response.getList().getFirst().maxMemberCount()).isEqualTo(5), + () -> assertThat(response.isHasNext()).isTrue() + ); + } + + @Test + void 크루_상세λ₯Ό_μ‘°νšŒν•œλ‹€(){ + //given + CrewCreateRequest request = createCrew( + μ •μˆ˜_ID, + "μ†μ΄ˆ 크루원 ꡬ함", + "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", + 5 + ); + UUID crewId = crewService.createCrew(request).id(); + + //when + CrewDetailResponse response = crewService.getCrewDetail(crewId); + + //then + assertAll( + () -> assertThat(response.id()).isEqualTo(crewId), + () -> assertThat(response.leaderId()).isEqualTo(μ •μˆ˜_ID), + () -> assertThat(response.members().size()).isEqualTo(1), + () -> assertThat(response.members().getFirst().roleCode()).isEqualTo(CrewMemberRole.LEADER.getCode()), + () -> assertThat(response.members().getFirst().memberId()).isEqualTo(μ •μˆ˜_ID) + ); + } } diff --git a/src/test/java/com/retrip/crew/common/BaseTest.java b/src/test/java/com/retrip/crew/common/BaseTest.java new file mode 100644 index 0000000..582349d --- /dev/null +++ b/src/test/java/com/retrip/crew/common/BaseTest.java @@ -0,0 +1,41 @@ +package com.retrip.crew.common; + +import com.retrip.crew.application.in.CrewService; +import com.retrip.crew.application.out.repository.CrewMemberRepository; +import com.retrip.crew.application.out.repository.CrewQueryRepository; +import com.retrip.crew.application.out.repository.CrewRepository; +import com.retrip.crew.common.config.QuerydslConfig; +import java.util.UUID; +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 class BaseTest { + + @Autowired + protected CrewRepository crewRepository; + + @Autowired + protected CrewMemberRepository crewMemberRepository; + + @Autowired + protected CrewQueryRepository crewQueryRepository; + + protected CrewService crewService; + + protected UUID μ •μˆ˜_ID = UUID.fromString("13c8ab91-76bc-4f70-93e9-89f1a65dc640"); + protected UUID 홍석_ID = UUID.fromString("bf97d20b-d1f7-46a9-8362-11b9fa02d67d"); + protected UUID μ€€ν˜Έ_ID = UUID.fromString("8b9b67fd-1d88-4b30-bfea-cd8f89fc10d9"); + protected UUID μ§€μˆ˜_ID = UUID.fromString("de3b60d2-5672-464d-8769-bf5c9de5eaff"); + protected UUID ν˜μ§„_ID = UUID.fromString("42880aaf-4b97-4b0c-8a8a-72df4bb592f6"); + + @BeforeEach + void setUp() { + crewService = new CrewService(crewRepository, crewMemberRepository, crewQueryRepository); + } +} diff --git a/src/test/java/com/retrip/crew/common/config/QuerydslConfig.java b/src/test/java/com/retrip/crew/common/config/QuerydslConfig.java new file mode 100644 index 0000000..7331886 --- /dev/null +++ b/src/test/java/com/retrip/crew/common/config/QuerydslConfig.java @@ -0,0 +1,27 @@ +package com.retrip.crew.common.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.retrip.crew.infra.adapter.out.persistence.mysql.query.CrewQuerydslRepository; +import jakarta.persistence.EntityManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@EnableJpaAuditing +@TestConfiguration +public class QuerydslConfig { + + @Autowired + EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } + + @Bean + public CrewQuerydslRepository crewQuerydslRepository(JPAQueryFactory jpaQueryFactory) { + return new CrewQuerydslRepository(jpaQueryFactory); + } +} diff --git a/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java b/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java new file mode 100644 index 0000000..2ed3b6a --- /dev/null +++ b/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java @@ -0,0 +1,29 @@ +package com.retrip.crew.common.fixture; + +import com.retrip.crew.application.in.request.CrewCreateRequest; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public abstract class CrewFixture { + + public static CrewCreateRequest createCrew(UUID memberId, String title, String description, int maxMembers) { + return new CrewCreateRequest( + memberId, + title, + description, + maxMembers + ); + } + + public static List createMultipleCrews(int count, UUID memberId, String baseTitle, String baseDescription, int maxMembers) { + return IntStream.range(0, count) + .mapToObj(i -> { + String title = baseTitle + " " + (i + 1); + String description = baseDescription + " " + (i + 1); + return createCrew(memberId, title, description, maxMembers); + }) + .collect(Collectors.toList()); + } +} diff --git a/src/test/java/com/retrip/crew/domain/entity/CrewTest.java b/src/test/java/com/retrip/crew/domain/entity/CrewTest.java index 2c89485..187e9a7 100644 --- a/src/test/java/com/retrip/crew/domain/entity/CrewTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/CrewTest.java @@ -8,13 +8,13 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; class CrewTest { - @DisplayName("크루 생성 ν…ŒμŠ€νŠΈ") + @Test - public void create() { + public void 크루_생성_ν…ŒμŠ€νŠΈ() { assertThatCode(() -> Crew.create( "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", + 4, UUID.randomUUID())).doesNotThrowAnyException(); } - } diff --git a/src/test/java/com/retrip/crew/domain/vo/CrewIntroductionTitleTest.java b/src/test/java/com/retrip/crew/domain/vo/CrewIntroductionTitleTest.java index 657c233..2b5c820 100644 --- a/src/test/java/com/retrip/crew/domain/vo/CrewIntroductionTitleTest.java +++ b/src/test/java/com/retrip/crew/domain/vo/CrewIntroductionTitleTest.java @@ -1,7 +1,6 @@ package com.retrip.crew.domain.vo; import com.retrip.crew.domain.exception.common.InvalidValueException; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; @@ -9,8 +8,7 @@ class CrewIntroductionTitleTest { @Test - @DisplayName("크루 이름은 30자 미만으둜 μ„€μ •ν•΄μ•Όν•œλ‹€.") - public void validateName() { + public void 크루_이름은_30자_미만으둜_μ„€μ •ν•΄μ•Όν•œλ‹€() { assertThatThrownBy(() -> new CrewTitle("ν†΅μ²œ, νšŒμ–‘, 평강, 이천, κΉ€ν™”, 철원, 양ꡬ, 인제, κ³ μ„±, 강릉, μ†μ΄ˆ λ“± 크루 인원 λͺ¨μ§‘ν•©λ‹ˆλ‹€.")) .isExactlyInstanceOf(InvalidValueException.class); } diff --git a/src/test/java/resources/application.yml b/src/test/java/resources/application.yml new file mode 100644 index 0000000..1efe678 --- /dev/null +++ b/src/test/java/resources/application.yml @@ -0,0 +1,25 @@ +spring: + application: + name: crew + #JPA + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false;MODE=MySQL + username: sa + password: + jpa: + hibernate: + ddl-auto: create-drop + properties: + hibernate: + show_sql: true + format_sql: true + dialect: org.hibernate.dialect.MySQL8Dialect + default_batch_fetch_size: 100 + open-in-view: false + +#logging +logging: + level: + org.hibernate.type.descriptor.sql: trace + org.springframework.web.client.RestTemplate: DEBUG From b0dd14d311300f52caded7eb0464b113313ddc93 Mon Sep 17 00:00:00 2001 From: mandykr <94955454+mandykr@users.noreply.github.com> Date: Mon, 17 Mar 2025 18:01:14 +0900 Subject: [PATCH 06/11] =?UTF-8?q?=ED=81=AC=EB=A3=A8=20=EC=B0=B8=EC=97=AC?= =?UTF-8?q?=20=EC=9A=94=EC=B2=AD=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 크루 λͺ¨μ§‘μƒνƒœ λ³€κ²½μ‹œ μ΅œλŒ€ μΈμ›μˆ˜ 검증 μΆ”κ°€ - IllegalStateException μΆ”κ°€ - common exception μƒμ„±μž μˆ˜μ • * refactor: CrewMembers size 쑰회 λ©”μ„œλ“œ μΆ”κ°€ * fix: conflict repair --- .../crew/application/in/CrewService.java | 53 ++++++++-- .../in/request/CreateDemandRequest.java | 8 ++ .../in/request/CrewCreateRequest.java | 8 +- .../in/request/CrewUpdateRequest.java | 20 ++++ .../ChangeRecruitmentStatusResponse.java | 19 ++++ .../in/response/CreateDemandResponse.java | 14 +++ .../in/response/CrewCreateResponse.java | 18 +++- .../in/response/CrewDetailResponse.java | 5 +- .../in/response/CrewUpdateResponse.java | 38 +++++++ .../in/usecase/CreateCrewUseCase.java | 8 -- .../in/usecase/ManageCrewUseCase.java | 14 +++ .../in/usecase/ManageDemandUseCase.java | 10 ++ .../in/usecase/UpdateRecruitmentUseCase.java | 11 ++ .../com/retrip/crew/domain/entity/Crew.java | 32 ++++-- .../crew/domain/entity/CrewMembers.java | 9 +- .../com/retrip/crew/domain/entity/Demand.java | 33 ++++++ .../crew/domain/entity/Recruitment.java | 68 ++++++++++++ .../exception/common/BusinessException.java | 5 + .../common/EntityNotFoundException.java | 5 + .../domain/exception/common/ErrorCode.java | 1 + .../common/IllegalStateException.java | 21 ++++ .../common/InvalidValueException.java | 4 + .../crew/domain/vo/RecruitmentStatus.java | 24 +++++ .../in/presentation/rest/CrewController.java | 59 ++++++++--- .../mysql/query/CrewQuerydslRepository.java | 15 +-- src/main/resources/application.yml | 1 + .../com/retrip/crew/CrewApplicationTests.java | 17 +-- .../crew/application/in/CrewServiceTest.java | 100 +++++++++++++++--- .../{BaseTest.java => ServiceTest.java} | 11 +- .../crew/common/fixture/CrewFixture.java | 4 +- .../retrip/crew/domain/entity/CrewTest.java | 87 +++++++++++++-- .../crew/domain/entity/RecruitmentTest.java | 13 +++ 32 files changed, 637 insertions(+), 98 deletions(-) create mode 100644 src/main/java/com/retrip/crew/application/in/request/CreateDemandRequest.java create mode 100644 src/main/java/com/retrip/crew/application/in/request/CrewUpdateRequest.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/CreateDemandResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/CrewUpdateResponse.java delete mode 100644 src/main/java/com/retrip/crew/application/in/usecase/CreateCrewUseCase.java create mode 100644 src/main/java/com/retrip/crew/application/in/usecase/ManageCrewUseCase.java create mode 100644 src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java create mode 100644 src/main/java/com/retrip/crew/application/in/usecase/UpdateRecruitmentUseCase.java create mode 100644 src/main/java/com/retrip/crew/domain/entity/Demand.java create mode 100644 src/main/java/com/retrip/crew/domain/entity/Recruitment.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/common/IllegalStateException.java create mode 100644 src/main/java/com/retrip/crew/domain/vo/RecruitmentStatus.java rename src/test/java/com/retrip/crew/common/{BaseTest.java => ServiceTest.java} (73%) create mode 100644 src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.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 0f56285..ed978f0 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -1,30 +1,37 @@ package com.retrip.crew.application.in; +import com.retrip.crew.application.in.request.CreateDemandRequest; import com.retrip.crew.application.in.request.CrewCreateRequest; import com.retrip.crew.application.in.request.CrewOrder; -import com.retrip.crew.application.in.response.CrewCreateResponse; -import com.retrip.crew.application.in.response.CrewDetailResponse; -import com.retrip.crew.application.in.response.CrewListResponse; -import com.retrip.crew.application.in.usecase.CreateCrewUseCase; +import com.retrip.crew.application.in.request.CrewUpdateRequest; +import com.retrip.crew.application.in.response.*; import com.retrip.crew.application.in.usecase.GetCrewUseCase; +import com.retrip.crew.application.in.usecase.ManageCrewUseCase; +import com.retrip.crew.application.in.usecase.ManageDemandUseCase; +import com.retrip.crew.application.in.usecase.UpdateRecruitmentUseCase; import com.retrip.crew.application.out.repository.CrewMemberRepository; import com.retrip.crew.application.out.repository.CrewQueryRepository; import com.retrip.crew.application.out.repository.CrewRepository; import com.retrip.crew.domain.entity.Crew; +import com.retrip.crew.domain.entity.Demand; +import com.retrip.crew.domain.entity.Recruitment; import com.retrip.crew.domain.exception.CrewNotFoundException; +import com.retrip.crew.domain.vo.CrewDescription; +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 java.util.UUID; 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 CrewService implements CreateCrewUseCase, GetCrewUseCase { +public class CrewService implements ManageCrewUseCase, UpdateRecruitmentUseCase, ManageDemandUseCase, GetCrewUseCase { private final CrewRepository crewRepository; private final CrewMemberRepository crewMemberRepository; private final CrewQueryRepository crewQueryRepository; @@ -32,10 +39,42 @@ public class CrewService implements CreateCrewUseCase, GetCrewUseCase { @Override public CrewCreateResponse createCrew(CrewCreateRequest request) { Crew crew = crewRepository.save(request.to(request.leader())); - return CrewCreateResponse.of(crew); } + @Override + public CrewUpdateResponse updateCrew(UUID crewId, CrewUpdateRequest request) { + Crew crew = findById(crewId); + CrewTitle title = new CrewTitle(request.title()); + CrewDescription description = new CrewDescription(request.description()); + Recruitment recruitment = crew.getRecruitment(); + + crew.update(title, description); + recruitment.updateMaxMembers(request.maxMembers()); + return CrewUpdateResponse.of(crew); + } + + @Override + public ChangeRecruitmentStatusResponse startRecruitment(UUID crewId) { + Crew crew = findById(crewId); + crew.startRecruitment(); + return ChangeRecruitmentStatusResponse.of(crew); + } + + @Override + public ChangeRecruitmentStatusResponse stopRecruitment(UUID crewId) { + Crew crew = findById(crewId); + crew.stopRecruitment(); + return ChangeRecruitmentStatusResponse.of(crew); + } + + @Override + public CreateDemandResponse createDemand(UUID crewId, CreateDemandRequest request) { + Crew crew = findById(crewId); + Demand demand = crew.demand(request.memberId()); + return CreateDemandResponse.of(crew.getId(), demand); + } + @Override @Transactional(readOnly = true) public ScrollPageResponse getCrews(Pageable pageable, String keyword, CrewOrder order, String sort) { diff --git a/src/main/java/com/retrip/crew/application/in/request/CreateDemandRequest.java b/src/main/java/com/retrip/crew/application/in/request/CreateDemandRequest.java new file mode 100644 index 0000000..537f1ea --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/request/CreateDemandRequest.java @@ -0,0 +1,8 @@ +package com.retrip.crew.application.in.request; + +import java.util.UUID; + +public record CreateDemandRequest( + UUID memberId +) { +} diff --git a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java b/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java index 0ee1e93..145bd3a 100644 --- a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java +++ b/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java @@ -2,7 +2,6 @@ import com.retrip.crew.domain.entity.Crew; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Size; import java.util.UUID; @@ -11,14 +10,17 @@ public record CrewCreateRequest( @Schema(description = "리더 ID") UUID leader, + @Schema(description = "크루 타이틀") @Size(min = 1, max = 30) String title, + @Schema(description = "크루 μ„€λͺ…") @Size(min = 1, max = 500) String description, - @Schema(description = "크루 μ΅œλŒ€ 인원") - @Min(1) + + @Schema(description = "크루 μ΅œλŒ€ μΈμ›μˆ˜") + @Size(min = 5, max = 1000) int maxMembers ){ diff --git a/src/main/java/com/retrip/crew/application/in/request/CrewUpdateRequest.java b/src/main/java/com/retrip/crew/application/in/request/CrewUpdateRequest.java new file mode 100644 index 0000000..9d5a509 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/request/CrewUpdateRequest.java @@ -0,0 +1,20 @@ +package com.retrip.crew.application.in.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; + +@Schema(description = "크루 정보 μˆ˜μ • Request") +public record CrewUpdateRequest( + @Schema(description = "크루 타이틀") + @Size(min = 1, max = 30) + String title, + + @Schema(description = "크루 μ„€λͺ…") + @Size(min = 1, max = 500) + String description, + + @Schema(description = "크루 μ΅œλŒ€ μΈμ›μˆ˜") + @Size(min = 5, max = 1000) + int maxMembers +) { +} diff --git a/src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java b/src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java new file mode 100644 index 0000000..0a56b73 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java @@ -0,0 +1,19 @@ +package com.retrip.crew.application.in.response; + +import com.retrip.crew.domain.entity.Crew; +import com.retrip.crew.domain.vo.RecruitmentStatus; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.UUID; + +public record ChangeRecruitmentStatusResponse( + @Schema(description = "크루 ID") + UUID id, + + @Schema(description = "λͺ¨μ§‘ μƒνƒœ") + RecruitmentStatus status +) { + public static ChangeRecruitmentStatusResponse of(Crew crew) { + return new ChangeRecruitmentStatusResponse(crew.getId(), crew.getRecruitment().getStatus()); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/response/CreateDemandResponse.java b/src/main/java/com/retrip/crew/application/in/response/CreateDemandResponse.java new file mode 100644 index 0000000..1c42d65 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/CreateDemandResponse.java @@ -0,0 +1,14 @@ +package com.retrip.crew.application.in.response; + +import com.retrip.crew.domain.entity.Demand; + +import java.util.UUID; + +public record CreateDemandResponse( + UUID crewId, + UUID memberId +) { + public static CreateDemandResponse of(UUID crewId, Demand demand) { + return new CreateDemandResponse(crewId, demand.getMemberId()); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java b/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java index cdb54e9..ad231f9 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java @@ -1,6 +1,7 @@ package com.retrip.crew.application.in.response; import com.retrip.crew.domain.entity.Crew; +import com.retrip.crew.domain.vo.RecruitmentStatus; import io.swagger.v3.oas.annotations.media.Schema; import java.util.UUID; @@ -9,19 +10,32 @@ public record CrewCreateResponse( @Schema(description = "크루 ID") UUID id, + @Schema(description = "크루 타이틀") String title, + @Schema(description = "크루 μ„€λͺ…") String description, + @Schema(description = "리더 ID") - UUID leaderId + UUID leaderId, + + @Schema(description = "크루 μ΅œλŒ€ μΈμ›μˆ˜") + int maxMembers, + + @Schema(description = "λͺ¨μ§‘ μƒνƒœ") + RecruitmentStatus status + + ) { public static CrewCreateResponse of(Crew crew) { return new CrewCreateResponse( crew.getId(), crew.getTitle().getValue(), crew.getDescription(), - crew.getLeader().getId() + crew.getLeader().getId(), + crew.getRecruitment().getMaxMembers(), + crew.getRecruitment().getStatus() ); } } diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewDetailResponse.java b/src/main/java/com/retrip/crew/application/in/response/CrewDetailResponse.java index 95d4705..bc95740 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CrewDetailResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/CrewDetailResponse.java @@ -3,9 +3,10 @@ import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.entity.CrewMember; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; + import java.util.List; import java.util.UUID; -import lombok.Builder; @Schema @Builder @@ -39,7 +40,7 @@ public static CrewDetailResponse of(Crew crew, int memberCount){ .description(crew.getDescription()) .leaderId(crew.getLeader().getMemberId()) .memberCount(memberCount) - .maxMemberCount(crew.getMaxMembers()) + .maxMemberCount(crew.getRecruitment().getMaxMembers()) .members(toList(crew.getCrewMembers().getValues())) .build(); } diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewUpdateResponse.java b/src/main/java/com/retrip/crew/application/in/response/CrewUpdateResponse.java new file mode 100644 index 0000000..2e513c6 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/CrewUpdateResponse.java @@ -0,0 +1,38 @@ +package com.retrip.crew.application.in.response; + +import com.retrip.crew.domain.entity.Crew; +import com.retrip.crew.domain.vo.RecruitmentStatus; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.UUID; + +public record CrewUpdateResponse( + @Schema(description = "크루 ID") + UUID id, + + @Schema(description = "크루 타이틀") + String title, + + @Schema(description = "크루 μ„€λͺ…") + String description, + + @Schema(description = "리더 ID") + UUID leaderId, + + @Schema(description = "크루 μ΅œλŒ€ μΈμ›μˆ˜") + int maxMembers, + + @Schema(description = "λͺ¨μ§‘ μƒνƒœ") + RecruitmentStatus status +) { + public static CrewUpdateResponse of(Crew crew) { + return new CrewUpdateResponse( + crew.getId(), + crew.getTitle().getValue(), + crew.getDescription(), + crew.getLeader().getId(), + crew.getRecruitment().getMaxMembers(), + crew.getRecruitment().getStatus() + ); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/usecase/CreateCrewUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/CreateCrewUseCase.java deleted file mode 100644 index e581d24..0000000 --- a/src/main/java/com/retrip/crew/application/in/usecase/CreateCrewUseCase.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.retrip.crew.application.in.usecase; - -import com.retrip.crew.application.in.request.CrewCreateRequest; -import com.retrip.crew.application.in.response.CrewCreateResponse; - -public interface CreateCrewUseCase { - CrewCreateResponse createCrew(CrewCreateRequest createRequest); -} 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 new file mode 100644 index 0000000..b8fb910 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManageCrewUseCase.java @@ -0,0 +1,14 @@ +package com.retrip.crew.application.in.usecase; + +import com.retrip.crew.application.in.request.CrewCreateRequest; +import com.retrip.crew.application.in.request.CrewUpdateRequest; +import com.retrip.crew.application.in.response.CrewCreateResponse; +import com.retrip.crew.application.in.response.CrewUpdateResponse; + +import java.util.UUID; + +public interface ManageCrewUseCase { + CrewCreateResponse createCrew(CrewCreateRequest request); + + CrewUpdateResponse updateCrew(UUID crewId, CrewUpdateRequest request); +} diff --git a/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java new file mode 100644 index 0000000..ae7008f --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java @@ -0,0 +1,10 @@ +package com.retrip.crew.application.in.usecase; + +import com.retrip.crew.application.in.request.CreateDemandRequest; +import com.retrip.crew.application.in.response.CreateDemandResponse; + +import java.util.UUID; + +public interface ManageDemandUseCase { + CreateDemandResponse createDemand(UUID crewId, CreateDemandRequest request); +} diff --git a/src/main/java/com/retrip/crew/application/in/usecase/UpdateRecruitmentUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/UpdateRecruitmentUseCase.java new file mode 100644 index 0000000..f96e831 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/usecase/UpdateRecruitmentUseCase.java @@ -0,0 +1,11 @@ +package com.retrip.crew.application.in.usecase; + +import com.retrip.crew.application.in.response.ChangeRecruitmentStatusResponse; + +import java.util.UUID; + +public interface UpdateRecruitmentUseCase { + ChangeRecruitmentStatusResponse startRecruitment(UUID crewId); + + ChangeRecruitmentStatusResponse stopRecruitment(UUID crewId); +} 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 f5dc7c9..fd2180e 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Crew.java +++ b/src/main/java/com/retrip/crew/domain/entity/Crew.java @@ -10,13 +10,16 @@ import java.util.UUID; @Entity -@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter public class Crew extends BaseEntity { @Id @Column(columnDefinition = "varbinary(16)") private UUID id; + @Version + private long version; + @Embedded private CrewTitle title; @@ -26,9 +29,6 @@ public class Crew extends BaseEntity { @Embedded private CrewMembers crewMembers; - @Column(name = "max_members", nullable = false) - private int maxMembers; - @Embedded private Posts posts; @@ -38,14 +38,14 @@ public class Crew extends BaseEntity { @Embedded private Introductions introductions; - @Version - private long version; + @Embedded + private Recruitment recruitment; private Crew(String name, String description, int maxMembers, UUID leader) { this.id = UUID.randomUUID(); this.title = new CrewTitle(name); this.description = new CrewDescription(description); - this.maxMembers = maxMembers; + this.recruitment = new Recruitment(maxMembers); this.crewMembers = new CrewMembers(this, leader); this.posts = new Posts(); this.announcements = new Announcements(); @@ -60,6 +60,24 @@ public CrewMember getLeader() { return crewMembers.getLeader(); } + public void startRecruitment() { + int membersSize = crewMembers.getSize(); + this.recruitment.start(membersSize); + } + + public void stopRecruitment() { + this.recruitment.stop(); + } + + public void update(CrewTitle title, CrewDescription description) { + this.title = title; + this.description = description; + } + + public Demand demand(UUID memberId) { + return recruitment.addDemand(memberId, this); + } + public String getDescription(){ return description.getValue(); } diff --git a/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java b/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java index 90feba1..4a358b1 100644 --- a/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java +++ b/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java @@ -27,6 +27,13 @@ private List createLeader(Crew crew, UUID memberId) { } public CrewMember getLeader() { - return this.values.stream().filter(it -> it.getCrewMemberRole() == CrewMemberRole.LEADER).findFirst().orElse(null); + return this.values.stream() + .filter(it -> it.getCrewMemberRole() == CrewMemberRole.LEADER) + .findFirst() + .orElse(null); + } + + public int getSize() { + return this.values.size(); } } diff --git a/src/main/java/com/retrip/crew/domain/entity/Demand.java b/src/main/java/com/retrip/crew/domain/entity/Demand.java new file mode 100644 index 0000000..8fae633 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/entity/Demand.java @@ -0,0 +1,33 @@ +package com.retrip.crew.domain.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +@Getter +public class Demand { + @Id + @Column(columnDefinition = "varbinary(16)") + private UUID id; + private UUID memberId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn( + name = "crew_id", + nullable = false, + columnDefinition = "varbinary(16)", + foreignKey = @ForeignKey(name = "fk_demand_to_crew") + ) + private Crew crew; + + public Demand(UUID memberId, Crew crew) { + this.id = UUID.randomUUID(); + this.memberId = memberId; + this.crew = crew; + } +} diff --git a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java new file mode 100644 index 0000000..e445277 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java @@ -0,0 +1,68 @@ +package com.retrip.crew.domain.entity; + +import com.retrip.crew.domain.exception.common.IllegalStateException; +import com.retrip.crew.domain.vo.RecruitmentStatus; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.OneToMany; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static com.retrip.crew.domain.vo.RecruitmentStatus.RECRUITING; +import static com.retrip.crew.domain.vo.RecruitmentStatus.STOPPED; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +@Embeddable +public class Recruitment { + private int maxMembers; + private RecruitmentStatus status; + + @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) + private List demands = new ArrayList<>(); + + public Recruitment(int maxMembers) { + this.maxMembers = maxMembers; + this.status = RECRUITING; + } + + public void start(int membersSize) { + if (isRecruitmentComplete(membersSize)) { + stop(); + throw new IllegalStateException("μ΅œλŒ€ 인원을 λͺ¨λ‘ λͺ¨μ§‘ μ™„λ£Œν•˜μ—¬ 더 이상 멀버λ₯Ό λͺ¨μ§‘ν•  수 μ—†μŠ΅λ‹ˆλ‹€."); + } + this.status = RECRUITING; + } + + public void stop() { + this.status = STOPPED; + } + + private boolean isRecruitmentComplete(int membersSize) { + return this.maxMembers == membersSize; + } + + public void updateMaxMembers(int maxMembers) { + this.maxMembers = maxMembers; + } + + public Demand addDemand(UUID memberId, Crew crew) { + if (isDuplicate(memberId)) { + throw new IllegalStateException("이미 μš”μ²­ν•œ μ‚¬μš©μžλŠ” λ‹€μ‹œ μš”μ²­ν•  수 μ—†μŠ΅λ‹ˆλ‹€."); + } + Demand demand = new Demand(memberId, crew); + demands.add(demand); + return demand; + } + + private boolean isDuplicate(UUID memberId) { + return demands.stream() + .map(Demand::getMemberId) + .anyMatch(id -> id.equals(memberId)); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/common/BusinessException.java b/src/main/java/com/retrip/crew/domain/exception/common/BusinessException.java index ea05e90..5b4711d 100644 --- a/src/main/java/com/retrip/crew/domain/exception/common/BusinessException.java +++ b/src/main/java/com/retrip/crew/domain/exception/common/BusinessException.java @@ -6,6 +6,11 @@ public class BusinessException extends RuntimeException { private final ErrorCode errorCode; + public BusinessException() { + super(ErrorCode.SERVER_ERROR.getMessage()); + this.errorCode = ErrorCode.SERVER_ERROR; + } + public BusinessException(ErrorCode errorCode) { super(errorCode.getMessage()); this.errorCode = errorCode; diff --git a/src/main/java/com/retrip/crew/domain/exception/common/EntityNotFoundException.java b/src/main/java/com/retrip/crew/domain/exception/common/EntityNotFoundException.java index 2c6d127..359e1f9 100644 --- a/src/main/java/com/retrip/crew/domain/exception/common/EntityNotFoundException.java +++ b/src/main/java/com/retrip/crew/domain/exception/common/EntityNotFoundException.java @@ -2,6 +2,11 @@ public class EntityNotFoundException extends BusinessException { private static final ErrorCode errorCode = ErrorCode.ENTITY_NOT_FOUND; + + public EntityNotFoundException() { + super(errorCode); + } + public EntityNotFoundException(ErrorCode errorCode) { 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 520a72c..4d05d8e 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 @@ -11,6 +11,7 @@ public enum ErrorCode { INVALID_INPUT_VALUE(BAD_REQUEST, "Common-002", "Invalid input value"), HANDLE_ACCESS_DENIED(FORBIDDEN, "Common-003", "Access is denied"), 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", "크루 μ—”ν‹°ν‹°λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.") ; diff --git a/src/main/java/com/retrip/crew/domain/exception/common/IllegalStateException.java b/src/main/java/com/retrip/crew/domain/exception/common/IllegalStateException.java new file mode 100644 index 0000000..f7f1889 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/common/IllegalStateException.java @@ -0,0 +1,21 @@ +package com.retrip.crew.domain.exception.common; + +public class IllegalStateException extends BusinessException { + private static final ErrorCode errorCode = ErrorCode.ILLEGAL_STATE; + + public IllegalStateException() { + super(errorCode); + } + + public IllegalStateException(ErrorCode errorCode) { + super(errorCode); + } + + public IllegalStateException(String message) { + super(errorCode, message); + } + + public IllegalStateException(ErrorCode errorCode, String message) { + super(errorCode, message); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/common/InvalidValueException.java b/src/main/java/com/retrip/crew/domain/exception/common/InvalidValueException.java index 546de82..c001268 100644 --- a/src/main/java/com/retrip/crew/domain/exception/common/InvalidValueException.java +++ b/src/main/java/com/retrip/crew/domain/exception/common/InvalidValueException.java @@ -2,6 +2,10 @@ public class InvalidValueException extends BusinessException { private static final ErrorCode errorCode = ErrorCode.INVALID_INPUT_VALUE; + + public InvalidValueException() { + super(errorCode); + } public InvalidValueException(ErrorCode errorCode) { super(errorCode); } diff --git a/src/main/java/com/retrip/crew/domain/vo/RecruitmentStatus.java b/src/main/java/com/retrip/crew/domain/vo/RecruitmentStatus.java new file mode 100644 index 0000000..7c11d80 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/vo/RecruitmentStatus.java @@ -0,0 +1,24 @@ +package com.retrip.crew.domain.vo; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +@Getter +@AllArgsConstructor +public enum RecruitmentStatus { + RECRUITING("RECRUITING", "λͺ¨μ§‘ 쀑"), + STOPPED("RECRUITMENT_CLOSED", "λͺ¨μ§‘ 쀑지") + ; + + private final String code; + private final String viewName; + + public static RecruitmentStatus codeOf(String code) { + return Arrays.stream(RecruitmentStatus.values()) + .filter(tripStatus -> tripStatus.getCode().equals(code)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” μ½”λ“œμž…λ‹ˆλ‹€.")); + } +} diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java index cea499d..573ff92 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java @@ -1,39 +1,72 @@ package com.retrip.crew.infra.adapter.in.presentation.rest; +import com.retrip.crew.application.in.request.CreateDemandRequest; import com.retrip.crew.application.in.request.CrewCreateRequest; import com.retrip.crew.application.in.request.CrewOrder; -import com.retrip.crew.application.in.response.CrewCreateResponse; -import com.retrip.crew.application.in.response.CrewDetailResponse; -import com.retrip.crew.application.in.response.CrewListResponse; -import com.retrip.crew.application.in.usecase.CreateCrewUseCase; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.tags.Tag; +import com.retrip.crew.application.in.request.CrewUpdateRequest; +import com.retrip.crew.application.in.response.*; import com.retrip.crew.application.in.usecase.GetCrewUseCase; +import com.retrip.crew.application.in.usecase.ManageCrewUseCase; +import com.retrip.crew.application.in.usecase.ManageDemandUseCase; +import com.retrip.crew.application.in.usecase.UpdateRecruitmentUseCase; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ApiResponse; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; -import java.util.UUID; +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 lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.net.URI; +import java.util.UUID; @RequiredArgsConstructor @RequestMapping("/crews") @RestController @Tag(name = "Crew", description = "크루 μ„œλΉ„μŠ€") public class CrewController { - private final CreateCrewUseCase createCrewUseCase; + private final ManageCrewUseCase manageCrewUseCase; + private final UpdateRecruitmentUseCase updateRecruitmentUseCase; + private final ManageDemandUseCase manageDemandUseCase; private final GetCrewUseCase getCrewUseCase; @PostMapping @Schema(description = "크루 생성") - public ResponseEntity createCrew(@RequestBody CrewCreateRequest request) { - CrewCreateResponse crew = createCrewUseCase.createCrew(request); - return ResponseEntity.created(URI.create("/crews/" + crew.id())).body(crew); + public ApiResponse createCrew(@RequestBody CrewCreateRequest request) { + CrewCreateResponse crew = manageCrewUseCase.createCrew(request); + return ApiResponse.created(crew); + } + + @PutMapping("/{crewId}") + @Schema(description = "크루 정보 μˆ˜μ •") + public ApiResponse updateCrew( + @PathVariable UUID crewId, @RequestBody CrewUpdateRequest request) { + CrewUpdateResponse crew = manageCrewUseCase.updateCrew(crewId, request); + return ApiResponse.ok(crew); + } + + @PutMapping("/{crewId}/recruitments/start") + @Schema(description = "크루 λͺ¨μ§‘ μ‹œμž‘") + public ApiResponse startRecruitment(@PathVariable final UUID crewId) { + ChangeRecruitmentStatusResponse recruitment = updateRecruitmentUseCase.startRecruitment(crewId); + return ApiResponse.ok(recruitment); + } + + @PutMapping("/{crewId}/recruitments/stop") + @Schema(description = "크루 λͺ¨μ§‘ 쀑지") + public ApiResponse stopRecruitment(@PathVariable final UUID crewId) { + ChangeRecruitmentStatusResponse recruitment = updateRecruitmentUseCase.stopRecruitment(crewId); + return ApiResponse.ok(recruitment); + } + + @PostMapping("/{crewId}/demands") + @Schema(description = "크루 μ°Έμ—¬ μš”μ²­") + public ApiResponse createDemand( + @PathVariable final UUID crewId, + @RequestBody CreateDemandRequest request) { + CreateDemandResponse demand = manageDemandUseCase.createDemand(crewId, request); + return ApiResponse.created(demand); } @GetMapping 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 a88f942..13e7a09 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,10 +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.infra.util.PaginationUtils.checkEndPage; - import com.querydsl.core.types.ExpressionUtils; import com.querydsl.core.types.Order; import com.querydsl.core.types.OrderSpecifier; @@ -17,13 +12,19 @@ import com.retrip.crew.application.in.response.CrewListResponse; import com.retrip.crew.application.out.repository.CrewQueryRepository; import com.retrip.crew.domain.entity.QCrewMember; -import java.util.List; 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; + @Repository @RequiredArgsConstructor public class CrewQuerydslRepository implements CrewQueryRepository { @@ -43,7 +44,7 @@ public Slice getCrews(Pageable pageable, String keyword) { select(subCrewMember.count()) .from(subCrewMember) .where(subCrewMember.crew.id.eq(crew.id)) ,MemberCountAlias), - crew.maxMembers.as("maxMemberCount") + crew.recruitment.maxMembers.as("maxMemberCount") ) ) .from(crew) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f9bb91b..cb6bbcd 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -19,6 +19,7 @@ spring: logging: level: org.hibernate.type.descriptor.sql: trace + org.hibernate.orm.jdbc.bind: TRACE org.springframework.web.client.RestTemplate: DEBUG springdoc: diff --git a/src/test/java/com/retrip/crew/CrewApplicationTests.java b/src/test/java/com/retrip/crew/CrewApplicationTests.java index 7ecfcc6..f5ca28b 100644 --- a/src/test/java/com/retrip/crew/CrewApplicationTests.java +++ b/src/test/java/com/retrip/crew/CrewApplicationTests.java @@ -1,23 +1,8 @@ package com.retrip.crew; -import com.retrip.crew.domain.entity.Crew; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThatCode; - class CrewApplicationTests { - @DisplayName("제λͺ©μ„ μž…λ ₯ν•΄ 크루λ₯Ό 생성할 수 μžˆλ‹€.") @Test - void create() { - assertThatCode(() -> Crew.create( - "인천 크루 λͺ¨μ§‘", - "인천 크루원을 λͺ¨μ§‘ν•©λ‹ˆλ‹€.\n κ°€μž…λ‚˜μ΄: 20~29μ‚΄ \n κΈˆμ§€ 사항\n - μ—°μ•  κΈˆμ§€\n - λ§Œλ‚¨ 당일 μ·¨μ†Œ κΈˆμ§€\n - 사적 연락 κΈˆμ§€", - 4, - UUID.randomUUID() - )).doesNotThrowAnyException(); - } - + void test() {} } diff --git a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java index 994ac86..a58c76a 100644 --- a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -1,34 +1,38 @@ package com.retrip.crew.application.in; +import com.retrip.crew.application.in.request.CreateDemandRequest; import com.retrip.crew.application.in.request.CrewCreateRequest; import com.retrip.crew.application.in.request.CrewOrder; -import com.retrip.crew.application.in.response.CrewCreateResponse; -import com.retrip.crew.application.in.response.CrewDetailResponse; -import com.retrip.crew.application.in.response.CrewListResponse; -import com.retrip.crew.common.BaseTest; +import com.retrip.crew.application.in.request.CrewUpdateRequest; +import com.retrip.crew.application.in.response.*; +import com.retrip.crew.common.ServiceTest; +import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.entity.CrewMemberRole; +import com.retrip.crew.domain.entity.Demand; +import com.retrip.crew.domain.exception.common.IllegalStateException; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; -import java.util.List; -import java.util.UUID; import org.junit.jupiter.api.Test; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import static com.retrip.crew.common.fixture.CrewFixture.createCrew; +import java.util.List; +import java.util.UUID; + +import static com.retrip.crew.common.fixture.CrewFixture.createCrewRequest; import static com.retrip.crew.common.fixture.CrewFixture.createMultipleCrews; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; -class CrewServiceTest extends BaseTest { - +class CrewServiceTest extends ServiceTest { @Test void 크루λ₯Ό_생성_ν•œλ‹€() { //given - CrewCreateRequest request = createCrew( - μ •μˆ˜_ID, + CrewCreateRequest request = createCrewRequest( + MEMBER_ID, "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", - 5 + 100 ); //when @@ -39,12 +43,74 @@ class CrewServiceTest extends BaseTest { assertThat(response.leaderId()).isNotNull(); } + @Test + void 크루의_제λͺ©_μ„€λͺ…_μ΅œλŒ€_μΈμ›μˆ˜λ₯Ό_μˆ˜μ •ν•œλ‹€() { + // given + Crew crew = crewRepository.save(Crew.create( + "μ†μ΄ˆ 크루원 ꡬ함", + "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", + 100, + MEMBER_ID + )); + CrewUpdateRequest request = new CrewUpdateRequest( + "강릉 크루원 ꡬ함", + "강릉 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", + 200 + ); + + // when + CrewUpdateResponse response = crewService.updateCrew(crew.getId(), request); + + // then + assertAll( + () -> assertThat(response.title()).isEqualTo("강릉 크루원 ꡬ함"), + () -> assertThat(response.maxMembers()).isEqualTo(200) + ); + } + + @Test + void 크루_μ°Έμ—¬_μš”μ²­μ„_μƒμ„±ν•œλ‹€() { + Crew crew = crewRepository.save(Crew.create( + "μ†μ΄ˆ 크루원 ꡬ함", + "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", + 100, + MEMBER_ID + )); + CreateDemandRequest request = new CreateDemandRequest(MEMBER_ID); + + CreateDemandResponse response = crewService.createDemand(crew.getId(), request); + + List demands = crew.getRecruitment().getDemands(); + assertAll( + () -> assertThat(demands.size()).isEqualTo(1), + () -> assertThat(response.memberId()).isEqualTo(demands.get(0).getMemberId()) + ); + } + + @Test + void 이미_μš”μ²­ν•œ_μ‚¬μš©μžλŠ”_λ‹€μ‹œ_크루에_μš”μ²­ν• _수_μ—†λ‹€() { + Crew crew = Crew.create( + "μ†μ΄ˆ 크루원 ꡬ함", + "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", + 100, + MEMBER_ID + ); + crew.demand(MEMBER_ID); + crew.demand(UUID.randomUUID()); + crew.demand(UUID.randomUUID()); + Crew save = crewRepository.save(crew); + CreateDemandRequest request = new CreateDemandRequest(MEMBER_ID); + + assertThatThrownBy(() -> crewService.createDemand(save.getId(), request)) + .isExactlyInstanceOf(IllegalStateException.class); + } + @Test void 크루λ₯Ό_검색_및_μ •λ ¬_ν•„ν„°λ§ν•˜μ—¬_μ‘°νšŒν•œλ‹€(){ //given List requests = createMultipleCrews( 10, - μ •μˆ˜_ID, + MEMBER_ID, "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 5 @@ -72,8 +138,8 @@ class CrewServiceTest extends BaseTest { @Test void 크루_상세λ₯Ό_μ‘°νšŒν•œλ‹€(){ //given - CrewCreateRequest request = createCrew( - μ •μˆ˜_ID, + CrewCreateRequest request = createCrewRequest( + MEMBER_ID, "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 5 @@ -86,10 +152,10 @@ class CrewServiceTest extends BaseTest { //then assertAll( () -> assertThat(response.id()).isEqualTo(crewId), - () -> assertThat(response.leaderId()).isEqualTo(μ •μˆ˜_ID), + () -> assertThat(response.leaderId()).isEqualTo(MEMBER_ID), () -> assertThat(response.members().size()).isEqualTo(1), () -> assertThat(response.members().getFirst().roleCode()).isEqualTo(CrewMemberRole.LEADER.getCode()), - () -> assertThat(response.members().getFirst().memberId()).isEqualTo(μ •μˆ˜_ID) + () -> assertThat(response.members().getFirst().memberId()).isEqualTo(MEMBER_ID) ); } } diff --git a/src/test/java/com/retrip/crew/common/BaseTest.java b/src/test/java/com/retrip/crew/common/ServiceTest.java similarity index 73% rename from src/test/java/com/retrip/crew/common/BaseTest.java rename to src/test/java/com/retrip/crew/common/ServiceTest.java index 582349d..a6452c3 100644 --- a/src/test/java/com/retrip/crew/common/BaseTest.java +++ b/src/test/java/com/retrip/crew/common/ServiceTest.java @@ -5,17 +5,18 @@ import com.retrip.crew.application.out.repository.CrewQueryRepository; import com.retrip.crew.application.out.repository.CrewRepository; import com.retrip.crew.common.config.QuerydslConfig; -import java.util.UUID; 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 class BaseTest { +public class ServiceTest { @Autowired protected CrewRepository crewRepository; @@ -28,11 +29,7 @@ public class BaseTest { protected CrewService crewService; - protected UUID μ •μˆ˜_ID = UUID.fromString("13c8ab91-76bc-4f70-93e9-89f1a65dc640"); - protected UUID 홍석_ID = UUID.fromString("bf97d20b-d1f7-46a9-8362-11b9fa02d67d"); - protected UUID μ€€ν˜Έ_ID = UUID.fromString("8b9b67fd-1d88-4b30-bfea-cd8f89fc10d9"); - protected UUID μ§€μˆ˜_ID = UUID.fromString("de3b60d2-5672-464d-8769-bf5c9de5eaff"); - protected UUID ν˜μ§„_ID = UUID.fromString("42880aaf-4b97-4b0c-8a8a-72df4bb592f6"); + protected UUID MEMBER_ID = UUID.fromString("13c8ab91-76bc-4f70-93e9-89f1a65dc640"); @BeforeEach void setUp() { diff --git a/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java b/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java index 2ed3b6a..34ee232 100644 --- a/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java +++ b/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java @@ -8,7 +8,7 @@ public abstract class CrewFixture { - public static CrewCreateRequest createCrew(UUID memberId, String title, String description, int maxMembers) { + public static CrewCreateRequest createCrewRequest(UUID memberId, String title, String description, int maxMembers) { return new CrewCreateRequest( memberId, title, @@ -22,7 +22,7 @@ public static List createMultipleCrews(int count, UUID member .mapToObj(i -> { String title = baseTitle + " " + (i + 1); String description = baseDescription + " " + (i + 1); - return createCrew(memberId, title, description, maxMembers); + return createCrewRequest(memberId, title, description, maxMembers); }) .collect(Collectors.toList()); } diff --git a/src/test/java/com/retrip/crew/domain/entity/CrewTest.java b/src/test/java/com/retrip/crew/domain/entity/CrewTest.java index 187e9a7..cb9ed60 100644 --- a/src/test/java/com/retrip/crew/domain/entity/CrewTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/CrewTest.java @@ -1,20 +1,95 @@ package com.retrip.crew.domain.entity; -import org.junit.jupiter.api.DisplayName; +import com.retrip.crew.domain.exception.common.IllegalStateException; +import com.retrip.crew.domain.vo.RecruitmentStatus; import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; +import java.util.List; import java.util.UUID; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.*; class CrewTest { - @Test - public void 크루_생성_ν…ŒμŠ€νŠΈ() { + void 크루λ₯Ό_μƒμ„±ν•œλ‹€() { assertThatCode(() -> Crew.create( "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", - 4, - UUID.randomUUID())).doesNotThrowAnyException(); + 100, + UUID.randomUUID()) + ).doesNotThrowAnyException(); + } + + @Test + void 크루가_μƒμ„±λ˜λ©΄_λͺ¨μ§‘을_μ‹œμž‘ν•œλ‹€() { + // given, when + Crew crew = Crew.create( + "μ†μ΄ˆ 크루원 ꡬ함", + "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", + 100, + UUID.randomUUID()); + + // then + assertThat(crew.getRecruitment().getStatus()).isEqualTo(RecruitmentStatus.RECRUITING); + } + + @Test + void 크루_λͺ¨μ§‘을_μ€‘μ§€ν•œλ‹€() { + // given + Crew crew = Crew.create( + "μ†μ΄ˆ 크루원 ꡬ함", + "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", + 100, + UUID.randomUUID()); + + // when + crew.stopRecruitment(); + + // then + assertThat(crew.getRecruitment().getStatus()).isEqualTo(RecruitmentStatus.STOPPED); + } + + @Test + void 크루_λͺ¨μ§‘을_μ‹œμž‘ν•œλ‹€() { + // given + Crew crew = Crew.create( + "μ†μ΄ˆ 크루원 ꡬ함", + "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", + 100, + UUID.randomUUID()); + crew.stopRecruitment(); + + // when + crew.startRecruitment(); + + // then + assertThat(crew.getRecruitment().getStatus()).isEqualTo(RecruitmentStatus.RECRUITING); + } + + @Test + void 크루_멀버가_μ΅œλŒ€_λͺ¨μ§‘_인원과_κ°™μœΌλ©΄_λͺ¨μ§‘을_μ‹œμž‘ν• _수_μ—†λ‹€() { + // given + Crew crew = Crew.create( + "μ†μ΄ˆ 크루원 ꡬ함", + "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", + 5, + UUID.randomUUID()); + List crewMemberList = List.of( + new CrewMember(crew, UUID.randomUUID(), CrewMemberRole.LEADER), + new CrewMember(crew, UUID.randomUUID(), CrewMemberRole.PARTICIPANT), + new CrewMember(crew, UUID.randomUUID(), CrewMemberRole.PARTICIPANT), + new CrewMember(crew, UUID.randomUUID(), CrewMemberRole.PARTICIPANT), + new CrewMember(crew, UUID.randomUUID(), CrewMemberRole.PARTICIPANT) + ); + crew.stopRecruitment(); + CrewMembers crewMembers = new CrewMembers(crew, UUID.randomUUID()); + ReflectionTestUtils.setField(crewMembers, "values", crewMemberList); + ReflectionTestUtils.setField(crew, "crewMembers", crewMembers); + + // when, then + assertThatThrownBy(crew::startRecruitment) + .isExactlyInstanceOf(IllegalStateException.class); + assertThat(crew.getRecruitment().getStatus()).isEqualTo(RecruitmentStatus.STOPPED); } } diff --git a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java new file mode 100644 index 0000000..298ebf5 --- /dev/null +++ b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java @@ -0,0 +1,13 @@ +package com.retrip.crew.domain.entity; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; + +class RecruitmentTest { + @Test + void λͺ¨μ§‘을_μƒμ„±ν•œλ‹€() { + assertThatCode(() -> new Recruitment(100)) + .doesNotThrowAnyException(); + } +} From 203f27f12d1f5ab5c9fe420b6b9475caff36f6da Mon Sep 17 00:00:00 2001 From: MonChest Date: Sat, 29 Mar 2025 01:11:30 +0900 Subject: [PATCH 07/11] =?UTF-8?q?feat:=ED=81=AC=EB=A3=A8=20=EC=B0=B8?= =?UTF-8?q?=EC=97=AC=20=EC=9A=94=EC=B2=AD=20=EC=A7=88=EB=AC=B8=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/request/CrewCreateRequest.java | 23 ++++++++-- .../in/response/CrewCreateResponse.java | 9 +++- .../in/usecase/ManageCrewUseCase.java | 2 + .../com/retrip/crew/domain/entity/Crew.java | 17 ++++++-- .../retrip/crew/domain/entity/Question.java | 42 +++++++++++++++++++ .../retrip/crew/domain/entity/Questions.java | 42 +++++++++++++++++++ .../crew/domain/vo/QuestionContent.java | 32 ++++++++++++++ .../crew/application/in/CrewServiceTest.java | 22 +++++++--- .../crew/common/fixture/CrewFixture.java | 9 ++-- .../retrip/crew/domain/entity/CrewTest.java | 17 +++++--- 10 files changed, 192 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/retrip/crew/domain/entity/Question.java create mode 100644 src/main/java/com/retrip/crew/domain/entity/Questions.java create mode 100644 src/main/java/com/retrip/crew/domain/vo/QuestionContent.java diff --git a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java b/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java index 145bd3a..15c72dc 100644 --- a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java +++ b/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Size; +import java.util.List; import java.util.UUID; @Schema(description = "크루 생성 Request") @@ -21,10 +22,26 @@ public record CrewCreateRequest( @Schema(description = "크루 μ΅œλŒ€ μΈμ›μˆ˜") @Size(min = 5, max = 1000) - int maxMembers -){ + int maxMembers, + + + @Schema(description = "크루 μ°Έμ—¬ 질문 리슀트") + CreateCrewQuestionRequest questions // 질문 리슀트 μΆ”κ°€ +) { + @Schema(description = "크루 μ°Έμ—¬ μš”μ²­ 질문 등둝 Request") + public record CreateCrewQuestionRequest( + + @Schema(description = "크루 μ°Έμ—¬ μš”μ²­ 질문") + @Size(min = 0, max = 5) + List crewQuestions + ) { + + public List toList() { + return crewQuestions.stream().filter(q -> !q.isEmpty()).toList(); + } + } public Crew to(UUID leader) { - return Crew.create(this.title, this.description, this.maxMembers, leader); + return Crew.create(this.title, this.description, this.maxMembers, leader, questions.toList()); } } diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java b/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java index ad231f9..695a042 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java @@ -4,6 +4,7 @@ import com.retrip.crew.domain.vo.RecruitmentStatus; import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; import java.util.UUID; @Schema(description = "크루 생성 Response") @@ -24,7 +25,10 @@ public record CrewCreateResponse( int maxMembers, @Schema(description = "λͺ¨μ§‘ μƒνƒœ") - RecruitmentStatus status + RecruitmentStatus status, + + @Schema(description = "크루 μ°Έμ—¬ 질문 리슀트") + List questions ) { @@ -35,7 +39,8 @@ public static CrewCreateResponse of(Crew crew) { crew.getDescription(), crew.getLeader().getId(), crew.getRecruitment().getMaxMembers(), - crew.getRecruitment().getStatus() + crew.getRecruitment().getStatus(), + crew.getQuestions() ); } } 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..dd28fc9 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 @@ -11,4 +11,6 @@ public interface ManageCrewUseCase { CrewCreateResponse createCrew(CrewCreateRequest request); CrewUpdateResponse updateCrew(UUID crewId, CrewUpdateRequest request); + + } 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..3d300ee 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Crew.java +++ b/src/main/java/com/retrip/crew/domain/entity/Crew.java @@ -7,6 +7,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.List; import java.util.UUID; @Entity @@ -26,6 +27,9 @@ public class Crew extends BaseEntity { @Embedded private CrewDescription description; + @Embedded + private Questions questions; + @Embedded private CrewMembers crewMembers; @@ -41,7 +45,7 @@ public class Crew extends BaseEntity { @Embedded private Recruitment recruitment; - private Crew(String name, String description, int maxMembers, UUID leader) { + private Crew(String name, String description, int maxMembers, UUID leader,List questions) { this.id = UUID.randomUUID(); this.title = new CrewTitle(name); this.description = new CrewDescription(description); @@ -50,10 +54,11 @@ private Crew(String name, String description, int maxMembers, UUID leader) { this.posts = new Posts(); this.announcements = new Announcements(); this.introductions = new Introductions(); + this.questions = new Questions(questions, this); } - public static Crew create(String title, String description, int maxMembers, UUID leader) { - return new Crew(title, description, maxMembers, leader); + public static Crew create(String title, String description, int maxMembers, UUID leader, List questions) { + return new Crew(title, description, maxMembers, leader, questions); } public CrewMember getLeader() { @@ -82,4 +87,10 @@ public String getDescription(){ return description.getValue(); } + public List getQuestions() { + return questions.getValues().stream() + .map(question -> question.getContent().getValue()) + .toList(); + } + } diff --git a/src/main/java/com/retrip/crew/domain/entity/Question.java b/src/main/java/com/retrip/crew/domain/entity/Question.java new file mode 100644 index 0000000..2ac3f21 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/entity/Question.java @@ -0,0 +1,42 @@ +package com.retrip.crew.domain.entity; + +import com.retrip.crew.domain.vo.QuestionContent; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.UUID; + + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +@Getter +public class Question { + + @Id + @Column(columnDefinition = "varbinary(16)") + private UUID id; + + @Embedded + private QuestionContent content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn( + name = "crew_id", + nullable = false, + columnDefinition = "varbinary(16)", + foreignKey = @ForeignKey(name = "fk_question_to_crew") + ) + private Crew crew; + + public Question(String content, Crew crew) { + this.id = UUID.randomUUID(); + this.content = new QuestionContent(content); + this.crew = crew; + } + + public static Question create(String content, Crew crew) { + return new Question(content, crew); + } +} diff --git a/src/main/java/com/retrip/crew/domain/entity/Questions.java b/src/main/java/com/retrip/crew/domain/entity/Questions.java new file mode 100644 index 0000000..fb9760e --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/entity/Questions.java @@ -0,0 +1,42 @@ +package com.retrip.crew.domain.entity; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.OneToMany; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.BatchSize; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +@Embeddable +public class Questions { + + @BatchSize(size = 100) + @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) + private List values = new ArrayList<>(); + + public Questions(List values, Crew crew) { + valid(values); + this.values = values.stream().map(s -> Question.create(s, crew)).toList(); + } + + private void valid(List values) { + // 질문의 개수 체크 (μ΅œμ†Œ 1개, μ΅œλŒ€ 10개) + if (values.size() > 10) { + throw new IllegalArgumentException("μ§ˆλ¬Έμ€ 10개 μ΄ν•˜λ‘œ μž…λ ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€."); + } + + // 각 질문의 길이 체크 (μ΅œμ†Œ 1자 이상, μ΅œλŒ€ 200자 μ΄ν•˜) + for (String question : values) { + if (question.length() < 1 || question.length() > 200) { + throw new IllegalArgumentException("각 μ§ˆλ¬Έμ€ μ΅œμ†Œ 50자 이상, 200자 μ΄ν•˜λ‘œ μž…λ ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€."); + } + } + } + +} diff --git a/src/main/java/com/retrip/crew/domain/vo/QuestionContent.java b/src/main/java/com/retrip/crew/domain/vo/QuestionContent.java new file mode 100644 index 0000000..311e82c --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/vo/QuestionContent.java @@ -0,0 +1,32 @@ +package com.retrip.crew.domain.vo; + +import com.retrip.crew.domain.exception.common.InvalidValueException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@EqualsAndHashCode +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +public class QuestionContent { + + private static final int CONTENT_LENGTH_LIMIT = 100; + + @Column(name = "content", nullable = false, length = CONTENT_LENGTH_LIMIT) + private final String value; + + public QuestionContent(String value) { + validate(value); + this.value = value; + } + + private void validate(String value) { + if (value.length() > CONTENT_LENGTH_LIMIT) { + throw new InvalidValueException("질문 λ‚΄μš©μ€ " + CONTENT_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + } +} diff --git a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java index a58c76a..754a3a5 100644 --- a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -32,7 +32,9 @@ class CrewServiceTest extends ServiceTest { MEMBER_ID, "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", - 100 + 100, + List.of("질문") + ); //when @@ -50,7 +52,8 @@ class CrewServiceTest extends ServiceTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - MEMBER_ID + MEMBER_ID, + List.of("질문1","질문2") )); CrewUpdateRequest request = new CrewUpdateRequest( "강릉 크루원 ꡬ함", @@ -74,7 +77,8 @@ class CrewServiceTest extends ServiceTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - MEMBER_ID + MEMBER_ID, + List.of("질문1") )); CreateDemandRequest request = new CreateDemandRequest(MEMBER_ID); @@ -93,7 +97,8 @@ class CrewServiceTest extends ServiceTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - MEMBER_ID + MEMBER_ID, + List.of("질문1") ); crew.demand(MEMBER_ID); crew.demand(UUID.randomUUID()); @@ -113,7 +118,8 @@ class CrewServiceTest extends ServiceTest { MEMBER_ID, "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", - 5 + 5, + List.of("질문1") ); requests.forEach(request -> { CrewCreateResponse response = crewService.createCrew(request); @@ -142,7 +148,8 @@ class CrewServiceTest extends ServiceTest { MEMBER_ID, "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", - 5 + 5, + List.of("질문1") ); UUID crewId = crewService.createCrew(request).id(); @@ -158,4 +165,7 @@ class CrewServiceTest extends ServiceTest { () -> assertThat(response.members().getFirst().memberId()).isEqualTo(MEMBER_ID) ); } + + + } diff --git a/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java b/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java index 34ee232..3ca16e1 100644 --- a/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java +++ b/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java @@ -8,21 +8,22 @@ public abstract class CrewFixture { - public static CrewCreateRequest createCrewRequest(UUID memberId, String title, String description, int maxMembers) { + public static CrewCreateRequest createCrewRequest(UUID memberId, String title, String description, int maxMembers,List questions) { return new CrewCreateRequest( memberId, title, description, - maxMembers + maxMembers, + new CrewCreateRequest.CreateCrewQuestionRequest(questions) ); } - public static List createMultipleCrews(int count, UUID memberId, String baseTitle, String baseDescription, int maxMembers) { + public static List createMultipleCrews(int count, UUID memberId, String baseTitle, String baseDescription, int maxMembers,List questions) { return IntStream.range(0, count) .mapToObj(i -> { String title = baseTitle + " " + (i + 1); String description = baseDescription + " " + (i + 1); - return createCrewRequest(memberId, title, description, maxMembers); + return createCrewRequest(memberId, title, description, maxMembers,questions); }) .collect(Collectors.toList()); } diff --git a/src/test/java/com/retrip/crew/domain/entity/CrewTest.java b/src/test/java/com/retrip/crew/domain/entity/CrewTest.java index cb9ed60..f40f03a 100644 --- a/src/test/java/com/retrip/crew/domain/entity/CrewTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/CrewTest.java @@ -17,7 +17,9 @@ class CrewTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - UUID.randomUUID()) + UUID.randomUUID(), + List.of("질문1")) + ).doesNotThrowAnyException(); } @@ -28,7 +30,9 @@ class CrewTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - UUID.randomUUID()); + UUID.randomUUID(), + List.of("질문1") + ); // then assertThat(crew.getRecruitment().getStatus()).isEqualTo(RecruitmentStatus.RECRUITING); @@ -41,7 +45,8 @@ class CrewTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - UUID.randomUUID()); + UUID.randomUUID(), + List.of("질문1")); // when crew.stopRecruitment(); @@ -57,7 +62,8 @@ class CrewTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - UUID.randomUUID()); + UUID.randomUUID(), + List.of("질문1")); crew.stopRecruitment(); // when @@ -74,7 +80,8 @@ class CrewTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 5, - UUID.randomUUID()); + UUID.randomUUID(), + List.of("질문1")); List crewMemberList = List.of( new CrewMember(crew, UUID.randomUUID(), CrewMemberRole.LEADER), new CrewMember(crew, UUID.randomUUID(), CrewMemberRole.PARTICIPANT), From 9fd3d831e4da277736d06ff6b2046ede4554d774 Mon Sep 17 00:00:00 2001 From: TueBack Date: Sun, 13 Apr 2025 19:15:39 +0900 Subject: [PATCH 08/11] =?UTF-8?q?feat:=ED=81=AC=EB=A3=A8=20=EC=B0=B8?= =?UTF-8?q?=EC=97=AC=20=EC=9A=94=EC=B2=AD=20=EC=A7=88=EB=AC=B8=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/request/CrewCreateRequest.java | 4 +- .../ChangeRecruitmentStatusResponse.java | 2 +- .../in/response/CrewCreateResponse.java | 2 +- .../com/retrip/crew/domain/entity/Crew.java | 17 +++---- .../retrip/crew/domain/entity/Questions.java | 42 ----------------- .../crew/domain/entity/Recruitment.java | 21 ++++++++- ...Question.java => RecruitmentQuestion.java} | 8 ++-- .../domain/entity/RecruitmentQuestions.java | 45 ++++++++++++++++++ .../domain/exception/common/ErrorCode.java | 8 +++- .../crew/domain/vo/QuestionContent.java | 10 ++-- .../retrip/crew/domain/entity/CrewTest.java | 2 + .../crew/domain/entity/RecruitmentTest.java | 46 ++++++++++++++++++- 12 files changed, 138 insertions(+), 69 deletions(-) delete mode 100644 src/main/java/com/retrip/crew/domain/entity/Questions.java rename src/main/java/com/retrip/crew/domain/entity/{Question.java => RecruitmentQuestion.java} (76%) create mode 100644 src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java diff --git a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java b/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java index 15c72dc..5995183 100644 --- a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java +++ b/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java @@ -26,9 +26,9 @@ public record CrewCreateRequest( @Schema(description = "크루 μ°Έμ—¬ 질문 리슀트") - CreateCrewQuestionRequest questions // 질문 리슀트 μΆ”κ°€ + CreateCrewQuestionRequest questions ) { - @Schema(description = "크루 μ°Έμ—¬ μš”μ²­ 질문 등둝 Request") + @Schema(description = "크루 μ°Έμ—¬ μš”μ²­ 질문 Request") public record CreateCrewQuestionRequest( @Schema(description = "크루 μ°Έμ—¬ μš”μ²­ 질문") diff --git a/src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java b/src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java index 0a56b73..96dab2e 100644 --- a/src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java @@ -6,7 +6,7 @@ import java.util.UUID; -public record ChangeRecruitmentStatusResponse( +public record ChangeRecruitmentStatusResponse( @Schema(description = "크루 ID") UUID id, diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java b/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java index 695a042..4246459 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java @@ -40,7 +40,7 @@ public static CrewCreateResponse of(Crew crew) { crew.getLeader().getId(), crew.getRecruitment().getMaxMembers(), crew.getRecruitment().getStatus(), - crew.getQuestions() + crew.getRecruitment().getRecruitmentQuestions() ); } } 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 3d300ee..75cdacf 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Crew.java +++ b/src/main/java/com/retrip/crew/domain/entity/Crew.java @@ -27,8 +27,7 @@ public class Crew extends BaseEntity { @Embedded private CrewDescription description; - @Embedded - private Questions questions; + @Embedded private CrewMembers crewMembers; @@ -49,16 +48,18 @@ private Crew(String name, String description, int maxMembers, UUID leader,List questions) { - return new Crew(title, description, maxMembers, leader, questions); + Crew crew =new Crew(title, description, maxMembers, leader, questions); + crew.recruitment = Recruitment.of(maxMembers, questions, crew); + return crew; + } public CrewMember getLeader() { @@ -87,10 +88,6 @@ public String getDescription(){ return description.getValue(); } - public List getQuestions() { - return questions.getValues().stream() - .map(question -> question.getContent().getValue()) - .toList(); - } + } diff --git a/src/main/java/com/retrip/crew/domain/entity/Questions.java b/src/main/java/com/retrip/crew/domain/entity/Questions.java deleted file mode 100644 index fb9760e..0000000 --- a/src/main/java/com/retrip/crew/domain/entity/Questions.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.retrip.crew.domain.entity; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.Embeddable; -import jakarta.persistence.OneToMany; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.BatchSize; - -import java.util.ArrayList; -import java.util.List; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) -@Embeddable -public class Questions { - - @BatchSize(size = 100) - @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) - private List values = new ArrayList<>(); - - public Questions(List values, Crew crew) { - valid(values); - this.values = values.stream().map(s -> Question.create(s, crew)).toList(); - } - - private void valid(List values) { - // 질문의 개수 체크 (μ΅œμ†Œ 1개, μ΅œλŒ€ 10개) - if (values.size() > 10) { - throw new IllegalArgumentException("μ§ˆλ¬Έμ€ 10개 μ΄ν•˜λ‘œ μž…λ ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€."); - } - - // 각 질문의 길이 체크 (μ΅œμ†Œ 1자 이상, μ΅œλŒ€ 200자 μ΄ν•˜) - for (String question : values) { - if (question.length() < 1 || question.length() > 200) { - throw new IllegalArgumentException("각 μ§ˆλ¬Έμ€ μ΅œμ†Œ 50자 이상, 200자 μ΄ν•˜λ‘œ μž…λ ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€."); - } - } - } - -} diff --git a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java index e445277..5474abb 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java +++ b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java @@ -4,6 +4,7 @@ import com.retrip.crew.domain.vo.RecruitmentStatus; import jakarta.persistence.CascadeType; import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; import jakarta.persistence.OneToMany; import lombok.AccessLevel; import lombok.Getter; @@ -26,11 +27,22 @@ public class Recruitment { @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) private List demands = new ArrayList<>(); - public Recruitment(int maxMembers) { + @Embedded + private RecruitmentQuestions recruitmentQuestions; + + private Recruitment(int maxMembers, RecruitmentQuestions recruitmentQuestions) { this.maxMembers = maxMembers; this.status = RECRUITING; + this.recruitmentQuestions = recruitmentQuestions; + } + + public static Recruitment of(int maxMembers, List questions, Crew crew) { + RecruitmentQuestions recruitmentQuestions = new RecruitmentQuestions(questions, crew); + return new Recruitment(maxMembers, recruitmentQuestions); } + + public void start(int membersSize) { if (isRecruitmentComplete(membersSize)) { stop(); @@ -65,4 +77,11 @@ private boolean isDuplicate(UUID memberId) { .map(Demand::getMemberId) .anyMatch(id -> id.equals(memberId)); } + + public List getRecruitmentQuestions() { + return recruitmentQuestions.getValues().stream() + .map(question -> question.getContent().getValue()) + .toList(); + } + } diff --git a/src/main/java/com/retrip/crew/domain/entity/Question.java b/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestion.java similarity index 76% rename from src/main/java/com/retrip/crew/domain/entity/Question.java rename to src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestion.java index 2ac3f21..3d9ab32 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Question.java +++ b/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestion.java @@ -12,7 +12,7 @@ @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) @Getter -public class Question { +public class RecruitmentQuestion { @Id @Column(columnDefinition = "varbinary(16)") @@ -30,13 +30,13 @@ public class Question { ) private Crew crew; - public Question(String content, Crew crew) { + public RecruitmentQuestion(UUID id,String content, Crew crew) { this.id = UUID.randomUUID(); this.content = new QuestionContent(content); this.crew = crew; } - public static Question create(String content, Crew crew) { - return new Question(content, crew); + public static RecruitmentQuestion create(String content, Crew crew) { + return new RecruitmentQuestion(UUID.randomUUID(), content, crew); } } diff --git a/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java b/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java new file mode 100644 index 0000000..87b97d6 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java @@ -0,0 +1,45 @@ +package com.retrip.crew.domain.entity; + +import com.retrip.crew.domain.exception.common.ErrorCode; +import com.retrip.crew.domain.exception.common.InvalidValueException; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.OneToMany; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.BatchSize; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) +@Embeddable +public class RecruitmentQuestions { + + private static final int MAX_QUESTIONS = 10; + + @BatchSize(size = 100) + @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) + private List values = new ArrayList<>(); + + public RecruitmentQuestions(List values, Crew crew) { + validateQuestions(values); + this.values = values.stream() + .map(questionText -> RecruitmentQuestion.create(questionText, crew)) + .toList(); + } + + private void validateQuestions(List values) { + if (values.size() > MAX_QUESTIONS) { + throw new InvalidValueException(ErrorCode.INVALID_QUESTION_COUNT); + } + } + + public List getContents() { + return values.stream() + .map(q -> q.getContent().getValue()) + .toList(); + } +} 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..9b5e41b 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,8 +13,12 @@ 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", "크루 μ—”ν‹°ν‹°λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."), + INVALID_QUESTION_COUNT(BAD_REQUEST, "Crew-002", "μ§ˆλ¬Έμ€ μ΅œλŒ€ 10κ°œκΉŒμ§€ μž…λ ₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€."), + INVALID_QUESTION_LENGTH(BAD_REQUEST, "Crew-003", "각 μ§ˆλ¬Έμ€ 10자 이상 200자 μ΄ν•˜λ‘œ μž…λ ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€.") + + ; private final HttpStatus status; private final String code; diff --git a/src/main/java/com/retrip/crew/domain/vo/QuestionContent.java b/src/main/java/com/retrip/crew/domain/vo/QuestionContent.java index 311e82c..8017f73 100644 --- a/src/main/java/com/retrip/crew/domain/vo/QuestionContent.java +++ b/src/main/java/com/retrip/crew/domain/vo/QuestionContent.java @@ -1,5 +1,6 @@ package com.retrip.crew.domain.vo; +import com.retrip.crew.domain.exception.common.ErrorCode; import com.retrip.crew.domain.exception.common.InvalidValueException; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; @@ -14,9 +15,10 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) public class QuestionContent { - private static final int CONTENT_LENGTH_LIMIT = 100; + private static final int MIN_LENGTH = 10; + private static final int MAX_LENGTH = 100; - @Column(name = "content", nullable = false, length = CONTENT_LENGTH_LIMIT) + @Column(name = "content", nullable = false, length = MAX_LENGTH) private final String value; public QuestionContent(String value) { @@ -25,8 +27,8 @@ public QuestionContent(String value) { } private void validate(String value) { - if (value.length() > CONTENT_LENGTH_LIMIT) { - throw new InvalidValueException("질문 λ‚΄μš©μ€ " + CONTENT_LENGTH_LIMIT + "자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + if (value == null || value.trim().length() < MIN_LENGTH || value.length() > MAX_LENGTH) { + throw new InvalidValueException(ErrorCode.INVALID_QUESTION_LENGTH); } } } diff --git a/src/test/java/com/retrip/crew/domain/entity/CrewTest.java b/src/test/java/com/retrip/crew/domain/entity/CrewTest.java index f40f03a..543e1f2 100644 --- a/src/test/java/com/retrip/crew/domain/entity/CrewTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/CrewTest.java @@ -99,4 +99,6 @@ class CrewTest { .isExactlyInstanceOf(IllegalStateException.class); assertThat(crew.getRecruitment().getStatus()).isEqualTo(RecruitmentStatus.STOPPED); } + + } diff --git a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java index 298ebf5..76b404e 100644 --- a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java @@ -1,13 +1,55 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.domain.exception.common.InvalidValueException; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.*; class RecruitmentTest { + @Test void λͺ¨μ§‘을_μƒμ„±ν•œλ‹€() { - assertThatCode(() -> new Recruitment(100)) + assertThatCode(() -> new Recruitment(10, List.of("질문1", "질문2"))) .doesNotThrowAnyException(); } + + @Test + void 질문이_10개λ₯Ό_λ„˜μœΌλ©΄_μ˜ˆμ™Έκ°€_λ°œμƒν•œλ‹€() { + + List questions = List.of( + "1","2","3","4","5","6","7","8","9","10","11" + ); + + // when, then + assertThatThrownBy(() -> new Recruitment(10, questions)) + .isInstanceOf(InvalidValueException.class) + .hasMessageContaining("질문"); + } + + @Test + void 질문_λ‚΄μš©μ΄_100자λ₯Ό_λ„˜μœΌλ©΄_μ˜ˆμ™Έκ°€_λ°œμƒν•œλ‹€() { + String longQuestion = "a".repeat(101); + List questions = List.of(longQuestion); + + assertThatThrownBy(() -> new Recruitment(10, questions)) + .isInstanceOf(InvalidValueException.class) + .hasMessageContaining("질문 λ‚΄μš©μ€ 100자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + + + @Test + void 질문_λͺ©λ‘μ„_λ¬Έμžμ—΄λ‘œ_μ‘°νšŒν• _수_μžˆλ‹€() { + // given + List questions = List.of("κ°€μž… μ΄μœ λŠ”?", "이전에 크루 ν™œλ™μ„ ν•΄λ³Έ κ²½ν—˜μ΄ μžˆλ‚˜μš”?"); + Recruitment recruitment = new Recruitment(10, questions); + + // when + List result = recruitment.getRecruitmentQuestions(); + + // then + assertThat(result).containsExactlyElementsOf(questions); + } } From 5f8dd9aaa3daa2d6bb7bba917ad899dd9780e114 Mon Sep 17 00:00:00 2001 From: TueBack Date: Sun, 1 Jun 2025 20:22:26 +0900 Subject: [PATCH 09/11] =?UTF-8?q?[FEAT]-recruitment=EB=A1=9C=20=EC=97=B0?= =?UTF-8?q?=EA=B4=80=EA=B4=80=EA=B3=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/retrip/crew/domain/entity/RecruitmentQuestions.java | 2 +- .../java/com/retrip/crew/domain/entity/RecruitmentTest.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java b/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java index 87b97d6..c07dda1 100644 --- a/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java +++ b/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java @@ -21,7 +21,7 @@ public class RecruitmentQuestions { private static final int MAX_QUESTIONS = 10; @BatchSize(size = 100) - @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) +c @OneToMany(mappedBy = "recruitment", cascade = CascadeType.ALL, orphanRemoval = true) private List values = new ArrayList<>(); public RecruitmentQuestions(List values, Crew crew) { diff --git a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java index 76b404e..609158a 100644 --- a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java @@ -12,7 +12,9 @@ class RecruitmentTest { @Test void λͺ¨μ§‘을_μƒμ„±ν•œλ‹€() { - assertThatCode(() -> new Recruitment(10, List.of("질문1", "질문2"))) + Crew crew = new Crew(); + RecruitmentQuestions questions = new RecruitmentQuestions(questions, crew); + assertThatCode(() -> new Recruitment(10, questions)) .doesNotThrowAnyException(); } From 8cbeac448ce3f022fbe358cb2a2f901556a7e9a7 Mon Sep 17 00:00:00 2001 From: TueBack Date: Fri, 13 Jun 2025 18:03:27 +0900 Subject: [PATCH 10/11] =?UTF-8?q?=ED=81=AC=EB=A3=A8=20=EC=B0=B8=EC=97=AC?= =?UTF-8?q?=20=EC=9A=94=EC=B2=AD=20=EC=A7=88=EB=AC=B8=20=EB=93=B1=EB=A1=9D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/request/CrewCreateRequest.java | 0 .../in/request/crew/CrewCreateRequest.java | 9 ++++-- .../com/retrip/crew/domain/entity/Crew.java | 9 +++--- .../crew/domain/entity/Introduction.java | 20 ++++++------ .../crew/domain/entity/Introductions.java | 4 ++- .../crew/domain/entity/Recruitment.java | 15 +++++---- .../domain/entity/RecruitmentQuestion.java | 9 ++---- .../domain/entity/RecruitmentQuestions.java | 2 +- src/main/resources/application.yml | 10 +++--- .../crew/application/in/CrewServiceTest.java | 2 ++ .../crew/common/fixture/CrewFixture.java | 31 +++++++++++++------ 11 files changed, 66 insertions(+), 45 deletions(-) delete mode 100644 src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java diff --git a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java b/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/retrip/crew/application/in/request/crew/CrewCreateRequest.java b/src/main/java/com/retrip/crew/application/in/request/crew/CrewCreateRequest.java index ce499e2..4a8770d 100644 --- a/src/main/java/com/retrip/crew/application/in/request/crew/CrewCreateRequest.java +++ b/src/main/java/com/retrip/crew/application/in/request/crew/CrewCreateRequest.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Size; +import java.util.List; import java.util.UUID; @Schema(description = "크루 생성 Request") @@ -21,10 +22,14 @@ public record CrewCreateRequest( @Schema(description = "크루 μ΅œλŒ€ μΈμ›μˆ˜") @Size(min = 5, max = 1000) - int maxMembers + int maxMembers, + + @Schema(description = "크루 질문 λͺ©λ‘") + @Size(min = 1, max = 5) + List questions ){ public Crew to(UUID leader) { - return Crew.create(this.title, this.description, this.maxMembers, leader); + return Crew.create(this.title, this.description, this.maxMembers, leader,this.questions); } } 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 dca0285..c7c7bc4 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Crew.java +++ b/src/main/java/com/retrip/crew/domain/entity/Crew.java @@ -13,6 +13,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.List; import java.util.UUID; @Entity @@ -39,19 +40,19 @@ public class Crew extends BaseEntity { @Embedded private Recruitment recruitment; - private Crew(String name, String description, int maxMembers, UUID leader) { + private Crew(String name, String description, int maxMembers, UUID leader, List questions) { this.id = UUID.randomUUID(); this.title = new CrewTitle(name); this.description = new CrewDescription(description); - this.recruitment = new Recruitment(maxMembers); + this.recruitment = Recruitment.of(maxMembers, questions, this); this.crewMembers = new CrewMembers(this, leader); this.posts = new Posts(); this.announcements = new Announcements(); this.introductions = new Introductions(); } - public static Crew create(String title, String description, int maxMembers, UUID leader) { - return new Crew(title, description, maxMembers, leader); + public static Crew create(String title, String description, int maxMembers, UUID leader, List questions) { + return new Crew(title, description, maxMembers, leader, questions); } public CrewMember getLeader() { diff --git a/src/main/java/com/retrip/crew/domain/entity/Introduction.java b/src/main/java/com/retrip/crew/domain/entity/Introduction.java index 0a10ce2..bc653d1 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Introduction.java +++ b/src/main/java/com/retrip/crew/domain/entity/Introduction.java @@ -11,7 +11,9 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; + import java.util.UUID; + import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -41,7 +43,7 @@ public class Introduction extends BaseEntity { ) private Crew crew; - public Introduction(UUID loginMemberId, String title, String content, Crew crew){ + public Introduction(UUID loginMemberId, String title, String content, Crew crew) { this.id = UUID.randomUUID(); this.memberId = loginMemberId; this.title = new IntroductionTitle(title); @@ -53,11 +55,11 @@ public static Introduction create(UUID loginMemberId, String title, String conte return new Introduction(loginMemberId, title, content, crew); } - public String getTitle(){ + public String getTitle() { return title.getValue(); } - public String getContent(){ + public String getContent() { return content.getValue(); } @@ -67,23 +69,23 @@ public void update(IntroductionTitle introductionTitle, IntroductionContent intr this.content = introductionContent; } - public void validateIntroductionOwner(UUID loginMemberId){ - if(!isOwner(loginMemberId)){ + public void validateIntroductionOwner(UUID loginMemberId) { + if (!isOwner(loginMemberId)) { throw new InvalidAccessException(); } } - public void validateIntroductionOwnerAndLeader(UUID loginMemberId){ - if(!isOwner(loginMemberId) || !isLeader(loginMemberId)){ + public void validateIntroductionOwnerAndLeader(UUID loginMemberId) { + if (!isOwner(loginMemberId) || !isLeader(loginMemberId)) { throw new InvalidAccessException(); } } - public boolean isOwner(UUID loginMemberId){ + public boolean isOwner(UUID loginMemberId) { return this.memberId.equals(loginMemberId); } - public boolean isLeader(UUID loginMemberId){ + public boolean isLeader(UUID loginMemberId) { return this.getCrew().getLeader().getMemberId().equals(loginMemberId); } diff --git a/src/main/java/com/retrip/crew/domain/entity/Introductions.java b/src/main/java/com/retrip/crew/domain/entity/Introductions.java index 7128b03..9ec712d 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Introductions.java +++ b/src/main/java/com/retrip/crew/domain/entity/Introductions.java @@ -3,8 +3,10 @@ import jakarta.persistence.CascadeType; import jakarta.persistence.Embeddable; import jakarta.persistence.OneToMany; + import java.util.ArrayList; import java.util.List; + import lombok.Getter; @Getter @@ -22,7 +24,7 @@ private List createEmptyValues() { return new ArrayList<>(); } - public void addIntroduction(Introduction introduction){ + public void addIntroduction(Introduction introduction) { this.values.add(introduction); } diff --git a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java index 5025d77..c85e661 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java +++ b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java @@ -1,15 +1,18 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.domain.exception.DuplicateDemandException; +import com.retrip.crew.domain.exception.IllegalDemandStateException; +import com.retrip.crew.domain.exception.UnableToStartRecruitmentException; +import com.retrip.crew.domain.exception.common.InvalidValueException; import com.retrip.crew.domain.vo.RecruitmentStatus; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Embeddable; -import jakarta.persistence.OneToMany; +import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; import static com.retrip.crew.domain.vo.RecruitmentStatus.RECRUITING; @@ -30,8 +33,7 @@ public class Recruitment { @Embedded private RecruitmentQuestions recruitmentQuestions; - private Recruitment(int maxMembers, RecruitmentQuestions recruitmentQuestions) { - public Recruitment(int maxMembers) { + public Recruitment(int maxMembers, RecruitmentQuestions recruitmentQuestions) { this.maxMembers = maxMembers; this.status = RECRUITING; this.recruitmentQuestions = recruitmentQuestions; @@ -43,7 +45,6 @@ public static Recruitment of(int maxMembers, List questions, Crew crew) } - public void start(int membersSize) { if (isRecruitmentComplete(membersSize)) { stop(); @@ -110,6 +111,8 @@ public void rejectDemand(Demand demand) { Demand find = findDemand(demand); throwIfNotPending(find); find.reject(); + } + public List getRecruitmentQuestions() { return recruitmentQuestions.getValues().stream() .map(question -> question.getContent().getValue()) diff --git a/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestion.java b/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestion.java index 3d9ab32..d8d3927 100644 --- a/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestion.java +++ b/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestion.java @@ -22,15 +22,10 @@ public class RecruitmentQuestion { private QuestionContent content; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn( - name = "crew_id", - nullable = false, - columnDefinition = "varbinary(16)", - foreignKey = @ForeignKey(name = "fk_question_to_crew") - ) + @JoinColumn(name = "crew_id", nullable = false, columnDefinition = "varbinary(16)", foreignKey = @ForeignKey(name = "fk_question_to_crew")) private Crew crew; - public RecruitmentQuestion(UUID id,String content, Crew crew) { + public RecruitmentQuestion(UUID id, String content, Crew crew) { this.id = UUID.randomUUID(); this.content = new QuestionContent(content); this.crew = crew; diff --git a/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java b/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java index c07dda1..87b97d6 100644 --- a/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java +++ b/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java @@ -21,7 +21,7 @@ public class RecruitmentQuestions { private static final int MAX_QUESTIONS = 10; @BatchSize(size = 100) -c @OneToMany(mappedBy = "recruitment", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) private List values = new ArrayList<>(); public RecruitmentQuestions(List values, Crew crew) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a7d14fc..cb6bbcd 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,15 +1,13 @@ spring: application: name: crew - #JPA +#JPA datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://retripstudydb.cv4sss0i0mk8.ap-southeast-2.rds.amazonaws.com:3306/crew?serverTimezone=Asia/Seoul&characterEncoding=UTF-8 - username: ${DB_USERNAME} - password: ${DB_PASSWORD} + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false;MODE=MySQL jpa: hibernate: - ddl-auto: update + ddl-auto: create-drop properties: hibernate: show_sql: true diff --git a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java index 5e2f186..2765cbb 100644 --- a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -1,5 +1,7 @@ package com.retrip.crew.application.in; +import com.retrip.crew.application.in.request.crew.CrewCreateRequest; +import com.retrip.crew.application.in.response.crew.CrewCreateResponse; import com.retrip.crew.common.ServiceTest; import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.entity.CrewMemberRole; diff --git a/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java b/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java index ae1be17..1199e8a 100644 --- a/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java +++ b/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java @@ -23,23 +23,32 @@ public abstract class CrewFixture { public static final UUID μ§€μˆ˜_ID = UUID.fromString("de3b60d2-5672-464d-8769-bf5c9de5eaff"); public static final UUID ν˜μ§„_ID = UUID.fromString("42880aaf-4b97-4b0c-8a8a-72df4bb592f6"); - public static CrewCreateRequest createCrewRequest(UUID memberId, String title, String description, int maxMembers,List questions) { + public static List createDefaultQuestions() { + return List.of( + "크루에 μ§€μ›ν•œ μ΄μœ λŠ” λ¬΄μ—‡μΈκ°€μš”?", + "μ–΄λ–€ ν™œλ™μ„ κΈ°λŒ€ν•˜κ³  μžˆλ‚˜μš”?", + "μžμ‹ μ„ ν•œ λ¬Έμž₯으둜 ν‘œν˜„ν•œλ‹€λ©΄?", + "ν¬λ£¨μ—μ„œ μ–΄λ–€ 역할을 ν•˜κ³  μ‹Άλ‚˜μš”?", + "μΆ”κ°€λ‘œ ν•˜κ³  싢은 말이 μžˆλ‚˜μš”?" + ); + } + + public static CrewCreateRequest createCrewRequest(UUID memberId, String title, String description, int maxMembers, List questions) { return new CrewCreateRequest( memberId, title, description, - maxMembers maxMembers, - new CrewCreateRequest.CreateCrewQuestionRequest(questions) + questions ); } - public static List createMultipleCrews(int count, UUID memberId, String baseTitle, String baseDescription, int maxMembers,List questions) { + public static List createMultipleCrews(int count, UUID memberId, String baseTitle, String baseDescription, int maxMembers, List questions) { return IntStream.range(0, count) .mapToObj(i -> { String title = baseTitle + " " + (i + 1); String description = baseDescription + " " + (i + 1); - return createCrewRequest(memberId, title, description, maxMembers,questions); + return createCrewRequest(memberId, title, description, maxMembers, questions); }) .collect(Collectors.toList()); } @@ -49,7 +58,8 @@ public static Crew createCrew(UUID leaderId) { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - leaderId); + leaderId, + createDefaultQuestions()); } public static Crew createCrewWithMutableMembers(UUID leaderId) { @@ -57,7 +67,8 @@ public static Crew createCrewWithMutableMembers(UUID leaderId) { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - leaderId); + leaderId, + createDefaultQuestions()); List crewMemberList = new ArrayList<>(5); crewMemberList.add(new CrewMember(crew, leaderId, CrewMemberRole.LEADER)); crewMemberList.add(new CrewMember(crew, μ •μˆ˜_ID, CrewMemberRole.PARTICIPANT)); @@ -76,7 +87,8 @@ public static Crew createCrewWithMembers(UUID leaderId) { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - leaderId); + leaderId, + createDefaultQuestions()); List crewMemberList = List.of( new CrewMember(crew, leaderId, CrewMemberRole.LEADER), new CrewMember(crew, μ •μˆ˜_ID, CrewMemberRole.PARTICIPANT), @@ -96,7 +108,8 @@ public static Crew createCrewWithMembers(UUID leaderId, int maxMembers) { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", maxMembers, - leaderId); + leaderId, + createDefaultQuestions()); List crewMemberList = List.of( new CrewMember(crew, leaderId, CrewMemberRole.LEADER), new CrewMember(crew, μ •μˆ˜_ID, CrewMemberRole.PARTICIPANT), From 8ee22b4e26cc5ecec571d30741af2bc157e93f1a Mon Sep 17 00:00:00 2001 From: TueBack Date: Sun, 15 Jun 2025 13:34:42 +0900 Subject: [PATCH 11/11] =?UTF-8?q?=ED=81=AC=EB=A3=A8=20=EC=B0=B8=EC=97=AC?= =?UTF-8?q?=20=EC=9A=94=EC=B2=AD=20=EC=A7=88=EB=AC=B8=20=EB=93=B1=EB=A1=9D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/RecruitmentQuestionService.java | 87 +++++++++++++++++++ .../CreateRecruitmentQuestionRequest.java | 17 ++++ .../UpdateRecruitmentQuestionRequest.java | 17 ++++ .../CreateRecruitmentQuestionResponse.java | 19 ++++ .../response/RecruitmentQuestionResponse.java | 10 +++ .../UpdateRecruitmentQuestionResponse.java | 19 ++++ .../GetRecruitmentQuestionUseCase.java | 9 ++ .../ManageRecruitmentQuestionUseCase.java | 14 +++ .../out/repository/CrewQueryRepository.java | 1 + .../RecruitmentQuestionQueryRepository.java | 12 +++ .../crew/domain/entity/Recruitment.java | 24 ++--- .../domain/entity/RecruitmentQuestion.java | 29 +++++-- .../domain/entity/RecruitmentQuestions.java | 52 +++++++++-- .../QuestionDeleteFailedException.java | 10 +++ .../exception/QuestionNotFoundException.java | 10 +++ .../QuestionUpdateFailedException.java | 12 +++ .../domain/exception/common/ErrorCode.java | 6 +- .../common/InvalidValueException.java | 32 +++---- .../mysql/query/CrewQuerydslRepository.java | 11 +++ ...RecruitmentQuestionQuerydslRepository.java | 50 +++++++++++ .../crew/application/in/CrewServiceTest.java | 40 ++++++--- .../crew/application/in/PostServiceTest.java | 14 ++- .../retrip/crew/domain/entity/PostTest.java | 13 ++- .../crew/domain/entity/RecruitmentTest.java | 2 +- 24 files changed, 450 insertions(+), 60 deletions(-) create mode 100644 src/main/java/com/retrip/crew/application/in/RecruitmentQuestionService.java create mode 100644 src/main/java/com/retrip/crew/application/in/request/CreateRecruitmentQuestionRequest.java create mode 100644 src/main/java/com/retrip/crew/application/in/request/UpdateRecruitmentQuestionRequest.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/CreateRecruitmentQuestionResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/RecruitmentQuestionResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/UpdateRecruitmentQuestionResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/usecase/GetRecruitmentQuestionUseCase.java create mode 100644 src/main/java/com/retrip/crew/application/in/usecase/ManageRecruitmentQuestionUseCase.java create mode 100644 src/main/java/com/retrip/crew/application/out/repository/RecruitmentQuestionQueryRepository.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/QuestionDeleteFailedException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/QuestionNotFoundException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/QuestionUpdateFailedException.java create mode 100644 src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/RecruitmentQuestionQuerydslRepository.java diff --git a/src/main/java/com/retrip/crew/application/in/RecruitmentQuestionService.java b/src/main/java/com/retrip/crew/application/in/RecruitmentQuestionService.java new file mode 100644 index 0000000..91020f9 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/RecruitmentQuestionService.java @@ -0,0 +1,87 @@ +package com.retrip.crew.application.in; + +import com.retrip.crew.application.in.request.CreateRecruitmentQuestionRequest; +import com.retrip.crew.application.in.request.UpdateRecruitmentQuestionRequest; +import com.retrip.crew.application.in.response.CreateRecruitmentQuestionResponse; +import com.retrip.crew.application.in.response.RecruitmentQuestionResponse; +import com.retrip.crew.application.in.response.UpdateRecruitmentQuestionResponse; +import com.retrip.crew.application.in.usecase.GetRecruitmentQuestionUseCase; +import com.retrip.crew.application.in.usecase.ManageRecruitmentQuestionUseCase; +import com.retrip.crew.application.out.repository.CrewMemberQueryRepository; +import com.retrip.crew.application.out.repository.CrewQueryRepository; +import com.retrip.crew.application.out.repository.RecruitmentQuestionQueryRepository; +import com.retrip.crew.domain.entity.Crew; +import com.retrip.crew.domain.entity.CrewMember; +import com.retrip.crew.domain.entity.RecruitmentQuestion; +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.List; +import java.util.UUID; + +@Service +@Transactional +@RequiredArgsConstructor +public class RecruitmentQuestionService implements ManageRecruitmentQuestionUseCase, GetRecruitmentQuestionUseCase { + + private final CrewQueryRepository crewQueryRepository; + private final CrewMemberQueryRepository crewMemberQueryRepository; + private final RecruitmentQuestionQueryRepository recruitmentQuestionQueryRepository; + + @Override + public CreateRecruitmentQuestionResponse createRecruitmentQuestion(UUID crewId, CreateRecruitmentQuestionRequest request) { + Crew crew = crewQueryRepository + .findByIdWithRecruitment(crewId) + .orElseThrow(CrewNotFoundException::new); + + RecruitmentQuestion question = RecruitmentQuestion.create(request.content(), crew); + crew.getRecruitment().addQuestion(question); + + return CreateRecruitmentQuestionResponse.of(question); + } + + @Override + public UpdateRecruitmentQuestionResponse updateRecruitmentQuestion(UUID crewId, UUID questionId, UpdateRecruitmentQuestionRequest request) { + Crew crew = crewQueryRepository + .findByIdWithRecruitment(crewId) + .orElseThrow(CrewNotFoundException::new); + + RecruitmentQuestion question = crew.getRecruitment() + .getRecruitmentQuestions() + .updateQuestion(questionId, request.content(), request.memberId()); + + return UpdateRecruitmentQuestionResponse.of(question); + } + + @Override + public void deleteRecruitmentQuestion(UUID crewId, UUID questionId, UUID memberId) { + Crew crew = crewQueryRepository + .findByIdWithRecruitment(crewId) + .orElseThrow(CrewNotFoundException::new); + + CrewMember crewMember = findCrewMemberByMemberId(crewId, memberId); + +// crew.getRecruitment() +// .getRecruitmentQuestions() +// .deleteQuestion(questionId, crewMember); + } + + private CrewMember findCrewMemberByMemberId(UUID crewId, UUID memberId) { + return crewMemberQueryRepository + .findCrewMemberByMemberId(crewId, memberId) + .orElseThrow(CrewMemberNotFoundException::new); + } + + @Override + @Transactional(readOnly = true) + public List getRecruitmentQuestions(UUID crewId) { + return recruitmentQuestionQueryRepository.findRecruitmentQuestions(crewId); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/request/CreateRecruitmentQuestionRequest.java b/src/main/java/com/retrip/crew/application/in/request/CreateRecruitmentQuestionRequest.java new file mode 100644 index 0000000..e043ba5 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/request/CreateRecruitmentQuestionRequest.java @@ -0,0 +1,17 @@ +package com.retrip.crew.application.in.request; + +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 CreateRecruitmentQuestionRequest( + @Schema(description = "질문 λ‚΄μš©") + @NotNull + @Size(min = 1, max = 100) + String content, + @Schema(description = "μž‘μ„±μž") + @NotNull + UUID memberId +) {} diff --git a/src/main/java/com/retrip/crew/application/in/request/UpdateRecruitmentQuestionRequest.java b/src/main/java/com/retrip/crew/application/in/request/UpdateRecruitmentQuestionRequest.java new file mode 100644 index 0000000..f87599a --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/request/UpdateRecruitmentQuestionRequest.java @@ -0,0 +1,17 @@ +package com.retrip.crew.application.in.request; + +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 UpdateRecruitmentQuestionRequest( + @Schema(description = "질문 λ‚΄μš©") + @NotNull + @Size(min = 1, max = 100) + String content, + @Schema(description = "μˆ˜μ •μž") + @NotNull + UUID memberId +) {} diff --git a/src/main/java/com/retrip/crew/application/in/response/CreateRecruitmentQuestionResponse.java b/src/main/java/com/retrip/crew/application/in/response/CreateRecruitmentQuestionResponse.java new file mode 100644 index 0000000..040337b --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/CreateRecruitmentQuestionResponse.java @@ -0,0 +1,19 @@ +package com.retrip.crew.application.in.response; + +import com.retrip.crew.domain.entity.RecruitmentQuestion; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.UUID; + +@Schema(description = "μ°Έμ—¬μš”μ²­μ§ˆλ¬Έ 생성 Response") +public record CreateRecruitmentQuestionResponse( + @Schema(description = "질문 ID") UUID id, + @Schema(description = "질문 λ‚΄μš©") String content +) { + public static CreateRecruitmentQuestionResponse of(RecruitmentQuestion question) { + return new CreateRecruitmentQuestionResponse( + question.getId(), + question.getContent().getValue() + ); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/response/RecruitmentQuestionResponse.java b/src/main/java/com/retrip/crew/application/in/response/RecruitmentQuestionResponse.java new file mode 100644 index 0000000..4171a56 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/RecruitmentQuestionResponse.java @@ -0,0 +1,10 @@ +package com.retrip.crew.application.in.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.UUID; + +@Schema(description = "μ°Έμ—¬μš”μ²­μ§ˆλ¬Έ Response") +public record RecruitmentQuestionResponse( + @Schema(description = "질문 Id") UUID id, + @Schema(description = "질문 λ‚΄μš©") String content +) {} diff --git a/src/main/java/com/retrip/crew/application/in/response/UpdateRecruitmentQuestionResponse.java b/src/main/java/com/retrip/crew/application/in/response/UpdateRecruitmentQuestionResponse.java new file mode 100644 index 0000000..5d8f9e5 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/UpdateRecruitmentQuestionResponse.java @@ -0,0 +1,19 @@ +package com.retrip.crew.application.in.response; + +import com.retrip.crew.domain.entity.RecruitmentQuestion; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.UUID; + +@Schema(description = "μ°Έμ—¬μš”μ²­μ§ˆλ¬Έ μˆ˜μ • Response") +public record UpdateRecruitmentQuestionResponse( + @Schema(description = "질문 ID") UUID id, + @Schema(description = "질문 λ‚΄μš©") String content +) { + public static UpdateRecruitmentQuestionResponse of(RecruitmentQuestion question) { + return new UpdateRecruitmentQuestionResponse( + question.getId(), + question.getContent().getValue() + ); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/usecase/GetRecruitmentQuestionUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/GetRecruitmentQuestionUseCase.java new file mode 100644 index 0000000..45277d9 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/usecase/GetRecruitmentQuestionUseCase.java @@ -0,0 +1,9 @@ +package com.retrip.crew.application.in.usecase; + +import com.retrip.crew.application.in.response.RecruitmentQuestionResponse; +import java.util.List; +import java.util.UUID; + +public interface GetRecruitmentQuestionUseCase { + List getRecruitmentQuestions(UUID crewId); +} \ No newline at end of file diff --git a/src/main/java/com/retrip/crew/application/in/usecase/ManageRecruitmentQuestionUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManageRecruitmentQuestionUseCase.java new file mode 100644 index 0000000..7a74319 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManageRecruitmentQuestionUseCase.java @@ -0,0 +1,14 @@ +package com.retrip.crew.application.in.usecase; + +import com.retrip.crew.application.in.request.CreateRecruitmentQuestionRequest; +import com.retrip.crew.application.in.request.UpdateRecruitmentQuestionRequest; +import com.retrip.crew.application.in.response.CreateRecruitmentQuestionResponse; +import com.retrip.crew.application.in.response.UpdateRecruitmentQuestionResponse; + +import java.util.UUID; + +public interface ManageRecruitmentQuestionUseCase { + CreateRecruitmentQuestionResponse createRecruitmentQuestion(UUID crewId, CreateRecruitmentQuestionRequest request); + UpdateRecruitmentQuestionResponse updateRecruitmentQuestion(UUID crewId, UUID questionId, UpdateRecruitmentQuestionRequest request); + void deleteRecruitmentQuestion(UUID crewId, UUID questionId, UUID memberId); +} 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 bada9a6..1945997 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 @@ -14,5 +14,6 @@ public interface CrewQueryRepository { Slice getCrews(Pageable pageable, String keyword); Long getCrewCount(String keyword); Optional findByIdWithPosts(UUID id); + Optional findByIdWithRecruitment(UUID crewId); Page findAllContainsMember(Pageable pageable, UUID memberId); } diff --git a/src/main/java/com/retrip/crew/application/out/repository/RecruitmentQuestionQueryRepository.java b/src/main/java/com/retrip/crew/application/out/repository/RecruitmentQuestionQueryRepository.java new file mode 100644 index 0000000..b559073 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/out/repository/RecruitmentQuestionQueryRepository.java @@ -0,0 +1,12 @@ +package com.retrip.crew.application.out.repository; + +import com.retrip.crew.application.in.response.RecruitmentQuestionResponse; +import com.retrip.crew.domain.entity.RecruitmentQuestion; + +import java.util.List; +import java.util.UUID; + +public interface RecruitmentQuestionQueryRepository { + List findRecruitmentQuestions(UUID crewId); + RecruitmentQuestion findByIdOrElseThrow(UUID questionId); +} diff --git a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java index c85e661..5fb8b27 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java +++ b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java @@ -22,6 +22,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) @Embeddable public class Recruitment { + private int maxMembers; @Column(name = "recruitment_status") @@ -33,18 +34,16 @@ public class Recruitment { @Embedded private RecruitmentQuestions recruitmentQuestions; - public Recruitment(int maxMembers, RecruitmentQuestions recruitmentQuestions) { + private Recruitment(int maxMembers, List questions, Crew crew) { this.maxMembers = maxMembers; this.status = RECRUITING; - this.recruitmentQuestions = recruitmentQuestions; + this.recruitmentQuestions = new RecruitmentQuestions(questions, crew); } public static Recruitment of(int maxMembers, List questions, Crew crew) { - RecruitmentQuestions recruitmentQuestions = new RecruitmentQuestions(questions, crew); - return new Recruitment(maxMembers, recruitmentQuestions); + return new Recruitment(maxMembers, questions, crew); } - public void start(int membersSize) { if (isRecruitmentComplete(membersSize)) { stop(); @@ -65,15 +64,21 @@ public void updateMaxMembers(int maxMembers) { this.maxMembers = maxMembers; } + public void addQuestion(RecruitmentQuestion question) { + this.recruitmentQuestions.add(question); + } + public Demand addDemand(UUID memberId, Crew crew) { if (isDuplicate(memberId)) { throw new DuplicateDemandException(); } + Optional reDemand = findReDemand(memberId); if (reDemand.isPresent()) { reDemand.get().restore(); return reDemand.get(); } + Demand demand = new Demand(memberId, crew); demands.add(demand); return demand; @@ -91,8 +96,7 @@ private Optional findReDemand(UUID memberId) { } private static boolean isReDemand(UUID memberId, Demand demand) { - return demand.isEqualTo(memberId) - && demand.isCanceled(); + return demand.isEqualTo(memberId) && demand.isCanceled(); } public void cancelDemand(Demand demand) { @@ -113,11 +117,7 @@ public void rejectDemand(Demand demand) { find.reject(); } - public List getRecruitmentQuestions() { - return recruitmentQuestions.getValues().stream() - .map(question -> question.getContent().getValue()) - .toList(); - } + private static void throwIfNotPending(Demand find) { if (find.isNotPending()) { diff --git a/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestion.java b/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestion.java index d8d3927..6a43645 100644 --- a/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestion.java +++ b/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestion.java @@ -1,5 +1,6 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.domain.exception.QuestionUpdateFailedException; import com.retrip.crew.domain.vo.QuestionContent; import jakarta.persistence.*; import lombok.AccessLevel; @@ -8,11 +9,10 @@ import java.util.UUID; - @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) @Getter -public class RecruitmentQuestion { +public class RecruitmentQuestion extends BaseEntity { @Id @Column(columnDefinition = "varbinary(16)") @@ -22,16 +22,35 @@ public class RecruitmentQuestion { private QuestionContent content; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "crew_id", nullable = false, columnDefinition = "varbinary(16)", foreignKey = @ForeignKey(name = "fk_question_to_crew")) + @JoinColumn( + name = "crew_id", + nullable = false, + columnDefinition = "varbinary(16)", + foreignKey = @ForeignKey(name = "fk_question_to_crew")) private Crew crew; - public RecruitmentQuestion(UUID id, String content, Crew crew) { + private RecruitmentQuestion(String content, Crew crew) { this.id = UUID.randomUUID(); this.content = new QuestionContent(content); this.crew = crew; } public static RecruitmentQuestion create(String content, Crew crew) { - return new RecruitmentQuestion(UUID.randomUUID(), content, crew); + return new RecruitmentQuestion(content, crew); + } + + public void update(String content, UUID memberId) { +// isUpdatable(this.getCreatedBy(), memberId); + this.content = new QuestionContent(content); } + + private void isUpdatable(UUID createBy, UUID updateBy) { + if (!createBy.equals(updateBy)) { + throw new QuestionUpdateFailedException(); + } + } + +// public boolean isDeletable(CrewMember crewMember) { +// return crewMember.isLeader() || crewMember.isCreatedBy(this.getCreatedBy()); +// } } diff --git a/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java b/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java index 87b97d6..840829f 100644 --- a/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java +++ b/src/main/java/com/retrip/crew/domain/entity/RecruitmentQuestions.java @@ -1,5 +1,7 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.domain.exception.QuestionDeleteFailedException; +import com.retrip.crew.domain.exception.QuestionNotFoundException; import com.retrip.crew.domain.exception.common.ErrorCode; import com.retrip.crew.domain.exception.common.InvalidValueException; import jakarta.persistence.CascadeType; @@ -12,9 +14,9 @@ import java.util.ArrayList; import java.util.List; +import java.util.UUID; @Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) @Embeddable public class RecruitmentQuestions { @@ -24,11 +26,21 @@ public class RecruitmentQuestions { @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) private List values = new ArrayList<>(); - public RecruitmentQuestions(List values, Crew crew) { - validateQuestions(values); - this.values = values.stream() - .map(questionText -> RecruitmentQuestion.create(questionText, crew)) - .toList(); + public RecruitmentQuestions() { + this.values = createEmptyValues(); + } + + public RecruitmentQuestions(List questions, Crew crew) { + validateQuestions(questions); + this.values = createEmptyValues(); + questions.forEach(questionText -> { + RecruitmentQuestion question = RecruitmentQuestion.create(questionText, crew); + this.values.add(question); + }); + } + + private List createEmptyValues() { + return new ArrayList<>(); } private void validateQuestions(List values) { @@ -37,6 +49,34 @@ private void validateQuestions(List values) { } } + public RecruitmentQuestion updateQuestion(UUID questionId, String content, UUID memberId) { + RecruitmentQuestion question = findQuestion(questionId); + question.update(content, memberId); + return question; + } + +// public void deleteQuestion(UUID questionId, CrewMember crewMember) { +// RecruitmentQuestion question = findQuestion(questionId); +// if (!question.isDeletable(crewMember)) { +// throw new QuestionDeleteFailedException(); +// } +// this.values.remove(question); +// } + + private RecruitmentQuestion findQuestion(UUID questionId) { + return this.values.stream() + .filter(q -> q.getId().equals(questionId)) + .findAny() + .orElseThrow(QuestionNotFoundException::new); + } + + public void add(RecruitmentQuestion question) { + if (this.values.size() >= MAX_QUESTIONS) { + throw new InvalidValueException(ErrorCode.INVALID_QUESTION_COUNT); + } + this.values.add(question); + } + public List getContents() { return values.stream() .map(q -> q.getContent().getValue()) diff --git a/src/main/java/com/retrip/crew/domain/exception/QuestionDeleteFailedException.java b/src/main/java/com/retrip/crew/domain/exception/QuestionDeleteFailedException.java new file mode 100644 index 0000000..bd5b721 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/QuestionDeleteFailedException.java @@ -0,0 +1,10 @@ +package com.retrip.crew.domain.exception; + +import com.retrip.crew.domain.exception.common.BusinessException; +import com.retrip.crew.domain.exception.common.ErrorCode; + +public class QuestionDeleteFailedException extends BusinessException { + public QuestionDeleteFailedException() { + super(ErrorCode.QUESTION_DELETE_FAIL); + } +} \ No newline at end of file diff --git a/src/main/java/com/retrip/crew/domain/exception/QuestionNotFoundException.java b/src/main/java/com/retrip/crew/domain/exception/QuestionNotFoundException.java new file mode 100644 index 0000000..6a6c808 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/QuestionNotFoundException.java @@ -0,0 +1,10 @@ +package com.retrip.crew.domain.exception; + +import com.retrip.crew.domain.exception.common.BusinessException; +import com.retrip.crew.domain.exception.common.ErrorCode; + +public class QuestionNotFoundException extends BusinessException { + public QuestionNotFoundException() { + super(ErrorCode.QUESTION_NOT_FOUND); + } +} \ No newline at end of file diff --git a/src/main/java/com/retrip/crew/domain/exception/QuestionUpdateFailedException.java b/src/main/java/com/retrip/crew/domain/exception/QuestionUpdateFailedException.java new file mode 100644 index 0000000..f7fe24e --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/QuestionUpdateFailedException.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 QuestionUpdateFailedException extends BusinessException { + private static final ErrorCode errorCode = ErrorCode.QUESTION_UPDATE_FAIL; + + public QuestionUpdateFailedException() { + 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 e5210e4..79fca3f 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 @@ -26,7 +26,11 @@ public enum ErrorCode { CREW_MEMBER_NOT_FOUND(BAD_REQUEST, "Crew-008", "크루원 멀버 μ—”ν‹°ν‹°λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.."), POST_NOT_FOUND(BAD_REQUEST, "Crew-009", "자유 κ²Œμ‹œκΈ€ μ—”ν‹°ν‹°λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."), POST_UPDATE_FAIL(FORBIDDEN, "Crew-010", "자유 κ²Œμ‹œκΈ€μ„ μˆ˜μ •ν•  κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€."), - POST_DELETE_FAIL(FORBIDDEN, "Crew-011", "자유 κ²Œμ‹œκΈ€μ„ μ‚­μ œν•  κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€.") + POST_DELETE_FAIL(FORBIDDEN, "Crew-011", "자유 κ²Œμ‹œκΈ€μ„ μ‚­μ œν•  κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€."), + QUESTION_UPDATE_FAIL(FORBIDDEN, "Crew-012", "질문 μˆ˜μ • κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€."), + QUESTION_DELETE_FAIL(FORBIDDEN, "Crew-013", "질문 μ‚­μ œ κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€."), + QUESTION_NOT_FOUND(BAD_REQUEST, "Crew-014", "μ§ˆλ¬Έμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."); + ; private final HttpStatus status; diff --git a/src/main/java/com/retrip/crew/domain/exception/common/InvalidValueException.java b/src/main/java/com/retrip/crew/domain/exception/common/InvalidValueException.java index c001268..22496e7 100644 --- a/src/main/java/com/retrip/crew/domain/exception/common/InvalidValueException.java +++ b/src/main/java/com/retrip/crew/domain/exception/common/InvalidValueException.java @@ -1,21 +1,21 @@ -package com.retrip.crew.domain.exception.common; + package com.retrip.crew.domain.exception.common; -public class InvalidValueException extends BusinessException { - private static final ErrorCode errorCode = ErrorCode.INVALID_INPUT_VALUE; + public class InvalidValueException extends BusinessException { + private static final ErrorCode errorCode = ErrorCode.INVALID_INPUT_VALUE; - public InvalidValueException() { - super(errorCode); - } - public InvalidValueException(ErrorCode errorCode) { - super(errorCode); - } + public InvalidValueException() { + super(errorCode); + } + public InvalidValueException(ErrorCode errorCode) { + super(errorCode); + } - public InvalidValueException(String message) { - super(errorCode, message); - } + public InvalidValueException(String message) { + super(errorCode, message); + } - public InvalidValueException(ErrorCode errorCode, String message) { - super(errorCode, message); - } + public InvalidValueException(ErrorCode errorCode, String message) { + super(errorCode, message); + } -} + } 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 22e8696..3dc9a6b 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 @@ -156,4 +156,15 @@ private OrderSpecifier createCrewOrderSpecifier(Pageable pageable) { } return new OrderSpecifier<>(Order.ASC, crew.createdAt); } + + @Override + public Optional findByIdWithRecruitment(UUID crewId) { + return Optional.ofNullable( + query.selectFrom(crew) + .distinct() + .leftJoin(crew.recruitment.recruitmentQuestions.values).fetchJoin() + .where(crew.id.eq(crewId)) + .fetchOne() + ); + } } diff --git a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/RecruitmentQuestionQuerydslRepository.java b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/RecruitmentQuestionQuerydslRepository.java new file mode 100644 index 0000000..6e2dda9 --- /dev/null +++ b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/RecruitmentQuestionQuerydslRepository.java @@ -0,0 +1,50 @@ +package com.retrip.crew.infra.adapter.out.persistence.mysql.query; + +import static com.retrip.crew.domain.entity.QRecruitmentQuestion.recruitmentQuestion; + +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.retrip.crew.application.in.response.RecruitmentQuestionResponse; +import com.retrip.crew.application.out.repository.RecruitmentQuestionQueryRepository; +import com.retrip.crew.domain.entity.RecruitmentQuestion; +import com.retrip.crew.domain.exception.QuestionNotFoundException; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.UUID; + +@Repository +@RequiredArgsConstructor +public class RecruitmentQuestionQuerydslRepository implements RecruitmentQuestionQueryRepository { + private final JPAQueryFactory query; + + @Override + public List findRecruitmentQuestions(UUID crewId) { + return query + .select(Projections.constructor( + RecruitmentQuestionResponse.class, + recruitmentQuestion.id, + recruitmentQuestion.content.value, + recruitmentQuestion.createdAt)) + .from(recruitmentQuestion) + .where(recruitmentQuestion.crew.id.eq(crewId)) + .orderBy(recruitmentQuestion.createdAt.desc()) + .fetch(); + } + + @Override + public RecruitmentQuestion findByIdOrElseThrow(UUID questionId) { + RecruitmentQuestion question = query + .selectFrom(recruitmentQuestion) + .where(recruitmentQuestion.id.eq(questionId)) + .fetchOne(); + + if (question == null) { + throw new QuestionNotFoundException(); + } + + return question; + } +} \ No newline at end of file diff --git a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java index 2765cbb..df96586 100644 --- a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -1,10 +1,23 @@ package com.retrip.crew.application.in; +import com.retrip.crew.application.in.request.IntroductionCreateRequest; +import com.retrip.crew.application.in.request.IntroductionDeleteRequest; +import com.retrip.crew.application.in.request.IntroductionUpdateRequest; import com.retrip.crew.application.in.request.crew.CrewCreateRequest; +import com.retrip.crew.application.in.request.crew.CrewOrder; +import com.retrip.crew.application.in.request.crew.CrewUpdateRequest; +import com.retrip.crew.application.in.request.demand.CreateDemandRequest; +import com.retrip.crew.application.in.response.IntroductionCreateResponse; +import com.retrip.crew.application.in.response.IntroductionDetailResponse; import com.retrip.crew.application.in.response.crew.CrewCreateResponse; +import com.retrip.crew.application.in.response.crew.CrewDetailResponse; +import com.retrip.crew.application.in.response.crew.CrewListResponse; +import com.retrip.crew.application.in.response.crew.CrewUpdateResponse; +import com.retrip.crew.application.in.response.demand.CreateDemandResponse; import com.retrip.crew.common.ServiceTest; import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.entity.CrewMemberRole; +import com.retrip.crew.domain.entity.Demand; import com.retrip.crew.domain.entity.Introduction; import com.retrip.crew.domain.exception.common.InvalidAccessException; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; @@ -17,6 +30,7 @@ import static com.retrip.crew.common.fixture.CrewFixture.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -29,7 +43,7 @@ class CrewServiceTest extends ServiceTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - List.of("질문") + createDefaultQuestions() ); @@ -48,8 +62,7 @@ class CrewServiceTest extends ServiceTest { CrewUpdateRequest request = new CrewUpdateRequest( "강릉 크루원 ꡬ함", "강릉 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", - 200, - List.of("질문1","질문2") + 200 ); // when @@ -69,11 +82,11 @@ class CrewServiceTest extends ServiceTest { "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, MEMBER_ID, - List.of("질문1") + createDefaultQuestions() )); CreateDemandRequest request = new CreateDemandRequest(MEMBER_ID); - CreateDemandResponse response = crewService.createDemand(crew.getId(), request); + CreateDemandResponse response = demandService.createDemand(crew.getId(), request); List demands = crew.getRecruitment().getDemands(); assertAll( @@ -97,7 +110,7 @@ class CrewServiceTest extends ServiceTest { Crew save = crewRepository.save(crew); CreateDemandRequest request = new CreateDemandRequest(MEMBER_ID); - assertThatThrownBy(() -> crewService.createDemand(save.getId(), request)) + assertThatThrownBy(() -> demandService.createDemand(save.getId(), request)) .isExactlyInstanceOf(IllegalStateException.class); } @@ -164,7 +177,8 @@ class CrewServiceTest extends ServiceTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - MEMBER_ID + MEMBER_ID, + createDefaultQuestions() )); IntroductionCreateRequest request = new IntroductionCreateRequest(MEMBER_ID, "μ •μˆ˜μ˜ μžκΈ°μ†Œκ°œ!", "μ•ˆλ…•ν•˜μ„Έμš”!"); @@ -182,7 +196,8 @@ class CrewServiceTest extends ServiceTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - MEMBER_ID + MEMBER_ID, + createDefaultQuestions() ); Introduction introduction = Introduction.create( MEMBER_ID, @@ -211,7 +226,8 @@ class CrewServiceTest extends ServiceTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - MEMBER_ID + MEMBER_ID, + createDefaultQuestions() ); Introduction introduction = Introduction.create( MEMBER_ID, @@ -236,7 +252,8 @@ class CrewServiceTest extends ServiceTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - MEMBER_ID + MEMBER_ID, + createDefaultQuestions() ); Introduction introduction = Introduction.create( MEMBER_ID, @@ -263,7 +280,8 @@ class CrewServiceTest extends ServiceTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - MEMBER_ID + MEMBER_ID, + createDefaultQuestions() ); Introduction introduction = Introduction.create( MEMBER_ID, 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 185ee67..4b767a6 100644 --- a/src/test/java/com/retrip/crew/application/in/PostServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/PostServiceTest.java @@ -13,6 +13,7 @@ import org.junit.jupiter.api.Test; import org.springframework.data.domain.PageRequest; +import static com.retrip.crew.common.fixture.CrewFixture.createDefaultQuestions; 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; @@ -30,7 +31,9 @@ class PostServiceTest extends BasePostServiceTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - MEMBER_ID)); + MEMBER_ID, + createDefaultQuestions() + )); postService.createPost( crew.getId(), PostFixture.createRequest("μ†μ΄ˆ μ—¬ν–‰ μ–΄λ””κ°€ μ’‹μ•„μš”?", "μΉœκ΅¬λ“€κ³Ό μ†μ΄ˆ μ—¬ν–‰ κ°€λ €κ³  ν•©λ‹ˆλ‹€. μ†μ΄ˆ μ—¬ν–‰μ§€ μΆ”μ²œν•΄ μ£Όμ„Έμš”~")); @@ -67,7 +70,8 @@ class PostServiceTest extends BasePostServiceTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - MEMBER_ID)); + MEMBER_ID, + createDefaultQuestions())); crewRepository.save(crew); CreatePostRequest request = @@ -91,7 +95,8 @@ class PostServiceTest extends BasePostServiceTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - MEMBER_ID)); + MEMBER_ID, + createDefaultQuestions())); CreatePostResponse createPostResponse = postService.createPost( crew.getId(), @@ -120,7 +125,8 @@ class PostServiceTest extends BasePostServiceTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 100, - MEMBER_ID)); + MEMBER_ID, + createDefaultQuestions())); CreatePostResponse createPostResponse = postService.createPost( crew.getId(), 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 ec25d6c..a49f854 100644 --- a/src/test/java/com/retrip/crew/domain/entity/PostTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/PostTest.java @@ -8,6 +8,7 @@ import java.util.UUID; +import static com.retrip.crew.common.fixture.CrewFixture.createDefaultQuestions; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.junit.jupiter.api.Assertions.*; @@ -19,7 +20,8 @@ class PostTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 5, - UUID.randomUUID()); + UUID.randomUUID(), + createDefaultQuestions()); //then @@ -42,7 +44,8 @@ class PostTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 5, - UUID.randomUUID()); + UUID.randomUUID(), + createDefaultQuestions()); //then @@ -61,7 +64,8 @@ class PostTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 5, - UUID.randomUUID()); + UUID.randomUUID(), + createDefaultQuestions()); Post post = Post.create( "μ†μ΄ˆ μ—¬ν–‰ μ–΄λ””κ°€ μ’‹μ•„μš”?", "μΉœκ΅¬λ“€κ³Ό μ†μ΄ˆ μ—¬ν–‰ κ°€λ €κ³  ν•©λ‹ˆλ‹€. μ†μ΄ˆ μ—¬ν–‰μ§€ μΆ”μ²œν•΄ μ£Όμ„Έμš”~", @@ -80,7 +84,8 @@ class PostTest { "μ†μ΄ˆ 크루원 ꡬ함", "μ†μ΄ˆ 친ꡬ κ΅¬ν•©λ‹ˆλ‹€! λ‚˜μ΄λŠ” 20~40.. λ§Žμ€ κ°€μž… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.", 5, - UUID.randomUUID()); + UUID.randomUUID(), + createDefaultQuestions()); Post post = Post.create( "μ†μ΄ˆ μ—¬ν–‰ μ–΄λ””κ°€ μ’‹μ•„μš”?", "μΉœκ΅¬λ“€κ³Ό μ†μ΄ˆ μ—¬ν–‰ κ°€λ €κ³  ν•©λ‹ˆλ‹€. μ†μ΄ˆ μ—¬ν–‰μ§€ μΆ”μ²œν•΄ μ£Όμ„Έμš”~", diff --git a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java index cad2e0a..7c99d2c 100644 --- a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java @@ -19,7 +19,7 @@ class RecruitmentTest { @Test void λͺ¨μ§‘을_μƒμ„±ν•œλ‹€() { - assertThatCode(() -> new Recruitment(100)) + assertThatCode(() -> new Recruitment(100,)) .doesNotThrowAnyException(); }