From 50deb06609b3458697f7ad5af1bda3fa2fb70376 Mon Sep 17 00:00:00 2001 From: hseong3243 Date: Sat, 10 Feb 2024 22:44:55 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=ED=97=88=EB=B8=8C=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/seong/shoutlink/domain/hub/Hub.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/main/java/com/seong/shoutlink/domain/hub/Hub.java diff --git a/src/main/java/com/seong/shoutlink/domain/hub/Hub.java b/src/main/java/com/seong/shoutlink/domain/hub/Hub.java new file mode 100644 index 0000000..031a236 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hub/Hub.java @@ -0,0 +1,46 @@ +package com.seong.shoutlink.domain.hub; + +import com.seong.shoutlink.domain.exception.ErrorCode; +import com.seong.shoutlink.domain.exception.ShoutLinkException; +import java.text.MessageFormat; +import java.util.Objects; + +public class Hub { + + private static final int NAME_MAX_SIZE = 30; + private static final String DESCRIPTION_DEFAULT = ""; + private static final int DESCRIPTION_MAX_SIZE = 200; + + private Long hubId; + private String name; + private String description; + + public Hub(String name, String description) { + this.name = validateName(name); + this.description = validateDescription(description); + } + + private String validateName(String name) { + if (Objects.isNull(name)) { + throw new ShoutLinkException("허브 이름은 필수입니다.", ErrorCode.ILLEGAL_ARGUMENT); + } + if(name.isEmpty() || name.length() > NAME_MAX_SIZE) { + throw new ShoutLinkException( + MessageFormat.format("허브 이름은 1자 이상, {0}자 이하여야 합니다.", NAME_MAX_SIZE), + ErrorCode.ILLEGAL_ARGUMENT); + } + return name; + } + + private String validateDescription(String description) { + if(Objects.isNull(description)) { + return DESCRIPTION_DEFAULT; + } + if(description.length() > DESCRIPTION_MAX_SIZE) { + throw new ShoutLinkException( + MessageFormat.format("허브 설명은 {0}자 이하여야 합니다.", DESCRIPTION_MAX_SIZE), + ErrorCode.ILLEGAL_ARGUMENT); + } + return description; + } +} From ffdc77c776f0de0f0f90411677326487e27a29ee Mon Sep 17 00:00:00 2001 From: hseong3243 Date: Sat, 10 Feb 2024 23:10:11 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=ED=97=88=EB=B8=8C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/seong/shoutlink/domain/hub/Hub.java | 2 + .../shoutlink/domain/hub/HubWithMembers.java | 16 ++++ .../hub/repository/HubRepositoryImpl.java | 14 +++ .../domain/hub/service/HubRepository.java | 8 ++ .../domain/hub/service/HubService.java | 36 ++++++++ .../hub/service/event/CreateHubEvent.java | 7 ++ .../hub/service/request/CreateHubCommand.java | 5 ++ .../service/response/CreateHubResponse.java | 5 ++ .../domain/hub/service/HubServiceTest.java | 85 +++++++++++++++++++ .../shoutlink/fixture/StubHubRepository.java | 17 ++++ 10 files changed, 195 insertions(+) create mode 100644 src/main/java/com/seong/shoutlink/domain/hub/HubWithMembers.java create mode 100644 src/main/java/com/seong/shoutlink/domain/hub/repository/HubRepositoryImpl.java create mode 100644 src/main/java/com/seong/shoutlink/domain/hub/service/HubRepository.java create mode 100644 src/main/java/com/seong/shoutlink/domain/hub/service/HubService.java create mode 100644 src/main/java/com/seong/shoutlink/domain/hub/service/event/CreateHubEvent.java create mode 100644 src/main/java/com/seong/shoutlink/domain/hub/service/request/CreateHubCommand.java create mode 100644 src/main/java/com/seong/shoutlink/domain/hub/service/response/CreateHubResponse.java create mode 100644 src/test/java/com/seong/shoutlink/domain/hub/service/HubServiceTest.java create mode 100644 src/test/java/com/seong/shoutlink/fixture/StubHubRepository.java diff --git a/src/main/java/com/seong/shoutlink/domain/hub/Hub.java b/src/main/java/com/seong/shoutlink/domain/hub/Hub.java index 031a236..a6b824b 100644 --- a/src/main/java/com/seong/shoutlink/domain/hub/Hub.java +++ b/src/main/java/com/seong/shoutlink/domain/hub/Hub.java @@ -4,7 +4,9 @@ import com.seong.shoutlink.domain.exception.ShoutLinkException; import java.text.MessageFormat; import java.util.Objects; +import lombok.Getter; +@Getter public class Hub { private static final int NAME_MAX_SIZE = 30; diff --git a/src/main/java/com/seong/shoutlink/domain/hub/HubWithMembers.java b/src/main/java/com/seong/shoutlink/domain/hub/HubWithMembers.java new file mode 100644 index 0000000..cab80fc --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hub/HubWithMembers.java @@ -0,0 +1,16 @@ +package com.seong.shoutlink.domain.hub; + +import com.seong.shoutlink.domain.member.Member; +import lombok.Getter; + +@Getter +public class HubWithMembers { + + private final Hub hub; + private final Member member; + + public HubWithMembers(Hub hub, Member member) { + this.hub = hub; + this.member = member; + } +} diff --git a/src/main/java/com/seong/shoutlink/domain/hub/repository/HubRepositoryImpl.java b/src/main/java/com/seong/shoutlink/domain/hub/repository/HubRepositoryImpl.java new file mode 100644 index 0000000..e6a04bf --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hub/repository/HubRepositoryImpl.java @@ -0,0 +1,14 @@ +package com.seong.shoutlink.domain.hub.repository; + +import com.seong.shoutlink.domain.hub.HubWithMembers; +import com.seong.shoutlink.domain.hub.service.HubRepository; +import org.springframework.stereotype.Repository; + +@Repository +public class HubRepositoryImpl implements HubRepository { + + @Override + public Long save(HubWithMembers hub) { + return 1L; + } +} diff --git a/src/main/java/com/seong/shoutlink/domain/hub/service/HubRepository.java b/src/main/java/com/seong/shoutlink/domain/hub/service/HubRepository.java new file mode 100644 index 0000000..a3ceb84 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hub/service/HubRepository.java @@ -0,0 +1,8 @@ +package com.seong.shoutlink.domain.hub.service; + +import com.seong.shoutlink.domain.hub.HubWithMembers; + +public interface HubRepository { + + Long save(HubWithMembers hub); +} diff --git a/src/main/java/com/seong/shoutlink/domain/hub/service/HubService.java b/src/main/java/com/seong/shoutlink/domain/hub/service/HubService.java new file mode 100644 index 0000000..05a9655 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hub/service/HubService.java @@ -0,0 +1,36 @@ +package com.seong.shoutlink.domain.hub.service; + +import com.seong.shoutlink.domain.common.EventPublisher; +import com.seong.shoutlink.domain.exception.ErrorCode; +import com.seong.shoutlink.domain.exception.ShoutLinkException; +import com.seong.shoutlink.domain.hub.Hub; +import com.seong.shoutlink.domain.hub.HubWithMembers; +import com.seong.shoutlink.domain.hub.service.event.CreateHubEvent; +import com.seong.shoutlink.domain.hub.service.request.CreateHubCommand; +import com.seong.shoutlink.domain.hub.service.response.CreateHubResponse; +import com.seong.shoutlink.domain.member.Member; +import com.seong.shoutlink.domain.member.service.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class HubService { + + private final MemberRepository memberRepository; + private final HubRepository hubRepository; + private final EventPublisher eventPublisher; + + public CreateHubResponse createHub(CreateHubCommand command) { + Member member = getMember(command.memberId()); + Hub hub = new Hub(command.name(), command.description()); + Long hubId = hubRepository.save(new HubWithMembers(hub, member)); + eventPublisher.publishEvent(new CreateHubEvent(hubId)); + return new CreateHubResponse(hubId); + } + + private Member getMember(Long memberId) { + return memberRepository.findById(memberId) + .orElseThrow(() -> new ShoutLinkException("존재하지 않는 사용자입니다.", ErrorCode.NOT_FOUND)); + } +} diff --git a/src/main/java/com/seong/shoutlink/domain/hub/service/event/CreateHubEvent.java b/src/main/java/com/seong/shoutlink/domain/hub/service/event/CreateHubEvent.java new file mode 100644 index 0000000..296152d --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hub/service/event/CreateHubEvent.java @@ -0,0 +1,7 @@ +package com.seong.shoutlink.domain.hub.service.event; + +import com.seong.shoutlink.domain.common.Event; + +public record CreateHubEvent(Long hubId) implements Event { + +} diff --git a/src/main/java/com/seong/shoutlink/domain/hub/service/request/CreateHubCommand.java b/src/main/java/com/seong/shoutlink/domain/hub/service/request/CreateHubCommand.java new file mode 100644 index 0000000..08e2640 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hub/service/request/CreateHubCommand.java @@ -0,0 +1,5 @@ +package com.seong.shoutlink.domain.hub.service.request; + +public record CreateHubCommand(Long memberId, String name, String description) { + +} diff --git a/src/main/java/com/seong/shoutlink/domain/hub/service/response/CreateHubResponse.java b/src/main/java/com/seong/shoutlink/domain/hub/service/response/CreateHubResponse.java new file mode 100644 index 0000000..eafc715 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hub/service/response/CreateHubResponse.java @@ -0,0 +1,5 @@ +package com.seong.shoutlink.domain.hub.service.response; + +public record CreateHubResponse(Long hubId) { + +} diff --git a/src/test/java/com/seong/shoutlink/domain/hub/service/HubServiceTest.java b/src/test/java/com/seong/shoutlink/domain/hub/service/HubServiceTest.java new file mode 100644 index 0000000..daba829 --- /dev/null +++ b/src/test/java/com/seong/shoutlink/domain/hub/service/HubServiceTest.java @@ -0,0 +1,85 @@ +package com.seong.shoutlink.domain.hub.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchException; + +import com.seong.shoutlink.domain.common.StubEventPublisher; +import com.seong.shoutlink.domain.exception.ErrorCode; +import com.seong.shoutlink.domain.exception.ShoutLinkException; +import com.seong.shoutlink.domain.hub.service.request.CreateHubCommand; +import com.seong.shoutlink.domain.hub.service.response.CreateHubResponse; +import com.seong.shoutlink.domain.member.Member; +import com.seong.shoutlink.domain.member.repository.StubMemberRepository; +import com.seong.shoutlink.fixture.MemberFixture; +import com.seong.shoutlink.fixture.StubHubRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class HubServiceTest { + + @Nested + @DisplayName("createHub 메서드 호출 시") + class CreateHubTest { + + private HubService hubService; + private StubMemberRepository memberRepository; + private StubHubRepository hubRepository; + private StubEventPublisher eventPublisher; + + @BeforeEach + void setUp() { + memberRepository = new StubMemberRepository(); + hubRepository = new StubHubRepository(); + eventPublisher = new StubEventPublisher(); + hubService = new HubService(memberRepository, hubRepository, eventPublisher); + } + + @Test + @DisplayName("성공: 허브 생성됨") + void createHub() { + //given + Member member = MemberFixture.member(); + memberRepository.stub(member); + CreateHubCommand command = new CreateHubCommand(member.getMemberId(), "허브 이름", "허브 설명"); + + //when + CreateHubResponse response = hubService.createHub(command); + + //then + assertThat(response.hubId()).isEqualTo(1L); + } + + @Test + @DisplayName("성공: 허브 생성 이벤트 발행 됨") + void createHub_ThenPublishCreateHubEvent() { + //given + Member member = MemberFixture.member(); + memberRepository.stub(member); + CreateHubCommand command = new CreateHubCommand(member.getMemberId(), "허브 이름", "허브 설명"); + + //when + CreateHubResponse response = hubService.createHub(command); + + //then + assertThat(eventPublisher.getPublishEventCount()).isEqualTo(1); + } + + @Test + @DisplayName("예외(NotFount): 존재하지 않는 사용자") + void notFound_WhenUserNotFound() { + //given + CreateHubCommand command = new CreateHubCommand(1L, "허브 이름", "허브 설명"); + + //when + Exception exception = catchException(() -> hubService.createHub(command)); + + //then + assertThat(exception) + .isInstanceOf(ShoutLinkException.class) + .extracting(e -> ((ShoutLinkException) e).getErrorCode()) + .isEqualTo(ErrorCode.NOT_FOUND); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/seong/shoutlink/fixture/StubHubRepository.java b/src/test/java/com/seong/shoutlink/fixture/StubHubRepository.java new file mode 100644 index 0000000..27ccad7 --- /dev/null +++ b/src/test/java/com/seong/shoutlink/fixture/StubHubRepository.java @@ -0,0 +1,17 @@ +package com.seong.shoutlink.fixture; + +import com.seong.shoutlink.domain.hub.Hub; +import com.seong.shoutlink.domain.hub.HubWithMembers; +import com.seong.shoutlink.domain.hub.service.HubRepository; +import java.util.HashMap; +import java.util.Map; + +public class StubHubRepository implements HubRepository { + + private final Map memory = new HashMap<>(); + + @Override + public Long save(HubWithMembers hub) { + return 1L; + } +} From 356933d0c9249df28afbbce99248b8b78eb40982 Mon Sep 17 00:00:00 2001 From: hseong3243 Date: Sat, 10 Feb 2024 23:29:32 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=ED=97=88=EB=B8=8C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=98=81=EC=86=8D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/hub/repository/HubEntity.java | 36 +++++++++++++++ .../hub/repository/HubJpaRepository.java | 7 +++ .../hub/repository/HubRepositoryImpl.java | 16 ++++++- .../domain/hub/service/HubRepository.java | 2 +- .../domain/hubMember/HubMemberRole.java | 5 +++ .../hubMember/repository/HubMemberEntity.java | 45 +++++++++++++++++++ .../repository/HubMemberJpaRepository.java | 7 +++ .../shoutlink/fixture/StubHubRepository.java | 2 +- 8 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/seong/shoutlink/domain/hub/repository/HubEntity.java create mode 100644 src/main/java/com/seong/shoutlink/domain/hub/repository/HubJpaRepository.java create mode 100644 src/main/java/com/seong/shoutlink/domain/hubMember/HubMemberRole.java create mode 100644 src/main/java/com/seong/shoutlink/domain/hubMember/repository/HubMemberEntity.java create mode 100644 src/main/java/com/seong/shoutlink/domain/hubMember/repository/HubMemberJpaRepository.java diff --git a/src/main/java/com/seong/shoutlink/domain/hub/repository/HubEntity.java b/src/main/java/com/seong/shoutlink/domain/hub/repository/HubEntity.java new file mode 100644 index 0000000..23b028e --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hub/repository/HubEntity.java @@ -0,0 +1,36 @@ +package com.seong.shoutlink.domain.hub.repository; + +import com.seong.shoutlink.domain.hub.Hub; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class HubEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long hubId; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String description; + + private HubEntity(String name, String description) { + this.name = name; + this.description = description; + } + + public static HubEntity create(Hub hub) { + return new HubEntity(hub.getName(), hub.getDescription()); + } +} diff --git a/src/main/java/com/seong/shoutlink/domain/hub/repository/HubJpaRepository.java b/src/main/java/com/seong/shoutlink/domain/hub/repository/HubJpaRepository.java new file mode 100644 index 0000000..9a86e2e --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hub/repository/HubJpaRepository.java @@ -0,0 +1,7 @@ +package com.seong.shoutlink.domain.hub.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HubJpaRepository extends JpaRepository { + +} diff --git a/src/main/java/com/seong/shoutlink/domain/hub/repository/HubRepositoryImpl.java b/src/main/java/com/seong/shoutlink/domain/hub/repository/HubRepositoryImpl.java index e6a04bf..1125f8d 100644 --- a/src/main/java/com/seong/shoutlink/domain/hub/repository/HubRepositoryImpl.java +++ b/src/main/java/com/seong/shoutlink/domain/hub/repository/HubRepositoryImpl.java @@ -2,13 +2,25 @@ import com.seong.shoutlink.domain.hub.HubWithMembers; import com.seong.shoutlink.domain.hub.service.HubRepository; +import com.seong.shoutlink.domain.hubMember.repository.HubMemberEntity; +import com.seong.shoutlink.domain.hubMember.repository.HubMemberJpaRepository; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @Repository +@RequiredArgsConstructor public class HubRepositoryImpl implements HubRepository { + private final HubJpaRepository hubJpaRepository; + private final HubMemberJpaRepository hubMemberJpaRepository; + @Override - public Long save(HubWithMembers hub) { - return 1L; + public Long save(HubWithMembers hubWithMembers) { + HubEntity hubEntity = HubEntity.create(hubWithMembers.getHub()); + hubJpaRepository.save(hubEntity); + HubMemberEntity hubMemberEntity + = HubMemberEntity.create(hubWithMembers.getHub(), hubWithMembers.getMember()); + hubMemberJpaRepository.save(hubMemberEntity); + return hubEntity.getHubId(); } } diff --git a/src/main/java/com/seong/shoutlink/domain/hub/service/HubRepository.java b/src/main/java/com/seong/shoutlink/domain/hub/service/HubRepository.java index a3ceb84..ac8ca70 100644 --- a/src/main/java/com/seong/shoutlink/domain/hub/service/HubRepository.java +++ b/src/main/java/com/seong/shoutlink/domain/hub/service/HubRepository.java @@ -4,5 +4,5 @@ public interface HubRepository { - Long save(HubWithMembers hub); + Long save(HubWithMembers hubWithMembers); } diff --git a/src/main/java/com/seong/shoutlink/domain/hubMember/HubMemberRole.java b/src/main/java/com/seong/shoutlink/domain/hubMember/HubMemberRole.java new file mode 100644 index 0000000..bcc990a --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hubMember/HubMemberRole.java @@ -0,0 +1,5 @@ +package com.seong.shoutlink.domain.hubMember; + +public enum HubMemberRole { + MASTER, PARTICIPANTS +} diff --git a/src/main/java/com/seong/shoutlink/domain/hubMember/repository/HubMemberEntity.java b/src/main/java/com/seong/shoutlink/domain/hubMember/repository/HubMemberEntity.java new file mode 100644 index 0000000..d23ca9a --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hubMember/repository/HubMemberEntity.java @@ -0,0 +1,45 @@ +package com.seong.shoutlink.domain.hubMember.repository; + +import com.seong.shoutlink.domain.hub.Hub; +import com.seong.shoutlink.domain.hubMember.HubMemberRole; +import com.seong.shoutlink.domain.member.Member; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class HubMemberEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long hubMemberId; + + @Column(nullable = false) + private Long memberId; + + @Column(nullable = false) + private Long hubId; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private HubMemberRole hubMemberRole; + + public HubMemberEntity(Long memberId, Long hubId, HubMemberRole hubMemberRole) { + this.memberId = memberId; + this.hubId = hubId; + this.hubMemberRole = hubMemberRole; + } + + public static HubMemberEntity create(Hub hub, Member member) { + return new HubMemberEntity(member.getMemberId(), hub.getHubId(), HubMemberRole.MASTER); + } +} diff --git a/src/main/java/com/seong/shoutlink/domain/hubMember/repository/HubMemberJpaRepository.java b/src/main/java/com/seong/shoutlink/domain/hubMember/repository/HubMemberJpaRepository.java new file mode 100644 index 0000000..d1c7b86 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hubMember/repository/HubMemberJpaRepository.java @@ -0,0 +1,7 @@ +package com.seong.shoutlink.domain.hubMember.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HubMemberJpaRepository extends JpaRepository { + +} diff --git a/src/test/java/com/seong/shoutlink/fixture/StubHubRepository.java b/src/test/java/com/seong/shoutlink/fixture/StubHubRepository.java index 27ccad7..e165eaf 100644 --- a/src/test/java/com/seong/shoutlink/fixture/StubHubRepository.java +++ b/src/test/java/com/seong/shoutlink/fixture/StubHubRepository.java @@ -11,7 +11,7 @@ public class StubHubRepository implements HubRepository { private final Map memory = new HashMap<>(); @Override - public Long save(HubWithMembers hub) { + public Long save(HubWithMembers hubWithMembers) { return 1L; } } From d63794e622569c1d3c8e1c4d49c3347986b3d01c Mon Sep 17 00:00:00 2001 From: hseong3243 Date: Sat, 10 Feb 2024 23:37:34 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=ED=97=88=EB=B8=8C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/hub/controller/HubController.java | 34 ++++++++++++ .../controller/request/CreateHubRequest.java | 10 ++++ .../shoutlink/base/BaseControllerTest.java | 4 ++ .../hub/controller/HubControllerTest.java | 55 +++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 src/main/java/com/seong/shoutlink/domain/hub/controller/HubController.java create mode 100644 src/main/java/com/seong/shoutlink/domain/hub/controller/request/CreateHubRequest.java create mode 100644 src/test/java/com/seong/shoutlink/domain/hub/controller/HubControllerTest.java diff --git a/src/main/java/com/seong/shoutlink/domain/hub/controller/HubController.java b/src/main/java/com/seong/shoutlink/domain/hub/controller/HubController.java new file mode 100644 index 0000000..ab2fcc6 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hub/controller/HubController.java @@ -0,0 +1,34 @@ +package com.seong.shoutlink.domain.hub.controller; + +import com.seong.shoutlink.domain.auth.LoginUser; +import com.seong.shoutlink.domain.hub.controller.request.CreateHubRequest; +import com.seong.shoutlink.domain.hub.service.HubService; +import com.seong.shoutlink.domain.hub.service.request.CreateHubCommand; +import com.seong.shoutlink.domain.hub.service.response.CreateHubResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequiredArgsConstructor +@RequestMapping("/api") +public class HubController { + + private final HubService hubService; + + @PostMapping("/hubs") + public ResponseEntity createHub( + @RequestBody @Valid CreateHubRequest request, + @LoginUser Long memberId) { + CreateHubResponse response = hubService.createHub(new CreateHubCommand( + memberId, + request.name(), + request.description())); + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } +} diff --git a/src/main/java/com/seong/shoutlink/domain/hub/controller/request/CreateHubRequest.java b/src/main/java/com/seong/shoutlink/domain/hub/controller/request/CreateHubRequest.java new file mode 100644 index 0000000..e59b7bd --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/hub/controller/request/CreateHubRequest.java @@ -0,0 +1,10 @@ +package com.seong.shoutlink.domain.hub.controller.request; + +import jakarta.validation.constraints.NotBlank; + +public record CreateHubRequest( + @NotBlank(message = "허브 이름은 공백일 수 없습니다.") + String name, + String description) { + +} diff --git a/src/test/java/com/seong/shoutlink/base/BaseControllerTest.java b/src/test/java/com/seong/shoutlink/base/BaseControllerTest.java index 4b3ce99..0dba222 100644 --- a/src/test/java/com/seong/shoutlink/base/BaseControllerTest.java +++ b/src/test/java/com/seong/shoutlink/base/BaseControllerTest.java @@ -6,6 +6,7 @@ import com.seong.shoutlink.base.BaseControllerTest.BaseControllerConfig; import com.seong.shoutlink.domain.auth.JwtProvider; import com.seong.shoutlink.domain.auth.service.AuthService; +import com.seong.shoutlink.domain.hub.service.HubService; import com.seong.shoutlink.domain.link.service.LinkService; import com.seong.shoutlink.domain.linkbundle.service.LinkBundleService; import com.seong.shoutlink.domain.member.MemberRole; @@ -73,6 +74,9 @@ public AuthenticationContext authenticationContext() { @MockBean protected LinkBundleService linkBundleService; + @MockBean + protected HubService hubService; + @BeforeEach void setUp( WebApplicationContext applicationContext, diff --git a/src/test/java/com/seong/shoutlink/domain/hub/controller/HubControllerTest.java b/src/test/java/com/seong/shoutlink/domain/hub/controller/HubControllerTest.java new file mode 100644 index 0000000..092e150 --- /dev/null +++ b/src/test/java/com/seong/shoutlink/domain/hub/controller/HubControllerTest.java @@ -0,0 +1,55 @@ +package com.seong.shoutlink.domain.hub.controller; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.seong.shoutlink.base.BaseControllerTest; +import com.seong.shoutlink.domain.hub.controller.request.CreateHubRequest; +import com.seong.shoutlink.domain.hub.service.response.CreateHubResponse; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +class HubControllerTest extends BaseControllerTest { + + @Test + @DisplayName("성공: 허브 생성 api 호출 시") + void createHub() throws Exception { + //given + CreateHubRequest request = new CreateHubRequest("허브 이름", "허브 설명"); + CreateHubResponse response = new CreateHubResponse(1L); + + given(hubService.createHub(any())).willReturn(response); + + //when + ResultActions resultActions = mockMvc.perform(post("/api/hubs") + .header(AUTHORIZATION, bearerAccessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + //then + resultActions.andExpect(status().isCreated()) + .andDo(restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION).description("액세스 토큰") + ), + requestFields( + fieldWithPath("name").type(JsonFieldType.STRING).description("허브 이름"), + fieldWithPath("description").type(JsonFieldType.STRING).description("허브 설명") + .optional() + ), + responseFields( + fieldWithPath("hubId").type(JsonFieldType.NUMBER).description("허브 ID") + ) + )); + } +} \ No newline at end of file From 697b526c3c2679823dfd37dd4ff1b6fe471738ee Mon Sep 17 00:00:00 2001 From: hseong3243 Date: Sat, 10 Feb 2024 23:40:15 +0900 Subject: [PATCH 5/6] =?UTF-8?q?docs:=20=ED=97=88=EB=B8=8C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20api=20=EB=AC=B8=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 12 ++ src/main/resources/static/docs/index.html | 155 +++++++++++++++++++++- 2 files changed, 162 insertions(+), 5 deletions(-) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 116d527..123c5e1 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -73,3 +73,15 @@ operation::link-controller-test/find-links[snippets='http-request,request-header ==== response operation::link-controller-test/find-links[snippets='http-response,response-fields'] + +== 허브 + +=== 허브 생성 + +==== request + +operation::hub-controller-test/create-hub[snippets='http-request,request-headers,request-fields'] + +==== response + +operation::hub-controller-test/create-hub[snippets='http-response,response-fields'] diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index 4ffad0d..e396a84 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -468,6 +468,11 @@

API 문서

  • 링크 목록 조회
  • +
  • 허브 + +
  • @@ -532,6 +537,9 @@
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
     Set-Cookie: refreshToken=refreshToken; Path=/api; Domain=.shoutlink.me; Secure; HttpOnly; SameSite=None
     Content-Type: application/json;charset=UTF-8
     Content-Length: 53
    @@ -642,6 +650,9 @@ 
    HTTP/1.1 201 Created
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
     Content-Type: application/json;charset=UTF-8
     Content-Length: 20
     
    @@ -692,7 +703,7 @@ 
    POST /api/link-bundles HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzA2Nzk3MTkzLCJzdWIiOiIxIiwiZXhwIjoxNzA2ODAwNzkzLCJyb2xlIjoiUk9MRV9VU0VSIn0.Ui4laWlNi7dZKUmDn06SODSuwdROpviBmm0rV-FUTaU
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzA3NTc1OTY4LCJzdWIiOiIxIiwiZXhwIjoxNzA3NTc5NTY4LCJyb2xlIjoiUk9MRV9VU0VSIn0.khABcLP_2-tJN9dEFH_Ot6o6HXFnYWRVKU4rQsUcL0A
     Content-Length: 57
     Host: localhost:8080
     
    @@ -761,6 +772,9 @@ 
    @@ -839,6 +853,9 @@
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
     Content-Type: application/json;charset=UTF-8
     Content-Length: 203
     
    @@ -912,7 +929,7 @@ 
    POST /api/links HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzA2Nzk3MTkzLCJzdWIiOiIxIiwiZXhwIjoxNzA2ODAwNzkzLCJyb2xlIjoiUk9MRV9VU0VSIn0.Ui4laWlNi7dZKUmDn06SODSuwdROpviBmm0rV-FUTaU
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzA3NTc1OTY4LCJzdWIiOiIxIiwiZXhwIjoxNzA3NTc5NTY4LCJyb2xlIjoiUk9MRV9VU0VSIn0.khABcLP_2-tJN9dEFH_Ot6o6HXFnYWRVKU4rQsUcL0A
     Content-Length: 100
     Host: localhost:8080
     
    @@ -987,6 +1004,9 @@ 
    @@ -1094,6 +1114,9 @@
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
     Content-Type: application/json;charset=UTF-8
     Content-Length: 145
     
    @@ -1162,11 +1185,133 @@ 
    +

    허브

    +
    +
    +

    허브 생성

    +
    +

    request

    +
    +
    HTTP request
    +
    +
    +
    POST /api/hubs HTTP/1.1
    +Content-Type: application/json;charset=UTF-8
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzA3NTc1OTY4LCJzdWIiOiIxIiwiZXhwIjoxNzA3NTc5NTY4LCJyb2xlIjoiUk9MRV9VU0VSIn0.khABcLP_2-tJN9dEFH_Ot6o6HXFnYWRVKU4rQsUcL0A
    +Content-Length: 65
    +Host: localhost:8080
    +
    +{
    +  "name" : "허브 이름",
    +  "description" : "허브 설명"
    +}
    +
    +
    +
    +
    +
    Request headers
    + ++++ + + + + + + + + + + + + +
    NameDescription

    Authorization

    액세스 토큰

    +
    +
    +
    Request fields
    + +++++ + + + + + + + + + + + + + + + + + + + +
    PathTypeDescription

    name

    String

    허브 이름

    description

    String

    허브 설명

    +
    +
    +
    +

    response

    +
    +
    HTTP response
    +
    +
    +
    HTTP/1.1 201 Created
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +Content-Type: application/json;charset=UTF-8
    +Content-Length: 17
    +
    +{
    +  "hubId" : 1
    +}
    +
    +
    +
    +
    +
    Response fields
    + +++++ + + + + + + + + + + + + + + +
    PathTypeDescription

    hubId

    Number

    허브 ID

    +
    +
    +
    +
    +
    From d1e008e29f9094920ad3b78001ef06f853a69064 Mon Sep 17 00:00:00 2001 From: hseong3243 Date: Sat, 10 Feb 2024 23:47:02 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20=ED=97=88=EB=B8=8C=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=EC=97=90=20=EA=B3=B5=EA=B0=9C=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/seong/shoutlink/domain/hub/Hub.java | 4 +++- .../shoutlink/domain/hub/controller/HubController.java | 3 ++- .../domain/hub/controller/request/CreateHubRequest.java | 5 ++++- .../seong/shoutlink/domain/hub/repository/HubEntity.java | 8 ++++++-- .../seong/shoutlink/domain/hub/service/HubService.java | 2 +- .../domain/hub/service/request/CreateHubCommand.java | 2 +- .../domain/hub/controller/HubControllerTest.java | 5 +++-- .../shoutlink/domain/hub/service/HubServiceTest.java | 8 +++++--- 8 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/seong/shoutlink/domain/hub/Hub.java b/src/main/java/com/seong/shoutlink/domain/hub/Hub.java index a6b824b..cdcba1d 100644 --- a/src/main/java/com/seong/shoutlink/domain/hub/Hub.java +++ b/src/main/java/com/seong/shoutlink/domain/hub/Hub.java @@ -16,10 +16,12 @@ public class Hub { private Long hubId; private String name; private String description; + private boolean isPrivate; - public Hub(String name, String description) { + public Hub(String name, String description, boolean isPrivate) { this.name = validateName(name); this.description = validateDescription(description); + this.isPrivate = isPrivate; } private String validateName(String name) { diff --git a/src/main/java/com/seong/shoutlink/domain/hub/controller/HubController.java b/src/main/java/com/seong/shoutlink/domain/hub/controller/HubController.java index ab2fcc6..d9bd427 100644 --- a/src/main/java/com/seong/shoutlink/domain/hub/controller/HubController.java +++ b/src/main/java/com/seong/shoutlink/domain/hub/controller/HubController.java @@ -28,7 +28,8 @@ public ResponseEntity createHub( CreateHubResponse response = hubService.createHub(new CreateHubCommand( memberId, request.name(), - request.description())); + request.description(), + request.isPrivate())); return ResponseEntity.status(HttpStatus.CREATED).body(response); } } diff --git a/src/main/java/com/seong/shoutlink/domain/hub/controller/request/CreateHubRequest.java b/src/main/java/com/seong/shoutlink/domain/hub/controller/request/CreateHubRequest.java index e59b7bd..c38965a 100644 --- a/src/main/java/com/seong/shoutlink/domain/hub/controller/request/CreateHubRequest.java +++ b/src/main/java/com/seong/shoutlink/domain/hub/controller/request/CreateHubRequest.java @@ -1,10 +1,13 @@ package com.seong.shoutlink.domain.hub.controller.request; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; public record CreateHubRequest( @NotBlank(message = "허브 이름은 공백일 수 없습니다.") String name, - String description) { + String description, + @NotNull(message = "허브 공개 여부는 필수입니다.") + Boolean isPrivate) { } diff --git a/src/main/java/com/seong/shoutlink/domain/hub/repository/HubEntity.java b/src/main/java/com/seong/shoutlink/domain/hub/repository/HubEntity.java index 23b028e..4d53bae 100644 --- a/src/main/java/com/seong/shoutlink/domain/hub/repository/HubEntity.java +++ b/src/main/java/com/seong/shoutlink/domain/hub/repository/HubEntity.java @@ -25,12 +25,16 @@ public class HubEntity { @Column(nullable = false) private String description; - private HubEntity(String name, String description) { + @Column(nullable = false) + private boolean isPrivate; + + private HubEntity(String name, String description, boolean isPrivate) { this.name = name; this.description = description; + this.isPrivate = isPrivate; } public static HubEntity create(Hub hub) { - return new HubEntity(hub.getName(), hub.getDescription()); + return new HubEntity(hub.getName(), hub.getDescription(), hub.isPrivate()); } } diff --git a/src/main/java/com/seong/shoutlink/domain/hub/service/HubService.java b/src/main/java/com/seong/shoutlink/domain/hub/service/HubService.java index 05a9655..33504aa 100644 --- a/src/main/java/com/seong/shoutlink/domain/hub/service/HubService.java +++ b/src/main/java/com/seong/shoutlink/domain/hub/service/HubService.java @@ -23,7 +23,7 @@ public class HubService { public CreateHubResponse createHub(CreateHubCommand command) { Member member = getMember(command.memberId()); - Hub hub = new Hub(command.name(), command.description()); + Hub hub = new Hub(command.name(), command.description(), command.isPrivate()); Long hubId = hubRepository.save(new HubWithMembers(hub, member)); eventPublisher.publishEvent(new CreateHubEvent(hubId)); return new CreateHubResponse(hubId); diff --git a/src/main/java/com/seong/shoutlink/domain/hub/service/request/CreateHubCommand.java b/src/main/java/com/seong/shoutlink/domain/hub/service/request/CreateHubCommand.java index 08e2640..9d490c2 100644 --- a/src/main/java/com/seong/shoutlink/domain/hub/service/request/CreateHubCommand.java +++ b/src/main/java/com/seong/shoutlink/domain/hub/service/request/CreateHubCommand.java @@ -1,5 +1,5 @@ package com.seong.shoutlink.domain.hub.service.request; -public record CreateHubCommand(Long memberId, String name, String description) { +public record CreateHubCommand(Long memberId, String name, String description, boolean isPrivate) { } diff --git a/src/test/java/com/seong/shoutlink/domain/hub/controller/HubControllerTest.java b/src/test/java/com/seong/shoutlink/domain/hub/controller/HubControllerTest.java index 092e150..628b580 100644 --- a/src/test/java/com/seong/shoutlink/domain/hub/controller/HubControllerTest.java +++ b/src/test/java/com/seong/shoutlink/domain/hub/controller/HubControllerTest.java @@ -25,7 +25,7 @@ class HubControllerTest extends BaseControllerTest { @DisplayName("성공: 허브 생성 api 호출 시") void createHub() throws Exception { //given - CreateHubRequest request = new CreateHubRequest("허브 이름", "허브 설명"); + CreateHubRequest request = new CreateHubRequest("허브 이름", "허브 설명", false); CreateHubResponse response = new CreateHubResponse(1L); given(hubService.createHub(any())).willReturn(response); @@ -45,7 +45,8 @@ void createHub() throws Exception { requestFields( fieldWithPath("name").type(JsonFieldType.STRING).description("허브 이름"), fieldWithPath("description").type(JsonFieldType.STRING).description("허브 설명") - .optional() + .optional(), + fieldWithPath("isPrivate").type(JsonFieldType.BOOLEAN).description("허브 공개 여부r") ), responseFields( fieldWithPath("hubId").type(JsonFieldType.NUMBER).description("허브 ID") diff --git a/src/test/java/com/seong/shoutlink/domain/hub/service/HubServiceTest.java b/src/test/java/com/seong/shoutlink/domain/hub/service/HubServiceTest.java index daba829..078bc66 100644 --- a/src/test/java/com/seong/shoutlink/domain/hub/service/HubServiceTest.java +++ b/src/test/java/com/seong/shoutlink/domain/hub/service/HubServiceTest.java @@ -42,7 +42,8 @@ void createHub() { //given Member member = MemberFixture.member(); memberRepository.stub(member); - CreateHubCommand command = new CreateHubCommand(member.getMemberId(), "허브 이름", "허브 설명"); + CreateHubCommand command = new CreateHubCommand(member.getMemberId(), "허브 이름", "허브 설명", + false); //when CreateHubResponse response = hubService.createHub(command); @@ -57,7 +58,8 @@ void createHub_ThenPublishCreateHubEvent() { //given Member member = MemberFixture.member(); memberRepository.stub(member); - CreateHubCommand command = new CreateHubCommand(member.getMemberId(), "허브 이름", "허브 설명"); + CreateHubCommand command = new CreateHubCommand(member.getMemberId(), "허브 이름", "허브 설명", + false); //when CreateHubResponse response = hubService.createHub(command); @@ -70,7 +72,7 @@ void createHub_ThenPublishCreateHubEvent() { @DisplayName("예외(NotFount): 존재하지 않는 사용자") void notFound_WhenUserNotFound() { //given - CreateHubCommand command = new CreateHubCommand(1L, "허브 이름", "허브 설명"); + CreateHubCommand command = new CreateHubCommand(1L, "허브 이름", "허브 설명", false); //when Exception exception = catchException(() -> hubService.createHub(command));