diff --git a/src/main/java/com/seong/shoutlink/domain/link/Link.java b/src/main/java/com/seong/shoutlink/domain/link/Link.java index 89bcd15..2cadc17 100644 --- a/src/main/java/com/seong/shoutlink/domain/link/Link.java +++ b/src/main/java/com/seong/shoutlink/domain/link/Link.java @@ -11,6 +11,11 @@ public class Link { public Link(String url, String description) { + this(null, url, description); + } + + public Link(Long linkId, String url, String description) { + this.linkId = linkId; this.url = url; this.description = description; } diff --git a/src/main/java/com/seong/shoutlink/domain/link/LinkWithLinkBundle.java b/src/main/java/com/seong/shoutlink/domain/link/LinkWithLinkBundle.java new file mode 100644 index 0000000..40516b3 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/link/LinkWithLinkBundle.java @@ -0,0 +1,16 @@ +package com.seong.shoutlink.domain.link; + +import com.seong.shoutlink.domain.linkbundle.LinkBundle; +import lombok.Getter; + +@Getter +public class LinkWithLinkBundle { + + private final Link link; + private final LinkBundle linkBundle; + + public LinkWithLinkBundle(Link link, LinkBundle linkBundle) { + this.link = link; + this.linkBundle = linkBundle; + } +} diff --git a/src/main/java/com/seong/shoutlink/domain/link/controller/LinkController.java b/src/main/java/com/seong/shoutlink/domain/link/controller/LinkController.java index 7faf34d..271633c 100644 --- a/src/main/java/com/seong/shoutlink/domain/link/controller/LinkController.java +++ b/src/main/java/com/seong/shoutlink/domain/link/controller/LinkController.java @@ -27,6 +27,7 @@ public ResponseEntity createLink( @Valid @RequestBody CreateLinkRequest request) { CreateLinkResponse response = linkService.createLink(new CreateLinkCommand( memberId, + request.linkBundleId(), request.url(), request.description())); return ResponseEntity.status(HttpStatus.CREATED).body(response); diff --git a/src/main/java/com/seong/shoutlink/domain/link/controller/request/CreateLinkRequest.java b/src/main/java/com/seong/shoutlink/domain/link/controller/request/CreateLinkRequest.java index 772675e..5ac632c 100644 --- a/src/main/java/com/seong/shoutlink/domain/link/controller/request/CreateLinkRequest.java +++ b/src/main/java/com/seong/shoutlink/domain/link/controller/request/CreateLinkRequest.java @@ -1,8 +1,11 @@ package com.seong.shoutlink.domain.link.controller.request; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; public record CreateLinkRequest( + @NotNull(message = "링크 묶음 ID는 필수값입니다.") + Long linkBundleId, @NotBlank(message = "링크 url은 필수값입니다.") String url, String description) { diff --git a/src/main/java/com/seong/shoutlink/domain/link/repository/LinkEntity.java b/src/main/java/com/seong/shoutlink/domain/link/repository/LinkEntity.java index 913deb0..c6a548b 100644 --- a/src/main/java/com/seong/shoutlink/domain/link/repository/LinkEntity.java +++ b/src/main/java/com/seong/shoutlink/domain/link/repository/LinkEntity.java @@ -1,6 +1,8 @@ package com.seong.shoutlink.domain.link.repository; import com.seong.shoutlink.domain.link.Link; +import com.seong.shoutlink.domain.link.LinkWithLinkBundle; +import com.seong.shoutlink.domain.linkbundle.LinkBundle; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -20,13 +22,20 @@ public class LinkEntity { private String url; private String description; + private Long linkBundleId; - private LinkEntity(String url, String description) { + private LinkEntity(String url, String description, Long linkBundleId) { this.url = url; this.description = description; + this.linkBundleId = linkBundleId; } - public static LinkEntity create(Link link) { - return new LinkEntity(link.getUrl(), link.getDescription()); + public static LinkEntity create(LinkWithLinkBundle linkWithLinkBundle) { + Link link = linkWithLinkBundle.getLink(); + LinkBundle linkBundle = linkWithLinkBundle.getLinkBundle(); + return new LinkEntity( + link.getUrl(), + link.getDescription(), + linkBundle.getLinkBundleId()); } } diff --git a/src/main/java/com/seong/shoutlink/domain/link/repository/LinkRepositoryImpl.java b/src/main/java/com/seong/shoutlink/domain/link/repository/LinkRepositoryImpl.java index a8c45df..2ae48ea 100644 --- a/src/main/java/com/seong/shoutlink/domain/link/repository/LinkRepositoryImpl.java +++ b/src/main/java/com/seong/shoutlink/domain/link/repository/LinkRepositoryImpl.java @@ -1,6 +1,6 @@ package com.seong.shoutlink.domain.link.repository; -import com.seong.shoutlink.domain.link.Link; +import com.seong.shoutlink.domain.link.LinkWithLinkBundle; import com.seong.shoutlink.domain.link.service.LinkRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -12,8 +12,8 @@ public class LinkRepositoryImpl implements LinkRepository { private final LinkJpaRepository linkJpaRepository; @Override - public Long save(Link link) { - LinkEntity linkEntity = linkJpaRepository.save(LinkEntity.create(link)); + public Long save(LinkWithLinkBundle linkWithLinkBundle) { + LinkEntity linkEntity = linkJpaRepository.save(LinkEntity.create(linkWithLinkBundle)); return linkEntity.getLinkId(); } } diff --git a/src/main/java/com/seong/shoutlink/domain/link/service/LinkRepository.java b/src/main/java/com/seong/shoutlink/domain/link/service/LinkRepository.java index 94b1ff5..1285d5f 100644 --- a/src/main/java/com/seong/shoutlink/domain/link/service/LinkRepository.java +++ b/src/main/java/com/seong/shoutlink/domain/link/service/LinkRepository.java @@ -1,8 +1,8 @@ package com.seong.shoutlink.domain.link.service; -import com.seong.shoutlink.domain.link.Link; +import com.seong.shoutlink.domain.link.LinkWithLinkBundle; public interface LinkRepository { - Long save(Link link); + Long save(LinkWithLinkBundle linkWithLinkBundle); } diff --git a/src/main/java/com/seong/shoutlink/domain/link/service/LinkService.java b/src/main/java/com/seong/shoutlink/domain/link/service/LinkService.java index 0211c47..e7210cc 100644 --- a/src/main/java/com/seong/shoutlink/domain/link/service/LinkService.java +++ b/src/main/java/com/seong/shoutlink/domain/link/service/LinkService.java @@ -1,10 +1,15 @@ package com.seong.shoutlink.domain.link.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.link.Link; +import com.seong.shoutlink.domain.link.LinkWithLinkBundle; import com.seong.shoutlink.domain.link.service.event.CreateLinkEvent; import com.seong.shoutlink.domain.link.service.request.CreateLinkCommand; import com.seong.shoutlink.domain.link.service.response.CreateLinkResponse; +import com.seong.shoutlink.domain.linkbundle.LinkBundle; +import com.seong.shoutlink.domain.linkbundle.service.LinkBundleRepository; import com.seong.shoutlink.domain.member.service.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -16,15 +21,21 @@ public class LinkService { private final MemberRepository memberRepository; private final LinkRepository linkRepository; + private final LinkBundleRepository linkBundleRepository; private final EventPublisher eventPublisher; @Transactional public CreateLinkResponse createLink(CreateLinkCommand command) { -// Member member = memberRepository.findById(command.memberId()) -// .orElseThrow(() -> new ShoutLinkException("존재하지 않는 사용자입니다.", ErrorCode.NOT_FOUND)); + LinkBundle linkBundle = getLinkBundle(command.linkBundleId()); Link link = new Link(command.url(), command.description()); - Long linkId = linkRepository.save(link); + LinkWithLinkBundle linkWithLinkBundle = new LinkWithLinkBundle(link, linkBundle); + Long linkId = linkRepository.save(linkWithLinkBundle); eventPublisher.publishEvent(new CreateLinkEvent(command.url())); return new CreateLinkResponse(linkId); } + + private LinkBundle getLinkBundle(Long linkBundleId) { + return linkBundleRepository.findById(linkBundleId) + .orElseThrow(() -> new ShoutLinkException("존재하지 않는 링크 번들입니다.", ErrorCode.NOT_FOUND)); + } } diff --git a/src/main/java/com/seong/shoutlink/domain/link/service/request/CreateLinkCommand.java b/src/main/java/com/seong/shoutlink/domain/link/service/request/CreateLinkCommand.java index 56e9bef..bfd2e2f 100644 --- a/src/main/java/com/seong/shoutlink/domain/link/service/request/CreateLinkCommand.java +++ b/src/main/java/com/seong/shoutlink/domain/link/service/request/CreateLinkCommand.java @@ -1,5 +1,5 @@ package com.seong.shoutlink.domain.link.service.request; -public record CreateLinkCommand(Long memberId, String url, String description) { +public record CreateLinkCommand(Long memberId, Long linkBundleId, String url, String description) { } diff --git a/src/main/java/com/seong/shoutlink/domain/linkbundle/LinkBundle.java b/src/main/java/com/seong/shoutlink/domain/linkbundle/LinkBundle.java index 5e99e36..ee1140f 100644 --- a/src/main/java/com/seong/shoutlink/domain/linkbundle/LinkBundle.java +++ b/src/main/java/com/seong/shoutlink/domain/linkbundle/LinkBundle.java @@ -10,12 +10,17 @@ public class LinkBundle { private Long linkBundleId; private String description; private boolean isDefault; - private Member member; + private Long memberId; public LinkBundle(String description, boolean isDefault, Member member) { + this(null, description, isDefault, member.getMemberId()); + } + + public LinkBundle(Long linkBundleId, String description, boolean isDefault, Long memberId) { + this.linkBundleId = linkBundleId; this.description = description; this.isDefault = isDefault; - this.member = member; + this.memberId = memberId; } public void initId(Long linkBundleId) { diff --git a/src/main/java/com/seong/shoutlink/domain/linkbundle/repository/LinkBundleEntity.java b/src/main/java/com/seong/shoutlink/domain/linkbundle/repository/LinkBundleEntity.java index 3a7e3ec..4399312 100644 --- a/src/main/java/com/seong/shoutlink/domain/linkbundle/repository/LinkBundleEntity.java +++ b/src/main/java/com/seong/shoutlink/domain/linkbundle/repository/LinkBundleEntity.java @@ -38,6 +38,15 @@ public static LinkBundleEntity create(LinkBundle linkBundle) { linkBundle.getLinkBundleId(), linkBundle.getDescription(), linkBundle.isDefault(), - linkBundle.getMember().getMemberId()); + linkBundle.getMemberId()); + } + + public LinkBundle toDomain() { + return new LinkBundle( + linkBundleId, + description, + isDefault, + memberId + ); } } diff --git a/src/main/java/com/seong/shoutlink/domain/linkbundle/repository/LinkBundleRepositoryImpl.java b/src/main/java/com/seong/shoutlink/domain/linkbundle/repository/LinkBundleRepositoryImpl.java index 8c10113..376974e 100644 --- a/src/main/java/com/seong/shoutlink/domain/linkbundle/repository/LinkBundleRepositoryImpl.java +++ b/src/main/java/com/seong/shoutlink/domain/linkbundle/repository/LinkBundleRepositoryImpl.java @@ -3,6 +3,7 @@ import com.seong.shoutlink.domain.linkbundle.LinkBundle; import com.seong.shoutlink.domain.linkbundle.service.LinkBundleRepository; import com.seong.shoutlink.domain.member.Member; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -24,4 +25,10 @@ public Long save(LinkBundle linkBundle) { public void updateDefaultBundleFalse(Member member) { linkBundleJpaRepository.updateDefaultBundleFalse(member.getMemberId()); } + + @Override + public Optional findById(Long linkBundleId) { + return linkBundleJpaRepository.findById(linkBundleId) + .map(LinkBundleEntity::toDomain); + } } diff --git a/src/main/java/com/seong/shoutlink/domain/linkbundle/service/LinkBundleRepository.java b/src/main/java/com/seong/shoutlink/domain/linkbundle/service/LinkBundleRepository.java index 1b2951b..5563d79 100644 --- a/src/main/java/com/seong/shoutlink/domain/linkbundle/service/LinkBundleRepository.java +++ b/src/main/java/com/seong/shoutlink/domain/linkbundle/service/LinkBundleRepository.java @@ -2,10 +2,13 @@ import com.seong.shoutlink.domain.linkbundle.LinkBundle; import com.seong.shoutlink.domain.member.Member; +import java.util.Optional; public interface LinkBundleRepository { Long save(LinkBundle linkBundle); void updateDefaultBundleFalse(Member member); + + Optional findById(Long linkBundleId); } diff --git a/src/test/java/com/seong/shoutlink/domain/link/controller/LinkControllerTest.java b/src/test/java/com/seong/shoutlink/domain/link/controller/LinkControllerTest.java index e85f921..f79333d 100644 --- a/src/test/java/com/seong/shoutlink/domain/link/controller/LinkControllerTest.java +++ b/src/test/java/com/seong/shoutlink/domain/link/controller/LinkControllerTest.java @@ -25,7 +25,7 @@ class LinkControllerTest extends BaseControllerTest { @DisplayName("성공: 링크 생성 API 호출 시") void createLink() throws Exception { //given - CreateLinkRequest request = new CreateLinkRequest("https://hseong.tistory.com/", "내 블로그"); + CreateLinkRequest request = new CreateLinkRequest(1L, "https://hseong.tistory.com/", "내 블로그"); CreateLinkResponse response = new CreateLinkResponse(1L); given(linkService.createLink(any())).willReturn(response); @@ -42,6 +42,7 @@ void createLink() throws Exception { headerWithName(AUTHORIZATION).description("사용자 액세스 토큰") ), requestFields( + fieldWithPath("linkBundleId").type(JsonFieldType.NUMBER).description("링크 번들 ID"), fieldWithPath("url").type(JsonFieldType.STRING).description("링크 url"), fieldWithPath("description").type(JsonFieldType.STRING).description("링크 설명") .optional() diff --git a/src/test/java/com/seong/shoutlink/domain/link/repository/FakeLinkRepository.java b/src/test/java/com/seong/shoutlink/domain/link/repository/FakeLinkRepository.java index 4e92cf3..69221ed 100644 --- a/src/test/java/com/seong/shoutlink/domain/link/repository/FakeLinkRepository.java +++ b/src/test/java/com/seong/shoutlink/domain/link/repository/FakeLinkRepository.java @@ -1,6 +1,7 @@ package com.seong.shoutlink.domain.link.repository; import com.seong.shoutlink.domain.link.Link; +import com.seong.shoutlink.domain.link.LinkWithLinkBundle; import com.seong.shoutlink.domain.link.service.LinkRepository; import java.util.HashMap; import java.util.Map; @@ -10,9 +11,14 @@ public class FakeLinkRepository implements LinkRepository { private final Map memory = new HashMap<>(); @Override - public Long save(Link link) { + public Long save(LinkWithLinkBundle linkWithLinkBundle) { + long nextId = getNextId(); + memory.put(nextId, linkWithLinkBundle.getLink()); + return nextId; + } + + private long getNextId() { long nextId = memory.keySet().size() + 1; - memory.put(nextId, link); return nextId; } -} \ No newline at end of file +} diff --git a/src/test/java/com/seong/shoutlink/domain/link/service/LinkServiceTest.java b/src/test/java/com/seong/shoutlink/domain/link/service/LinkServiceTest.java index 73a996d..6046d83 100644 --- a/src/test/java/com/seong/shoutlink/domain/link/service/LinkServiceTest.java +++ b/src/test/java/com/seong/shoutlink/domain/link/service/LinkServiceTest.java @@ -6,7 +6,10 @@ import com.seong.shoutlink.domain.link.repository.FakeLinkRepository; import com.seong.shoutlink.domain.link.service.request.CreateLinkCommand; import com.seong.shoutlink.domain.link.service.response.CreateLinkResponse; +import com.seong.shoutlink.domain.linkbundle.repository.FakeLinkBundleRepository; +import com.seong.shoutlink.domain.member.Member; import com.seong.shoutlink.domain.member.repository.StubMemberRepository; +import com.seong.shoutlink.fixture.LinkBundleFixture; import com.seong.shoutlink.fixture.MemberFixture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -22,14 +25,18 @@ class CreateLinkTest { private LinkService linkService; private StubMemberRepository memberRepository; private FakeLinkRepository linkRepository; + private FakeLinkBundleRepository linkBundleRepository; private StubEventPublisher eventPublisher; @BeforeEach void setUp() { - memberRepository = new StubMemberRepository(MemberFixture.member()); + Member member = MemberFixture.member(); + memberRepository = new StubMemberRepository(member); linkRepository = new FakeLinkRepository(); + linkBundleRepository = new FakeLinkBundleRepository(LinkBundleFixture.linkBundle(member)); eventPublisher = new StubEventPublisher(); - linkService = new LinkService(memberRepository, linkRepository, eventPublisher); + linkService = new LinkService(memberRepository, linkRepository, linkBundleRepository, + eventPublisher); } @Test @@ -37,7 +44,7 @@ void setUp() { void createLink() { //given CreateLinkCommand command = new CreateLinkCommand(1L, - "https://hseong.tistory.com/", "내 블로그"); + 1L, "https://hseong.tistory.com/", "내 블로그"); //when CreateLinkResponse response = linkService.createLink(command); @@ -51,7 +58,7 @@ void createLink() { void createLink_ThenPublishCreateLinkEvent() { //given CreateLinkCommand command = new CreateLinkCommand(1L, - "https://hseong.tistory.com/", "내 블로그"); + 1L, "https://hseong.tistory.com/", "내 블로그"); //when CreateLinkResponse response = linkService.createLink(command); diff --git a/src/test/java/com/seong/shoutlink/domain/linkbundle/repository/FakeLinkBundleRepository.java b/src/test/java/com/seong/shoutlink/domain/linkbundle/repository/FakeLinkBundleRepository.java index ad32cc9..e8794f4 100644 --- a/src/test/java/com/seong/shoutlink/domain/linkbundle/repository/FakeLinkBundleRepository.java +++ b/src/test/java/com/seong/shoutlink/domain/linkbundle/repository/FakeLinkBundleRepository.java @@ -6,11 +6,18 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; public class FakeLinkBundleRepository implements LinkBundleRepository { private final Map memory = new HashMap<>(); + public FakeLinkBundleRepository(LinkBundle... linkBundles) { + for (LinkBundle linkBundle : linkBundles) { + memory.put(getNextId(), linkBundle); + } + } + @Override public Long save(LinkBundle linkBundle) { long nextId = getNextId(); @@ -22,16 +29,21 @@ public Long save(LinkBundle linkBundle) { @Override public void updateDefaultBundleFalse(Member member) { List defaultLinkBundle = memory.values().stream() - .filter(linkBundle -> linkBundle.isDefault() && linkBundle.getMember().equals(member)) + .filter(linkBundle -> linkBundle.isDefault() && linkBundle.getMemberId().equals(member.getMemberId())) .toList(); defaultLinkBundle.forEach(lb -> { memory.remove(lb.getLinkBundleId()); memory.put(lb.getLinkBundleId(), - new LinkBundle(lb.getDescription(), false, lb.getMember())); + new LinkBundle(lb.getDescription(), false, member)); }); } private long getNextId() { return memory.keySet().size() + 1; } + + @Override + public Optional findById(Long linkBundleId) { + return memory.values().stream().findFirst(); + } } diff --git a/src/test/java/com/seong/shoutlink/fixture/LinkBundleFixture.java b/src/test/java/com/seong/shoutlink/fixture/LinkBundleFixture.java new file mode 100644 index 0000000..b997d20 --- /dev/null +++ b/src/test/java/com/seong/shoutlink/fixture/LinkBundleFixture.java @@ -0,0 +1,15 @@ +package com.seong.shoutlink.fixture; + +import com.seong.shoutlink.domain.linkbundle.LinkBundle; +import com.seong.shoutlink.domain.member.Member; + +public final class LinkBundleFixture { + + public static final long LINK_BUNDLE_ID = 1L; + public static final String DESCRIPTION = "기본 분류"; + public static final boolean IS_DEFAULT = true; + + public static LinkBundle linkBundle(Member member) { + return new LinkBundle(LINK_BUNDLE_ID, DESCRIPTION, IS_DEFAULT, member.getMemberId()); + } +} diff --git a/src/test/java/com/seong/shoutlink/fixture/LinkFixture.java b/src/test/java/com/seong/shoutlink/fixture/LinkFixture.java new file mode 100644 index 0000000..4bf32c0 --- /dev/null +++ b/src/test/java/com/seong/shoutlink/fixture/LinkFixture.java @@ -0,0 +1,2 @@ +package com.seong.shoutlink.fixture;public class LinkFixture { +}