diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index c9d7a1f..961b467 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -109,6 +109,16 @@ operation::link-controller-test/find-hub-links[snippets='http-request,request-he operation::link-controller-test/find-hub-links[snippets='http-response,response-fields'] +=== 회원 링크 삭제 + +==== request + +operation::link-controller-test/delete-link[snippets='http-request,request-headers,path-parameters'] + +==== response + +operation::link-controller-test/delete-link[snippets='http-response,response-fields'] + == 허브 === 허브 생성 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 1f0443f..8291547 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 @@ -7,15 +7,18 @@ import com.seong.shoutlink.domain.link.service.LinkUseCase; import com.seong.shoutlink.domain.link.service.request.CreateHubLinkCommand; import com.seong.shoutlink.domain.link.service.request.CreateLinkCommand; +import com.seong.shoutlink.domain.link.service.request.DeleteLinkCommand; import com.seong.shoutlink.domain.link.service.request.FindHubLinksCommand; import com.seong.shoutlink.domain.link.service.request.FindLinksCommand; import com.seong.shoutlink.domain.link.service.response.CreateHubLinkResponse; import com.seong.shoutlink.domain.link.service.response.CreateLinkResponse; +import com.seong.shoutlink.domain.link.service.response.DeleteLinkResponse; import com.seong.shoutlink.domain.link.service.response.FindLinksResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; @@ -82,4 +85,13 @@ public ResponseEntity findHubLinks( request.size())); return ResponseEntity.ok(response); } + + @DeleteMapping("/links/{linkId}") + public ResponseEntity deleteLink( + @LoginUser Long memberId, + @PathVariable("linkId") Long linkId) { + DeleteLinkResponse deleteLinkResponse = linkUseCase.deleteLink( + new DeleteLinkCommand(memberId, linkId)); + return ResponseEntity.ok(deleteLinkResponse); + } } \ No newline at end of file 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 1a40e97..3263128 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 @@ -2,6 +2,7 @@ import com.seong.shoutlink.domain.domain.service.result.DomainLinkResult; import java.util.List; +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -20,4 +21,9 @@ public interface LinkJpaRepository extends JpaRepository { Page findDomainLinks(@Param("domainId") Long domainId, Pageable pageable); List findAllByLinkBundleIdIn(List linkBundleIds); + + @Query("select l from LinkEntity l " + + "join MemberLinkBundleEntity lb on l.linkBundleId = lb.linkBundleId " + + "where l.linkId = :linkId and lb.memberId = :memberId") + Optional findByIdAndMemberId(@Param("linkId") Long linkId, @Param("memberId") Long memberId); } 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 8d645eb..383b0ee 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 @@ -6,6 +6,7 @@ import com.seong.shoutlink.domain.link.service.LinkRepository; import com.seong.shoutlink.domain.link.service.result.LinkPaginationResult; import com.seong.shoutlink.domain.linkbundle.LinkBundle; +import com.seong.shoutlink.domain.member.Member; import java.util.List; import java.util.Map; import java.util.Optional; @@ -68,4 +69,15 @@ public List findAllByLinkBundlesIn(List linkBund linkBundleIdAndLinkBundle.get(linkEntity.getLinkBundleId()))) .toList(); } + + @Override + public Optional findMemberLink(Long linkId, Member member) { + return linkJpaRepository.findByIdAndMemberId(linkId, member.getMemberId()) + .map(LinkEntity::toDomain); + } + + @Override + public void delete(Link link) { + linkJpaRepository.deleteById(link.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 79e37c5..7e092a3 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 @@ -5,6 +5,7 @@ import com.seong.shoutlink.domain.link.LinkWithLinkBundle; import com.seong.shoutlink.domain.link.service.result.LinkPaginationResult; import com.seong.shoutlink.domain.linkbundle.LinkBundle; +import com.seong.shoutlink.domain.member.Member; import java.util.List; import java.util.Optional; @@ -19,4 +20,8 @@ public interface LinkRepository { Optional findById(Long linkId); List findAllByLinkBundlesIn(List linkBundles); + + Optional findMemberLink(Long linkId, Member member); + + void delete(Link link); } 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 fc92385..3a4eba3 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 @@ -12,10 +12,12 @@ import com.seong.shoutlink.domain.link.service.event.CreateMemberLinkEvent; import com.seong.shoutlink.domain.link.service.request.CreateHubLinkCommand; import com.seong.shoutlink.domain.link.service.request.CreateLinkCommand; +import com.seong.shoutlink.domain.link.service.request.DeleteLinkCommand; import com.seong.shoutlink.domain.link.service.request.FindHubLinksCommand; import com.seong.shoutlink.domain.link.service.request.FindLinksCommand; import com.seong.shoutlink.domain.link.service.response.CreateHubLinkResponse; import com.seong.shoutlink.domain.link.service.response.CreateLinkResponse; +import com.seong.shoutlink.domain.link.service.response.DeleteLinkResponse; import com.seong.shoutlink.domain.link.service.response.FindLinksResponse; import com.seong.shoutlink.domain.link.service.result.LinkPaginationResult; import com.seong.shoutlink.domain.linkbundle.LinkBundle; @@ -103,6 +105,15 @@ public FindLinksResponse findHubLinks(FindHubLinksCommand command) { return FindLinksResponse.of(result.links(), result.totalElements(), result.hasNext()); } + @Override + public DeleteLinkResponse deleteLink(DeleteLinkCommand command) { + Member member = getMember(command.memberId()); + Link link = linkRepository.findMemberLink(command.linkId(), member) + .orElseThrow(() -> new ShoutLinkException("존재하지 않는 링크입니다.", ErrorCode.NOT_FOUND)); + linkRepository.delete(link); + return new DeleteLinkResponse(link.getLinkId()); + } + private void checkAuthenticated(@Nullable Long nullableMemberId) { if(Objects.isNull(nullableMemberId)) { throw new ShoutLinkException("인증되지 않은 사용자입니다.", ErrorCode.UNAUTHENTICATED); diff --git a/src/main/java/com/seong/shoutlink/domain/link/service/LinkUseCase.java b/src/main/java/com/seong/shoutlink/domain/link/service/LinkUseCase.java index 92a665d..656c829 100644 --- a/src/main/java/com/seong/shoutlink/domain/link/service/LinkUseCase.java +++ b/src/main/java/com/seong/shoutlink/domain/link/service/LinkUseCase.java @@ -2,10 +2,12 @@ import com.seong.shoutlink.domain.link.service.request.CreateHubLinkCommand; import com.seong.shoutlink.domain.link.service.request.CreateLinkCommand; +import com.seong.shoutlink.domain.link.service.request.DeleteLinkCommand; import com.seong.shoutlink.domain.link.service.request.FindHubLinksCommand; import com.seong.shoutlink.domain.link.service.request.FindLinksCommand; import com.seong.shoutlink.domain.link.service.response.CreateHubLinkResponse; import com.seong.shoutlink.domain.link.service.response.CreateLinkResponse; +import com.seong.shoutlink.domain.link.service.response.DeleteLinkResponse; import com.seong.shoutlink.domain.link.service.response.FindLinksResponse; public interface LinkUseCase { @@ -17,4 +19,6 @@ public interface LinkUseCase { CreateHubLinkResponse createHubLink(CreateHubLinkCommand command); FindLinksResponse findHubLinks(FindHubLinksCommand command); + + DeleteLinkResponse deleteLink(DeleteLinkCommand command); } diff --git a/src/main/java/com/seong/shoutlink/domain/link/service/request/DeleteLinkCommand.java b/src/main/java/com/seong/shoutlink/domain/link/service/request/DeleteLinkCommand.java new file mode 100644 index 0000000..fb1d5e2 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/link/service/request/DeleteLinkCommand.java @@ -0,0 +1,5 @@ +package com.seong.shoutlink.domain.link.service.request; + +public record DeleteLinkCommand(Long memberId, Long linkId) { + +} diff --git a/src/main/java/com/seong/shoutlink/domain/link/service/response/DeleteLinkResponse.java b/src/main/java/com/seong/shoutlink/domain/link/service/response/DeleteLinkResponse.java new file mode 100644 index 0000000..304d6c7 --- /dev/null +++ b/src/main/java/com/seong/shoutlink/domain/link/service/response/DeleteLinkResponse.java @@ -0,0 +1,5 @@ +package com.seong.shoutlink.domain.link.service.response; + +public record DeleteLinkResponse(Long linkId) { + +} diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index 4afd76a..8462859 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -470,6 +470,7 @@

API 문서

  • 링크 목록 조회
  • 허브 링크 생성
  • 허브 링크 목록 조회
  • +
  • 회원 링크 삭제
  • 허브 @@ -608,98 +609,30 @@

    회원 가입

    request

    HTTP request
    -
    -
    -
    POST /api/members HTTP/1.1
    -Content-Type: application/json;charset=UTF-8
    -Content-Length: 89
    -Host: localhost:8080
    -
    -{
    -  "email" : "email@email.com",
    -  "password" : "asdf1234!",
    -  "nickname" : "닉네임"
    -}
    -
    +
    +

    Snippet http-request not found for operation::auth-controller-test/create-member

    Request fields
    - ----- - - - - - - - - - - - - - - - - - - - - - - - - -
    PathTypeDescription

    email

    String

    사용자 이메일

    password

    String

    사용자 비밀번호

    nickname

    String

    사용자 닉네임

    +
    +

    Snippet request-fields not found for operation::auth-controller-test/create-member

    +

    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: 20
    -
    -{
    -  "memberId" : 1
    -}
    -
    +
    +

    Snippet http-response not found for operation::auth-controller-test/create-member

    Response fields
    - ----- - - - - - - - - - - - - - - -
    PathTypeDescription

    memberId

    Number

    사용자 ID

    +
    +

    Snippet response-fields not found for operation::auth-controller-test/create-member

    +
    @@ -718,7 +651,7 @@
    POST /api/link-bundles HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzMTg3Nzg1LCJzdWIiOiIxIiwiZXhwIjoxNzEzMTkxMzg1LCJyb2xlIjoiUk9MRV9VU0VSIn0.lMSQQ_fWOUszqq2YM05fOrvXZljTWRyQ4rJK5o95494
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzNjk3MDc5LCJzdWIiOiIxIiwiZXhwIjoxNzEzNzAwNjc5LCJyb2xlIjoiUk9MRV9VU0VSIn0.dgA78BnrQKybFlhoStHl0TT8R5DvM-3RK8nUniPZqPc
     Content-Length: 57
     Host: localhost:8080
     
    @@ -834,7 +767,7 @@ 
    GET /api/link-bundles HTTP/1.1
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzMTg3Nzg1LCJzdWIiOiIxIiwiZXhwIjoxNzEzMTkxMzg1LCJyb2xlIjoiUk9MRV9VU0VSIn0.lMSQQ_fWOUszqq2YM05fOrvXZljTWRyQ4rJK5o95494
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzNjk3MDc5LCJzdWIiOiIxIiwiZXhwIjoxNzEzNzAwNjc5LCJyb2xlIjoiUk9MRV9VU0VSIn0.dgA78BnrQKybFlhoStHl0TT8R5DvM-3RK8nUniPZqPc
     Host: localhost:8080
    @@ -939,7 +872,7 @@
    POST /api/hubs/1/link-bundles HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzMTg3Nzg1LCJzdWIiOiIxIiwiZXhwIjoxNzEzMTkxMzg1LCJyb2xlIjoiUk9MRV9VU0VSIn0.lMSQQ_fWOUszqq2YM05fOrvXZljTWRyQ4rJK5o95494
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzNjk3MDc5LCJzdWIiOiIxIiwiZXhwIjoxNzEzNzAwNjc5LCJyb2xlIjoiUk9MRV9VU0VSIn0.dgA78BnrQKybFlhoStHl0TT8R5DvM-3RK8nUniPZqPc
     Content-Length: 53
     Host: localhost:8080
     
    @@ -1077,7 +1010,7 @@ 
    GET /api/hubs/1/link-bundles HTTP/1.1
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzMTg3Nzg1LCJzdWIiOiIxIiwiZXhwIjoxNzEzMTkxMzg1LCJyb2xlIjoiUk9MRV9VU0VSIn0.lMSQQ_fWOUszqq2YM05fOrvXZljTWRyQ4rJK5o95494
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzNjk3MDc5LCJzdWIiOiIxIiwiZXhwIjoxNzEzNzAwNjc5LCJyb2xlIjoiUk9MRV9VU0VSIn0.dgA78BnrQKybFlhoStHl0TT8R5DvM-3RK8nUniPZqPc
     Host: localhost:8080
    @@ -1205,7 +1138,7 @@
    POST /api/links HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzMTg3Nzg0LCJzdWIiOiIxIiwiZXhwIjoxNzEzMTkxMzg0LCJyb2xlIjoiUk9MRV9VU0VSIn0.DziH9P-JjDpjXUU88BBZYHHEHe05dtZm05UJ2g1mMq4
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzNjk3MDc5LCJzdWIiOiIxIiwiZXhwIjoxNzEzNzAwNjc5LCJyb2xlIjoiUk9MRV9VU0VSIn0.dgA78BnrQKybFlhoStHl0TT8R5DvM-3RK8nUniPZqPc
     Content-Length: 100
     Host: localhost:8080
     
    @@ -1327,7 +1260,7 @@ 
    GET /api/links?linkBundleId=1&page=0&size=10 HTTP/1.1
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzMTg3Nzg0LCJzdWIiOiIxIiwiZXhwIjoxNzEzMTkxMzg0LCJyb2xlIjoiUk9MRV9VU0VSIn0.DziH9P-JjDpjXUU88BBZYHHEHe05dtZm05UJ2g1mMq4
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzNjk3MDc5LCJzdWIiOiIxIiwiZXhwIjoxNzEzNzAwNjc5LCJyb2xlIjoiUk9MRV9VU0VSIn0.dgA78BnrQKybFlhoStHl0TT8R5DvM-3RK8nUniPZqPc
     Host: localhost:8080
    @@ -1394,7 +1327,7 @@
    POST /api/hubs/1/links HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzMTg3Nzg0LCJzdWIiOiIxIiwiZXhwIjoxNzEzMTkxMzg0LCJyb2xlIjoiUk9MRV9VU0VSIn0.DziH9P-JjDpjXUU88BBZYHHEHe05dtZm05UJ2g1mMq4
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzNjk3MDc5LCJzdWIiOiIxIiwiZXhwIjoxNzEzNzAwNjc5LCJyb2xlIjoiUk9MRV9VU0VSIn0.dgA78BnrQKybFlhoStHl0TT8R5DvM-3RK8nUniPZqPc
     Content-Length: 69
     Host: localhost:8080
     
    @@ -1538,7 +1471,7 @@ 
    GET /api/hubs/1/links?linkBundleId=1&page=0&size=20 HTTP/1.1
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzMTg3Nzg0LCJzdWIiOiIxIiwiZXhwIjoxNzEzMTkxMzg0LCJyb2xlIjoiUk9MRV9VU0VSIn0.DziH9P-JjDpjXUU88BBZYHHEHe05dtZm05UJ2g1mMq4
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzNjk3MDc5LCJzdWIiOiIxIiwiZXhwIjoxNzEzNzAwNjc5LCJyb2xlIjoiUk9MRV9VU0VSIn0.dgA78BnrQKybFlhoStHl0TT8R5DvM-3RK8nUniPZqPc
     Host: localhost:8080
    @@ -1692,6 +1625,109 @@
    +

    회원 링크 삭제

    +
    +

    request

    +
    +
    HTTP request
    +
    +
    +
    DELETE /api/links/1 HTTP/1.1
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzNjk3MDc5LCJzdWIiOiIxIiwiZXhwIjoxNzEzNzAwNjc5LCJyb2xlIjoiUk9MRV9VU0VSIn0.dgA78BnrQKybFlhoStHl0TT8R5DvM-3RK8nUniPZqPc
    +Host: localhost:8080
    +
    +
    +
    +
    +
    Request headers
    + ++++ + + + + + + + + + + + + +
    NameDescription

    Authorization

    액세스 토큰

    +
    +
    +
    Path parameters
    + + ++++ + + + + + + + + + + + + +
    Table 1. /api/links/{linkId}
    ParameterDescription

    linkId

    링크 ID

    +
    +
    +
    +

    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: 18
    +
    +{
    +  "linkId" : 1
    +}
    +
    +
    +
    +
    +
    Response fields
    + +++++ + + + + + + + + + + + + + + +
    PathTypeDescription

    linkId

    Number

    삭제된 링크 ID

    +
    +
    +
    @@ -1700,14 +1736,14 @@

    허브

    허브 생성

    -

    request

    +

    request

    -
    HTTP request
    +
    HTTP request
    POST /api/hubs HTTP/1.1
     Content-Type: application/json;charset=UTF-8
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzMTg3Nzg0LCJzdWIiOiIxIiwiZXhwIjoxNzEzMTkxMzg0LCJyb2xlIjoiUk9MRV9VU0VSIn0.DziH9P-JjDpjXUU88BBZYHHEHe05dtZm05UJ2g1mMq4
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzNjk3MDc4LCJzdWIiOiIxIiwiZXhwIjoxNzEzNzAwNjc4LCJyb2xlIjoiUk9MRV9VU0VSIn0.Pn2wYf3T95eJ9FjqTb5DW31x4OPEUerPYySHMLNQrV0
     Content-Length: 88
     Host: localhost:8080
     
    @@ -1720,7 +1756,7 @@ 
    -
    Request headers
    +
    Request headers
    @@ -1741,7 +1777,7 @@
    -
    Request fields
    +
    Request fields
    @@ -1776,9 +1812,9 @@
    -

    response

    +

    response

    -
    HTTP response
    +
    HTTP response
    HTTP/1.1 201 Created
    @@ -1795,7 +1831,7 @@ 
    -
    Response fields
    +
    Response fields
    @@ -1823,9 +1859,9 @@

    허브 목록 조회

    -

    request

    +

    request

    -
    HTTP request
    +
    HTTP request
    GET /api/hubs?page=0&size=0 HTTP/1.1
    @@ -1834,7 +1870,7 @@ 
    -
    Path parameters
    +
    Path parameters
    @@ -1861,9 +1897,9 @@
    -

    response

    +

    response

    -
    HTTP response
    +
    HTTP response
    HTTP/1.1 200 OK
    @@ -1892,7 +1928,7 @@ 
    -
    Response fields
    +
    Response fields
    Table 1. /api/hubs
    @@ -1970,9 +2006,9 @@

    허브 조회

    -

    request

    +

    request

    -
    HTTP request
    +
    HTTP request
    GET /api/hubs/1 HTTP/1.1
    @@ -1981,7 +2017,7 @@ 
    -
    Path parameters
    +
    Path parameters
    @@ -2004,9 +2040,9 @@
    -

    response

    +

    response

    -
    HTTP response
    +
    HTTP response
    HTTP/1.1 200 OK
    @@ -2032,19 +2068,19 @@ 

    사용자의 허브 목록 조회

    -

    request

    +

    request

    -
    HTTP request
    +
    HTTP request
    GET /api/hubs/me?page=0&size=10 HTTP/1.1
    -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzMTg3Nzg0LCJzdWIiOiIxIiwiZXhwIjoxNzEzMTkxMzg0LCJyb2xlIjoiUk9MRV9VU0VSIn0.DziH9P-JjDpjXUU88BBZYHHEHe05dtZm05UJ2g1mMq4
    +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNzEzNjk3MDc4LCJzdWIiOiIxIiwiZXhwIjoxNzEzNzAwNjc4LCJyb2xlIjoiUk9MRV9VU0VSIn0.Pn2wYf3T95eJ9FjqTb5DW31x4OPEUerPYySHMLNQrV0
     Host: localhost:8080
    Table 1. /api/hubs/{hubId}
    @@ -2065,7 +2101,7 @@
    -
    Query parameters
    +
    Query parameters
    @@ -2091,9 +2127,9 @@
    -

    response

    +

    response

    -
    HTTP response
    +
    HTTP response
    HTTP/1.1 200 OK
    @@ -2122,7 +2158,7 @@ 
    -
    Response fields
    +
    Response fields
    @@ -2205,9 +2241,9 @@

    도메인

    루트 도메인 목록 조회

    -

    request

    +

    request

    -
    HTTP request
    +
    HTTP request
    GET /api/domains/search?keyword=searchKeyword&size=20 HTTP/1.1
    @@ -2216,7 +2252,7 @@ 
    -
    Query parameters
    +
    Query parameters
    @@ -2242,9 +2278,9 @@
    -

    response

    +

    response

    -
    HTTP response
    +
    HTTP response
    HTTP/1.1 200 OK
    @@ -2261,7 +2297,7 @@ 
    -
    Response fields
    +
    Response fields
    @@ -2289,9 +2325,9 @@

    도메인 목록 조회

    -

    request

    +

    request

    -
    HTTP request
    +
    HTTP request
    GET /api/domains?keyword=searchKeyword&page=0&size=10 HTTP/1.1
    @@ -2300,7 +2336,7 @@ 
    -
    Query parameters
    +
    Query parameters
    @@ -2330,9 +2366,9 @@
    -

    response

    +

    response

    -
    HTTP response
    +
    HTTP response
    HTTP/1.1 200 OK
    @@ -2354,7 +2390,7 @@ 
    -
    Response fields
    +
    Response fields
    @@ -2402,9 +2438,9 @@

    도메인 단건 조회

    -

    request

    +

    request

    -
    HTTP request
    +
    HTTP request
    GET /api/domains/1 HTTP/1.1
    @@ -2413,7 +2449,7 @@ 
    -
    Path parameters
    +
    Path parameters
    @@ -2436,9 +2472,9 @@
    -

    response

    +

    response

    -
    HTTP response
    +
    HTTP response
    HTTP/1.1 200 OK
    @@ -2456,7 +2492,7 @@ 
    -
    Response fields
    +
    Response fields
    Table 1. /api/domains/{domainId}
    @@ -2489,9 +2525,9 @@

    도메인 링크 목록 조회

    -

    request

    +

    request

    -
    HTTP request
    +
    HTTP request
    GET /api/domains/1/links?page=0&size=10 HTTP/1.1
    @@ -2500,7 +2536,7 @@ 
    -
    Path parameters
    +
    Path parameters
    @@ -2522,7 +2558,7 @@
    -
    Query parameters
    +
    Query parameters
    Table 1. /api/domains/{domainId}/links
    @@ -2548,9 +2584,9 @@
    -

    response

    +

    response

    -
    HTTP response
    +
    HTTP response
    HTTP/1.1 200 OK
    @@ -2573,7 +2609,7 @@ 
    -
    Response fields
    +
    Response fields
    @@ -2629,7 +2665,7 @@
    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 f9d7a15..0277dc6 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 @@ -4,6 +4,7 @@ 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.delete; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; @@ -18,6 +19,7 @@ import com.seong.shoutlink.domain.link.controller.request.CreateLinkRequest; import com.seong.shoutlink.domain.link.service.response.CreateHubLinkResponse; import com.seong.shoutlink.domain.link.service.response.CreateLinkResponse; +import com.seong.shoutlink.domain.link.service.response.DeleteLinkResponse; import com.seong.shoutlink.domain.link.service.response.FindLinkResponse; import com.seong.shoutlink.domain.link.service.response.FindLinksResponse; import java.util.List; @@ -181,10 +183,37 @@ void findHubLinks() throws Exception { fieldWithPath("links").type(JsonFieldType.ARRAY).description("허브 링크 목록"), fieldWithPath("links[].linkId").type(JsonFieldType.NUMBER).description("링크 ID"), fieldWithPath("links[].url").type(JsonFieldType.STRING).description("링크 url"), - fieldWithPath("links[].description").type(JsonFieldType.STRING).description("링크 설명"), - fieldWithPath("totalElements").type(JsonFieldType.NUMBER).description("총 요소 개수"), + fieldWithPath("links[].description").type(JsonFieldType.STRING) + .description("링크 설명"), + fieldWithPath("totalElements").type(JsonFieldType.NUMBER) + .description("총 요소 개수"), fieldWithPath("hasNext").type(JsonFieldType.BOOLEAN).description("다음 페이지 여부") ) )); } + + @Test + @DisplayName("성공: 회원 링크 삭제 api 호출 시") + void deleteLink() throws Exception { + //given + given(linkService.deleteLink(any())).willReturn(new DeleteLinkResponse(1L)); + + //when + ResultActions resultActions = mockMvc.perform(delete("/api/links/{linkId}", 1L) + .header(AUTHORIZATION, bearerAccessToken)); + + //then + resultActions.andExpect(status().isOk()) + .andDo(restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION).description("액세스 토큰") + ), + pathParameters( + parameterWithName("linkId").description("링크 ID") + ), + responseFields( + fieldWithPath("linkId").description("삭제된 링크 ID") + ) + )); + } } \ No newline at end of file diff --git a/src/test/java/com/seong/shoutlink/domain/link/repository/StubLinkRepository.java b/src/test/java/com/seong/shoutlink/domain/link/repository/StubLinkRepository.java index ded9319..b941bcc 100644 --- a/src/test/java/com/seong/shoutlink/domain/link/repository/StubLinkRepository.java +++ b/src/test/java/com/seong/shoutlink/domain/link/repository/StubLinkRepository.java @@ -6,6 +6,7 @@ import com.seong.shoutlink.domain.link.service.LinkRepository; import com.seong.shoutlink.domain.link.service.result.LinkPaginationResult; import com.seong.shoutlink.domain.linkbundle.LinkBundle; +import com.seong.shoutlink.domain.member.Member; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -26,6 +27,10 @@ public void stub(Link... links) { } } + public int count() { + return memory.size(); + } + @Override public Long save(LinkWithLinkBundle linkWithLinkBundle) { long nextId = getNextId(); @@ -66,4 +71,14 @@ public List findAllByLinkBundlesIn(List linkBund } return result; } + + @Override + public Optional findMemberLink(Long linkId, Member member) { + return memory.values().stream().findFirst(); + } + + @Override + public void delete(Link link) { + memory.values().remove(link); + } } 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 6b8418b..6b24cd1 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 @@ -12,6 +12,7 @@ import com.seong.shoutlink.domain.link.repository.StubLinkRepository; import com.seong.shoutlink.domain.link.service.request.CreateHubLinkCommand; import com.seong.shoutlink.domain.link.service.request.CreateLinkCommand; +import com.seong.shoutlink.domain.link.service.request.DeleteLinkCommand; import com.seong.shoutlink.domain.link.service.request.FindHubLinksCommand; import com.seong.shoutlink.domain.link.service.request.FindLinksCommand; import com.seong.shoutlink.domain.link.service.response.CreateHubLinkResponse; @@ -414,4 +415,59 @@ void unauthorized_WhenMemberIsNotHubMember() { } } } + + @Nested + @DisplayName("deleteLink 호출 시") + class DeleteLinkTest { + + @Test + @DisplayName("성공: 링크 삭제됨") + void deleteLink() { + //given + Member member = MemberFixture.member(); + Link link = LinkFixture.link(); + DeleteLinkCommand command = new DeleteLinkCommand(member.getMemberId(), + link.getLinkId()); + memberRepository.stub(member); + linkRepository.stub(link); + + //when + linkService.deleteLink(command); + + //then + assertThat(linkRepository.count()).isZero(); + } + + @Test + @DisplayName("예외(notFound): 존재하지 않는 링크") + void notFound_WhenLinkNotFound() { + //given + Member member = MemberFixture.member(); + memberRepository.stub(member); + DeleteLinkCommand command = new DeleteLinkCommand(member.getMemberId(), 1L); + + //when + Exception exception = catchException(() -> linkService.deleteLink(command)); + + //then + assertThat(exception).isInstanceOf(ShoutLinkException.class) + .extracting(e -> ((ShoutLinkException) e).getErrorCode()) + .isEqualTo(ErrorCode.NOT_FOUND); + } + + @Test + @DisplayName("예외(notFound): 존재하지 않는 회원") + void notFound_WhenMemberNotFound() { + //given + DeleteLinkCommand command = new DeleteLinkCommand(1L, 1L); + + //when + Exception exception = catchException(() -> linkService.deleteLink(command)); + + //then + assertThat(exception).isInstanceOf(ShoutLinkException.class) + .extracting(e -> ((ShoutLinkException) e).getErrorCode()) + .isEqualTo(ErrorCode.NOT_FOUND); + } + } }