From d6e24a83084f81082db4cb433ba848ec3cf3fbe7 Mon Sep 17 00:00:00 2001 From: hseong3243 Date: Thu, 28 Mar 2024 21:25:04 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EB=A7=81=ED=81=AC=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/DomainRepositoryImpl.java | 6 +++ .../domain/service/DomainRepository.java | 5 +- .../domain/domain/service/DomainService.java | 18 ++++++- .../request/FindDomainLinksCommand.java | 5 ++ .../response/FindDomainLinkResponse.java | 5 ++ .../response/FindDomainLinksResponse.java | 24 +++++++++ .../result/DomainLinkPaginationResult.java | 11 ++++ .../service/result/DomainLinkResult.java | 5 ++ .../repository/StubDomainRepository.java | 18 +++++++ .../domain/service/DomainServiceTest.java | 50 +++++++++++++++++++ 10 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/seong/shoutlink/domain/domain/service/request/FindDomainLinksCommand.java create mode 100644 src/main/java/com/seong/shoutlink/domain/domain/service/response/FindDomainLinkResponse.java create mode 100644 src/main/java/com/seong/shoutlink/domain/domain/service/response/FindDomainLinksResponse.java create mode 100644 src/main/java/com/seong/shoutlink/domain/domain/service/result/DomainLinkPaginationResult.java create mode 100644 src/main/java/com/seong/shoutlink/domain/domain/service/result/DomainLinkResult.java diff --git a/src/main/java/com/seong/shoutlink/domain/domain/repository/DomainRepositoryImpl.java b/src/main/java/com/seong/shoutlink/domain/domain/repository/DomainRepositoryImpl.java index 900a091..5f9b19f 100644 --- a/src/main/java/com/seong/shoutlink/domain/domain/repository/DomainRepositoryImpl.java +++ b/src/main/java/com/seong/shoutlink/domain/domain/repository/DomainRepositoryImpl.java @@ -2,6 +2,7 @@ import com.seong.shoutlink.domain.domain.Domain; import com.seong.shoutlink.domain.domain.service.DomainRepository; +import com.seong.shoutlink.domain.domain.service.result.DomainLinkPaginationResult; import java.util.List; import com.seong.shoutlink.domain.domain.service.result.DomainPaginationResult; import java.util.Optional; @@ -54,4 +55,9 @@ public Optional findById(Long domainId) { return domainJpaRepository.findById(domainId) .map(DomainEntity::toDomain); } + + @Override + public DomainLinkPaginationResult findDomainLinks(Domain domain, int page, int size) { + return null; + } } diff --git a/src/main/java/com/seong/shoutlink/domain/domain/service/DomainRepository.java b/src/main/java/com/seong/shoutlink/domain/domain/service/DomainRepository.java index 4e98141..701d5d5 100644 --- a/src/main/java/com/seong/shoutlink/domain/domain/service/DomainRepository.java +++ b/src/main/java/com/seong/shoutlink/domain/domain/service/DomainRepository.java @@ -1,8 +1,9 @@ package com.seong.shoutlink.domain.domain.service; import com.seong.shoutlink.domain.domain.Domain; -import java.util.List; +import com.seong.shoutlink.domain.domain.service.result.DomainLinkPaginationResult; import com.seong.shoutlink.domain.domain.service.result.DomainPaginationResult; +import java.util.List; import java.util.Optional; public interface DomainRepository { @@ -18,4 +19,6 @@ public interface DomainRepository { DomainPaginationResult findDomains(String keyword, int page, int size); Optional findById(Long domainId); + + DomainLinkPaginationResult findDomainLinks(Domain domain, int page, int size); } diff --git a/src/main/java/com/seong/shoutlink/domain/domain/service/DomainService.java b/src/main/java/com/seong/shoutlink/domain/domain/service/DomainService.java index e8d8c27..3699550 100644 --- a/src/main/java/com/seong/shoutlink/domain/domain/service/DomainService.java +++ b/src/main/java/com/seong/shoutlink/domain/domain/service/DomainService.java @@ -2,13 +2,16 @@ import com.seong.shoutlink.domain.domain.Domain; import com.seong.shoutlink.domain.domain.service.request.FindDomainCommand; +import com.seong.shoutlink.domain.domain.service.request.FindDomainLinksCommand; import com.seong.shoutlink.domain.domain.service.request.FindDomainsCommand; import com.seong.shoutlink.domain.domain.service.request.FindRootDomainsCommand; import com.seong.shoutlink.domain.domain.service.request.UpdateDomainCommand; import com.seong.shoutlink.domain.domain.service.response.FindDomainDetailResponse; +import com.seong.shoutlink.domain.domain.service.response.FindDomainLinksResponse; import com.seong.shoutlink.domain.domain.service.response.FindDomainsResponse; import com.seong.shoutlink.domain.domain.service.response.FindRootDomainsResponse; import com.seong.shoutlink.domain.domain.service.response.UpdateDomainResponse; +import com.seong.shoutlink.domain.domain.service.result.DomainLinkPaginationResult; import com.seong.shoutlink.domain.domain.service.result.DomainPaginationResult; import com.seong.shoutlink.domain.domain.util.DomainExtractor; import com.seong.shoutlink.domain.exception.ErrorCode; @@ -56,8 +59,19 @@ public FindDomainsResponse findDomains(FindDomainsCommand command) { } public FindDomainDetailResponse findDomain(FindDomainCommand command) { - Domain domain = domainRepository.findById(command.domainId()) - .orElseThrow(() -> new ShoutLinkException("존재하지 않는 도메인입니다.", ErrorCode.NOT_FOUND)); + Domain domain = getDomain(command.domainId()); return FindDomainDetailResponse.from(domain); } + + public FindDomainLinksResponse findDomainLinks(FindDomainLinksCommand command) { + Domain domain = getDomain(command.domainId()); + DomainLinkPaginationResult result + = domainRepository.findDomainLinks(domain, command.page(), command.size()); + return FindDomainLinksResponse.of(result.links(), result.totalElements(), result.hasNext()); + } + + private Domain getDomain(Long domainId) { + return domainRepository.findById(domainId) + .orElseThrow(() -> new ShoutLinkException("존재하지 않는 도메인입니다.", ErrorCode.NOT_FOUND)); + } } diff --git a/src/main/java/com/seong/shoutlink/domain/domain/service/request/FindDomainLinksCommand.java b/src/main/java/com/seong/shoutlink/domain/domain/service/request/FindDomainLinksCommand.java new file mode 100644 index 0000000..5dac15a --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/domain/service/request/FindDomainLinksCommand.java @@ -0,0 +1,5 @@ +package com.seong.shoutlink.domain.domain.service.request; + +public record FindDomainLinksCommand(Long domainId, int page, int size) { + +} diff --git a/src/main/java/com/seong/shoutlink/domain/domain/service/response/FindDomainLinkResponse.java b/src/main/java/com/seong/shoutlink/domain/domain/service/response/FindDomainLinkResponse.java new file mode 100644 index 0000000..107f3f2 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/domain/service/response/FindDomainLinkResponse.java @@ -0,0 +1,5 @@ +package com.seong.shoutlink.domain.domain.service.response; + +public record FindDomainLinkResponse(Long linkId, String url, long aggregationCount) { + +} diff --git a/src/main/java/com/seong/shoutlink/domain/domain/service/response/FindDomainLinksResponse.java b/src/main/java/com/seong/shoutlink/domain/domain/service/response/FindDomainLinksResponse.java new file mode 100644 index 0000000..171b4f7 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/domain/service/response/FindDomainLinksResponse.java @@ -0,0 +1,24 @@ +package com.seong.shoutlink.domain.domain.service.response; + +import com.seong.shoutlink.domain.domain.service.result.DomainLinkResult; +import java.util.List; + +public record FindDomainLinksResponse( + List links, + long totalElements, + boolean hasNext) { + + + public static FindDomainLinksResponse of( + List links, + long totalElements, + boolean hasNext) { + List content = links.stream() + .map(link -> new FindDomainLinkResponse( + link.linkId(), + link.url(), + link.aggregationCount()) + ).toList(); + return new FindDomainLinksResponse(content, totalElements, hasNext); + } +} diff --git a/src/main/java/com/seong/shoutlink/domain/domain/service/result/DomainLinkPaginationResult.java b/src/main/java/com/seong/shoutlink/domain/domain/service/result/DomainLinkPaginationResult.java new file mode 100644 index 0000000..f2d98c8 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/domain/service/result/DomainLinkPaginationResult.java @@ -0,0 +1,11 @@ +package com.seong.shoutlink.domain.domain.service.result; + +import java.util.List; + +public record DomainLinkPaginationResult( + List links, + long totalElements, + boolean hasNext) { + + +} diff --git a/src/main/java/com/seong/shoutlink/domain/domain/service/result/DomainLinkResult.java b/src/main/java/com/seong/shoutlink/domain/domain/service/result/DomainLinkResult.java new file mode 100644 index 0000000..1b7fdf5 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/domain/service/result/DomainLinkResult.java @@ -0,0 +1,5 @@ +package com.seong.shoutlink.domain.domain.service.result; + +public record DomainLinkResult(Long linkId, String url, long aggregationCount) { + +} diff --git a/src/test/java/com/seong/shoutlink/domain/domain/repository/StubDomainRepository.java b/src/test/java/com/seong/shoutlink/domain/domain/repository/StubDomainRepository.java index 1e27318..7d5585a 100644 --- a/src/test/java/com/seong/shoutlink/domain/domain/repository/StubDomainRepository.java +++ b/src/test/java/com/seong/shoutlink/domain/domain/repository/StubDomainRepository.java @@ -3,7 +3,10 @@ import com.seong.shoutlink.domain.common.Trie; import com.seong.shoutlink.domain.domain.Domain; import com.seong.shoutlink.domain.domain.service.DomainRepository; +import com.seong.shoutlink.domain.domain.service.result.DomainLinkPaginationResult; +import com.seong.shoutlink.domain.domain.service.result.DomainLinkResult; import com.seong.shoutlink.domain.domain.service.result.DomainPaginationResult; +import com.seong.shoutlink.domain.link.Link; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -12,6 +15,7 @@ public class StubDomainRepository implements DomainRepository { private final Map memory = new HashMap<>(); + private final Map> domainLinks = new HashMap<>(); private final Trie searchAutoComplete = new Trie(); public void stub(Domain domain) { @@ -19,6 +23,11 @@ public void stub(Domain domain) { searchAutoComplete.insert(domain.getRootDomain()); } + public void stub(Domain domain, Link... links) { + memory.put(nextId(), domain); + domainLinks.put(domain, List.of(links)); + } + @Override public Optional findByRootDomain(String rootDomain) { return memory.values().stream() @@ -54,6 +63,15 @@ private Long nextId() { return (long) (memory.size() + 1); } + @Override + public DomainLinkPaginationResult findDomainLinks(Domain domain, int page, int size) { + List links = domainLinks.get(domain); + List content = links.stream() + .map(link -> new DomainLinkResult(link.getLinkId(), link.getUrl(), links.size())) + .toList(); + return new DomainLinkPaginationResult(content, links.size(), links.size() > size); + } + @Override public List findRootDomains(String keyword, int size) { return searchAutoComplete.search(keyword, size); diff --git a/src/test/java/com/seong/shoutlink/domain/domain/service/DomainServiceTest.java b/src/test/java/com/seong/shoutlink/domain/domain/service/DomainServiceTest.java index a615399..fe5ca67 100644 --- a/src/test/java/com/seong/shoutlink/domain/domain/service/DomainServiceTest.java +++ b/src/test/java/com/seong/shoutlink/domain/domain/service/DomainServiceTest.java @@ -6,10 +6,12 @@ import com.seong.shoutlink.domain.domain.Domain; import com.seong.shoutlink.domain.domain.repository.StubDomainRepository; import com.seong.shoutlink.domain.domain.service.request.FindDomainCommand; +import com.seong.shoutlink.domain.domain.service.request.FindDomainLinksCommand; import com.seong.shoutlink.domain.domain.service.request.FindDomainsCommand; import com.seong.shoutlink.domain.domain.service.request.FindRootDomainsCommand; import com.seong.shoutlink.domain.domain.service.request.UpdateDomainCommand; import com.seong.shoutlink.domain.domain.service.response.FindDomainDetailResponse; +import com.seong.shoutlink.domain.domain.service.response.FindDomainLinksResponse; import com.seong.shoutlink.domain.domain.service.response.FindDomainsResponse; import com.seong.shoutlink.domain.domain.service.response.FindRootDomainsResponse; import com.seong.shoutlink.domain.domain.service.response.UpdateDomainResponse; @@ -190,4 +192,52 @@ void notFound_whenDomainNotFound() { .isEqualTo(ErrorCode.NOT_FOUND); } } + + @Nested + @DisplayName("findDomainLinks 호출 시") + class FindDomainLinksTest { + + @BeforeEach + void setUp() { + domainRepository = new StubDomainRepository(); + linkRepository = new FakeLinkRepository(); + domainService = new DomainService(domainRepository, linkRepository); + } + + @Test + @DisplayName("성공: 도메인 링크 목록을 조회한다.") + void findDomainLinks() { + //given + FindDomainLinksCommand command = new FindDomainLinksCommand(1L, 0, 10); + Link link = LinkFixture.link(); + Domain domain = DomainFixture.domain(); + domainRepository.stub(domain, link); + + //when + FindDomainLinksResponse response = domainService.findDomainLinks(command); + + //then + assertThat(response.links()).hasSize(1) + .allSatisfy(findLink -> { + assertThat(findLink.linkId()).isEqualTo(link.getLinkId()); + assertThat(findLink.url()).isEqualTo(link.getUrl()); + assertThat(findLink.aggregationCount()).isEqualTo(1); + }); + } + + @Test + @DisplayName("예외(notFound): 존재하지 않는 도메인") + void notFound_WhenDomainNotFound() { + //given + FindDomainLinksCommand command = new FindDomainLinksCommand(1L, 0, 10); + + //when + Exception exception = catchException(() -> domainService.findDomainLinks(command)); + + //then + assertThat(exception).isInstanceOf(ShoutLinkException.class) + .extracting(e -> ((ShoutLinkException) e).getErrorCode()) + .isEqualTo(ErrorCode.NOT_FOUND); + } + } } \ No newline at end of file From c9a6a2e91597fb5715fbfebe54efa80a6fdb7176 Mon Sep 17 00:00:00 2001 From: hseong3243 Date: Thu, 28 Mar 2024 22:08:39 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EB=A7=81=ED=81=AC=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=98=81=EC=86=8D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/repository/DomainRepositoryImpl.java | 11 ++++++++++- .../domain/link/repository/LinkJpaRepository.java | 10 ++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/seong/shoutlink/domain/domain/repository/DomainRepositoryImpl.java b/src/main/java/com/seong/shoutlink/domain/domain/repository/DomainRepositoryImpl.java index 5f9b19f..e1e65a0 100644 --- a/src/main/java/com/seong/shoutlink/domain/domain/repository/DomainRepositoryImpl.java +++ b/src/main/java/com/seong/shoutlink/domain/domain/repository/DomainRepositoryImpl.java @@ -3,6 +3,8 @@ import com.seong.shoutlink.domain.domain.Domain; import com.seong.shoutlink.domain.domain.service.DomainRepository; import com.seong.shoutlink.domain.domain.service.result.DomainLinkPaginationResult; +import com.seong.shoutlink.domain.domain.service.result.DomainLinkResult; +import com.seong.shoutlink.domain.link.repository.LinkJpaRepository; import java.util.List; import com.seong.shoutlink.domain.domain.service.result.DomainPaginationResult; import java.util.Optional; @@ -17,6 +19,7 @@ public class DomainRepositoryImpl implements DomainRepository { private final DomainJpaRepository domainJpaRepository; private final DomainCacheRepository domainCacheRepository; + private final LinkJpaRepository linkJpaRepository; @Override public Optional findByRootDomain(String rootDomain) { @@ -58,6 +61,12 @@ public Optional findById(Long domainId) { @Override public DomainLinkPaginationResult findDomainLinks(Domain domain, int page, int size) { - return null; + PageRequest pageRequest = PageRequest.of(page, size); + Page domainLinks + = linkJpaRepository.findDomainLinks(domain.getDomainId(), pageRequest); + return new DomainLinkPaginationResult( + domainLinks.getContent(), + domainLinks.getTotalElements(), + domainLinks.hasNext()); } } diff --git a/src/main/java/com/seong/shoutlink/domain/link/repository/LinkJpaRepository.java b/src/main/java/com/seong/shoutlink/domain/link/repository/LinkJpaRepository.java index 89600be..54f47a9 100644 --- a/src/main/java/com/seong/shoutlink/domain/link/repository/LinkJpaRepository.java +++ b/src/main/java/com/seong/shoutlink/domain/link/repository/LinkJpaRepository.java @@ -1,10 +1,20 @@ package com.seong.shoutlink.domain.link.repository; +import com.seong.shoutlink.domain.domain.service.result.DomainLinkResult; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface LinkJpaRepository extends JpaRepository { Page findAllByLinkBundleId(Long linkBundleId, Pageable pageable); + + @Query("select new com.seong.shoutlink.domain.domain.service.result.DomainLinkResult(l.linkId, l.url, count(l.url))" + + " from LinkEntity l" + + " where l.domainId = :domainId" + + " group by l.url" + + " order by count(l.url) desc") + Page findDomainLinks(@Param("domainId") Long domainId, Pageable pageable); } From 92019f048931869478aa158d40fdacc457fe15b4 Mon Sep 17 00:00:00 2001 From: hseong3243 Date: Thu, 28 Mar 2024 22:24:26 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EB=A7=81=ED=81=AC=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 10 ++ .../domain/controller/DomainController.java | 12 ++ .../request/FindDomainLinksRequest.java | 17 ++ src/main/resources/static/docs/index.html | 158 ++++++++++++++++-- .../controller/DomainControllerTest.java | 39 +++++ 5 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/seong/shoutlink/domain/domain/controller/request/FindDomainLinksRequest.java diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 3ab2000..c544eb9 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -172,3 +172,13 @@ operation::domain-controller-test/find-domain[snippets='http-request,path-parame ==== response operation::domain-controller-test/find-domain[snippets='http-response,response-fields'] + +=== 도메인 링크 목록 조회 + +==== request + +operation::domain-controller-test/find-domain-links[snippets='http-request,path-parameters,query-parameters'] + +==== response + +operation::domain-controller-test/find-domain-links[snippets='http-response,response-fields'] diff --git a/src/main/java/com/seong/shoutlink/domain/domain/controller/DomainController.java b/src/main/java/com/seong/shoutlink/domain/domain/controller/DomainController.java index 17f16e6..8fcceea 100644 --- a/src/main/java/com/seong/shoutlink/domain/domain/controller/DomainController.java +++ b/src/main/java/com/seong/shoutlink/domain/domain/controller/DomainController.java @@ -1,12 +1,15 @@ package com.seong.shoutlink.domain.domain.controller; +import com.seong.shoutlink.domain.domain.controller.request.FindDomainLinksRequest; import com.seong.shoutlink.domain.domain.controller.request.FindDomainsRequest; import com.seong.shoutlink.domain.domain.controller.request.FindRootDomainsRequest; import com.seong.shoutlink.domain.domain.service.DomainService; import com.seong.shoutlink.domain.domain.service.request.FindDomainCommand; +import com.seong.shoutlink.domain.domain.service.request.FindDomainLinksCommand; import com.seong.shoutlink.domain.domain.service.request.FindDomainsCommand; import com.seong.shoutlink.domain.domain.service.request.FindRootDomainsCommand; import com.seong.shoutlink.domain.domain.service.response.FindDomainDetailResponse; +import com.seong.shoutlink.domain.domain.service.response.FindDomainLinksResponse; import com.seong.shoutlink.domain.domain.service.response.FindDomainsResponse; import com.seong.shoutlink.domain.domain.service.response.FindRootDomainsResponse; import jakarta.validation.Valid; @@ -48,4 +51,13 @@ public ResponseEntity findDomain( = domainService.findDomain(new FindDomainCommand(domainId)); return ResponseEntity.ok(response); } + + @GetMapping("/{domainId}/links") + public ResponseEntity findDomainLinks( + @PathVariable("domainId") Long domainId, + @ModelAttribute @Valid FindDomainLinksRequest request) { + FindDomainLinksResponse response = domainService.findDomainLinks( + new FindDomainLinksCommand(domainId, request.page(), request.size())); + return ResponseEntity.ok(response); + } } diff --git a/src/main/java/com/seong/shoutlink/domain/domain/controller/request/FindDomainLinksRequest.java b/src/main/java/com/seong/shoutlink/domain/domain/controller/request/FindDomainLinksRequest.java new file mode 100644 index 0000000..41f3473 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/domain/controller/request/FindDomainLinksRequest.java @@ -0,0 +1,17 @@ +package com.seong.shoutlink.domain.domain.controller.request; + +import jakarta.validation.constraints.Min; +import java.util.Objects; +import org.hibernate.validator.constraints.Range; + +public record FindDomainLinksRequest( + @Min(value = 0, message = "페이지는 음수일 수 없습니다.") + Integer page, + @Range(min = 1, max = 100, message = "사이즈는 1 이상 100 이하여야 합니다.") + Integer size) { + + public FindDomainLinksRequest(Integer page, Integer size) { + this.page = Objects.isNull(page) ? 0 : page; + this.size = Objects.isNull(size) ? 10 : size; + } +} diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index e9d92d0..09360f8 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -484,6 +484,7 @@

API 문서

  • 루트 도메인 목록 조회
  • 도메인 목록 조회
  • 도메인 단건 조회
  • +
  • 도메인 링크 목록 조회
  • @@ -716,7 +717,7 @@
    POST /api/link-bundles HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjI2NDM5LCJzdWIiOiIxIiwiZXhwIjoxNzExNjMwMDM5LCJyb2xlIjoiUk9MRV9VU0VSIn0.RiQMc9pgMkFKxG0oZO6q6R_HjPrHZeRMjOTegxbDwAc
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjMxOTM1LCJzdWIiOiIxIiwiZXhwIjoxNzExNjM1NTM1LCJyb2xlIjoiUk9MRV9VU0VSIn0.0V1ZcvM6a1HPSMSbXJ2jVNijQKWJnCNVJrWnQNXG788
     Content-Length: 57
     Host: localhost:8080
     
    @@ -832,7 +833,7 @@ 
    GET /api/link-bundles HTTP/1.1
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjI2NDM5LCJzdWIiOiIxIiwiZXhwIjoxNzExNjMwMDM5LCJyb2xlIjoiUk9MRV9VU0VSIn0.RiQMc9pgMkFKxG0oZO6q6R_HjPrHZeRMjOTegxbDwAc
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjMxOTM1LCJzdWIiOiIxIiwiZXhwIjoxNzExNjM1NTM1LCJyb2xlIjoiUk9MRV9VU0VSIn0.0V1ZcvM6a1HPSMSbXJ2jVNijQKWJnCNVJrWnQNXG788
     Host: localhost:8080
    @@ -937,7 +938,7 @@
    POST /api/hubs/1/link-bundles HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjI2NDM5LCJzdWIiOiIxIiwiZXhwIjoxNzExNjMwMDM5LCJyb2xlIjoiUk9MRV9VU0VSIn0.RiQMc9pgMkFKxG0oZO6q6R_HjPrHZeRMjOTegxbDwAc
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjMxOTM1LCJzdWIiOiIxIiwiZXhwIjoxNzExNjM1NTM1LCJyb2xlIjoiUk9MRV9VU0VSIn0.0V1ZcvM6a1HPSMSbXJ2jVNijQKWJnCNVJrWnQNXG788
     Content-Length: 53
     Host: localhost:8080
     
    @@ -1075,7 +1076,7 @@ 
    GET /api/hubs/1/link-bundles HTTP/1.1
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjI2NDM5LCJzdWIiOiIxIiwiZXhwIjoxNzExNjMwMDM5LCJyb2xlIjoiUk9MRV9VU0VSIn0.RiQMc9pgMkFKxG0oZO6q6R_HjPrHZeRMjOTegxbDwAc
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjMxOTM1LCJzdWIiOiIxIiwiZXhwIjoxNzExNjM1NTM1LCJyb2xlIjoiUk9MRV9VU0VSIn0.0V1ZcvM6a1HPSMSbXJ2jVNijQKWJnCNVJrWnQNXG788
     Host: localhost:8080
    @@ -1203,7 +1204,7 @@
    POST /api/links HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjI2NDM5LCJzdWIiOiIxIiwiZXhwIjoxNzExNjMwMDM5LCJyb2xlIjoiUk9MRV9VU0VSIn0.RiQMc9pgMkFKxG0oZO6q6R_HjPrHZeRMjOTegxbDwAc
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjMxOTM1LCJzdWIiOiIxIiwiZXhwIjoxNzExNjM1NTM1LCJyb2xlIjoiUk9MRV9VU0VSIn0.0V1ZcvM6a1HPSMSbXJ2jVNijQKWJnCNVJrWnQNXG788
     Content-Length: 100
     Host: localhost:8080
     
    @@ -1325,7 +1326,7 @@ 
    GET /api/links?linkBundleId=1&page=0&size=10 HTTP/1.1
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjI2NDM5LCJzdWIiOiIxIiwiZXhwIjoxNzExNjMwMDM5LCJyb2xlIjoiUk9MRV9VU0VSIn0.RiQMc9pgMkFKxG0oZO6q6R_HjPrHZeRMjOTegxbDwAc
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjMxOTM1LCJzdWIiOiIxIiwiZXhwIjoxNzExNjM1NTM1LCJyb2xlIjoiUk9MRV9VU0VSIn0.0V1ZcvM6a1HPSMSbXJ2jVNijQKWJnCNVJrWnQNXG788
     Host: localhost:8080
    @@ -1392,7 +1393,7 @@
    POST /api/hubs/1/links HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjI2NDM5LCJzdWIiOiIxIiwiZXhwIjoxNzExNjMwMDM5LCJyb2xlIjoiUk9MRV9VU0VSIn0.RiQMc9pgMkFKxG0oZO6q6R_HjPrHZeRMjOTegxbDwAc
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjMxOTM1LCJzdWIiOiIxIiwiZXhwIjoxNzExNjM1NTM1LCJyb2xlIjoiUk9MRV9VU0VSIn0.0V1ZcvM6a1HPSMSbXJ2jVNijQKWJnCNVJrWnQNXG788
     Content-Length: 69
     Host: localhost:8080
     
    @@ -1536,7 +1537,7 @@ 
    GET /api/hubs/1/links?linkBundleId=1&page=0&size=20 HTTP/1.1
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjI2NDM5LCJzdWIiOiIxIiwiZXhwIjoxNzExNjMwMDM5LCJyb2xlIjoiUk9MRV9VU0VSIn0.RiQMc9pgMkFKxG0oZO6q6R_HjPrHZeRMjOTegxbDwAc
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjMxOTM1LCJzdWIiOiIxIiwiZXhwIjoxNzExNjM1NTM1LCJyb2xlIjoiUk9MRV9VU0VSIn0.0V1ZcvM6a1HPSMSbXJ2jVNijQKWJnCNVJrWnQNXG788
     Host: localhost:8080
    @@ -1705,7 +1706,7 @@
    POST /api/hubs HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjI2NDM5LCJzdWIiOiIxIiwiZXhwIjoxNzExNjMwMDM5LCJyb2xlIjoiUk9MRV9VU0VSIn0.RiQMc9pgMkFKxG0oZO6q6R_HjPrHZeRMjOTegxbDwAc
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzExNjMxOTM1LCJzdWIiOiIxIiwiZXhwIjoxNzExNjM1NTM1LCJyb2xlIjoiUk9MRV9VU0VSIn0.0V1ZcvM6a1HPSMSbXJ2jVNijQKWJnCNVJrWnQNXG788
     Content-Length: 88
     Host: localhost:8080
     
    @@ -2297,13 +2298,150 @@ 
    +

    도메인 링크 목록 조회

    +
    +

    request

    +
    +
    HTTP request
    +
    +
    +
    GET /api/domains/1/links?page=0&size=10 HTTP/1.1
    +Host: localhost:8080
    +
    +
    +
    +
    +
    Path parameters
    + + ++++ + + + + + + + + + + + + +
    Table 1. /api/domains/{domainId}/links
    ParameterDescription

    domainId

    도메인 ID

    +
    +
    +
    Query parameters
    + ++++ + + + + + + + + + + + + + + + + +
    ParameterDescription

    page

    페이지

    size

    사이즈

    +
    +
    +
    +

    response

    +
    +
    HTTP response
    +
    +
    +
    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: 151
    +
    +{
    +  "links" : [ {
    +    "linkId" : 1,
    +    "url" : "github.com/hseong3243",
    +    "aggregationCount" : 1
    +  } ],
    +  "totalElements" : 1,
    +  "hasNext" : false
    +}
    +
    +
    +
    +
    +
    Response fields
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PathTypeDescription

    links

    Array

    링크 목록

    links[].linkId

    Number

    링크 ID

    links[].url

    String

    링크 url

    links[].aggregationCount

    Number

    링크 집계 카운트

    totalElements

    Number

    총 요소 개수

    hasNext

    Boolean

    다음 페이지 여부

    +
    +
    + diff --git a/src/test/java/com/seong/shoutlink/domain/domain/controller/DomainControllerTest.java b/src/test/java/com/seong/shoutlink/domain/domain/controller/DomainControllerTest.java index ebd27f0..42a258e 100644 --- a/src/test/java/com/seong/shoutlink/domain/domain/controller/DomainControllerTest.java +++ b/src/test/java/com/seong/shoutlink/domain/domain/controller/DomainControllerTest.java @@ -16,6 +16,8 @@ import com.seong.shoutlink.base.BaseControllerTest; import com.seong.shoutlink.domain.domain.service.response.FindDomainDetailResponse; +import com.seong.shoutlink.domain.domain.service.response.FindDomainLinkResponse; +import com.seong.shoutlink.domain.domain.service.response.FindDomainLinksResponse; import com.seong.shoutlink.domain.domain.service.response.FindDomainResponse; import com.seong.shoutlink.domain.domain.service.response.FindDomainsResponse; import com.seong.shoutlink.domain.domain.service.response.FindRootDomainsResponse; @@ -113,4 +115,41 @@ void findDomain() throws Exception { ) )); } + + @Test + @DisplayName("성공: 도메인 링크 목록 조회 api 호출 시") + void findDomainLinks() throws Exception { + //given + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("page", "0"); + params.add("size", "10"); + + FindDomainLinkResponse content = new FindDomainLinkResponse(1L, "github.com/hseong3243", 1); + FindDomainLinksResponse response = new FindDomainLinksResponse(List.of(content), 1L, false); + given(domainService.findDomainLinks(any())).willReturn(response); + + //when + ResultActions resultActions = mockMvc.perform(get("/api/domains/{domainId}/links", 1L) + .params(params)); + + //then + resultActions.andExpect(status().isOk()) + .andDo(restDocs.document( + pathParameters( + parameterWithName("domainId").description("도메인 ID") + ), + queryParameters( + parameterWithName("page").description("페이지"), + parameterWithName("size").description("사이즈") + ), + responseFields( + fieldWithPath("links").type(ARRAY).description("링크 목록"), + fieldWithPath("links[].linkId").type(NUMBER).description("링크 ID"), + fieldWithPath("links[].url").type(STRING).description("링크 url"), + fieldWithPath("links[].aggregationCount").type(NUMBER).description("링크 집계 카운트"), + fieldWithPath("totalElements").type(NUMBER).description("총 요소 개수"), + fieldWithPath("hasNext").type(BOOLEAN).description("다음 페이지 여부") + ) + )); + } } \ No newline at end of file