diff --git a/app/api/common-api/src/main/java/org/example/config/SecurityConfig.java b/app/api/common-api/src/main/java/org/example/config/SecurityConfig.java index daf60463..7dd87c64 100644 --- a/app/api/common-api/src/main/java/org/example/config/SecurityConfig.java +++ b/app/api/common-api/src/main/java/org/example/config/SecurityConfig.java @@ -85,7 +85,9 @@ private RequestMatcher getMatcherForUserAndAdmin() { antMatcher(HttpMethod.POST, "/api/v1/shows/**/interest"), antMatcher(HttpMethod.POST, "/api/v1/shows/**/alert"), antMatcher(HttpMethod.POST, "/api/v1/genres/**"), - antMatcher(HttpMethod.POST, "/api/v1/artists/subscribe") + antMatcher(HttpMethod.GET, "/api/v1/artists/subscribed"), + antMatcher(HttpMethod.POST, "/api/v1/artists/subscribe"), + antMatcher(HttpMethod.POST, "/api/v1/artists/unsubscribe") ); } } diff --git a/app/api/common-api/src/main/java/org/example/dto/response/PaginationApiResponse.java b/app/api/common-api/src/main/java/org/example/dto/response/PaginationApiResponse.java new file mode 100644 index 00000000..478cc706 --- /dev/null +++ b/app/api/common-api/src/main/java/org/example/dto/response/PaginationApiResponse.java @@ -0,0 +1,23 @@ +package org.example.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import lombok.Builder; + +public record PaginationApiResponse( + @Schema(description = "조회한 데이터 개수") + int size, + @Schema(description = "다음 조회 가능 여부") + boolean hasNext, + @Schema(description = "조회 데이터") + List data +) { + + @Builder + public PaginationApiResponse( + List data, + boolean hasNext + ) { + this(data.size(), hasNext, data); + } +} diff --git a/app/api/show-api/src/main/java/com/example/artist/controller/ArtistController.java b/app/api/show-api/src/main/java/com/example/artist/controller/ArtistController.java index b0699381..1d109501 100644 --- a/app/api/show-api/src/main/java/com/example/artist/controller/ArtistController.java +++ b/app/api/show-api/src/main/java/com/example/artist/controller/ArtistController.java @@ -1,11 +1,14 @@ package com.example.artist.controller; +import com.example.artist.controller.dto.param.ArtistSubscriptionPaginationApiParam; import com.example.artist.controller.dto.request.ArtistPaginationApiRequest; import com.example.artist.controller.dto.request.ArtistSubscriptionApiRequest; +import com.example.artist.controller.dto.request.ArtistSubscriptionPaginationApiRequest; import com.example.artist.controller.dto.request.ArtistUnsubscriptionApiRequest; import com.example.artist.controller.dto.response.ArtistPaginationApiResponse; import com.example.artist.controller.dto.response.ArtistSimpleApiResponse; import com.example.artist.controller.dto.response.ArtistSubscriptionApiResponse; +import com.example.artist.controller.dto.response.ArtistUnsubscriptionApiResponse; import com.example.artist.service.ArtistService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -13,7 +16,9 @@ import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.example.dto.response.PaginationApiResponse; import org.example.security.dto.AuthenticatedUser; +import org.springdoc.core.annotations.ParameterObject; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; @@ -52,6 +57,25 @@ public ResponseEntity getArtists( ); } + @GetMapping("/subscriptions") + @Operation(summary = "구독한 아티스트 목록 조회") + public ResponseEntity> getSubscribedArtists( + @AuthenticationPrincipal AuthenticatedUser user, + @ParameterObject ArtistSubscriptionPaginationApiRequest request + ) { + var response = artistService.findArtistSubscriptions(request.toServiceRequest(user.userId())); + var data = response.data().stream() + .map(ArtistSubscriptionPaginationApiParam::from) + .toList(); + + return ResponseEntity.ok( + PaginationApiResponse.builder() + .hasNext(response.hasNext()) + .data(data) + .build() + ); + } + @PostMapping("/subscribe") @Operation(summary = "구독하기") public ResponseEntity subscribe( @@ -67,9 +91,15 @@ public ResponseEntity subscribe( @PostMapping("/unsubscribe") @Operation(summary = "구독 취소하기") - public ResponseEntity unsubscribe( + public ResponseEntity unsubscribe( + @AuthenticationPrincipal AuthenticatedUser user, @Valid @RequestBody ArtistUnsubscriptionApiRequest request ) { - return ResponseEntity.noContent().build(); + ; + return ResponseEntity.ok( + ArtistUnsubscriptionApiResponse.from( + artistService.unsubscribe(request.toServiceRequest(user.userId())) + ) + ); } } diff --git a/app/api/show-api/src/main/java/com/example/artist/controller/dto/param/ArtistSubscriptionPaginationApiParam.java b/app/api/show-api/src/main/java/com/example/artist/controller/dto/param/ArtistSubscriptionPaginationApiParam.java new file mode 100644 index 00000000..1f7e137a --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/artist/controller/dto/param/ArtistSubscriptionPaginationApiParam.java @@ -0,0 +1,26 @@ +package com.example.artist.controller.dto.param; + +import com.example.artist.service.dto.param.ArtistSubscriptionPaginationServiceParam; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.UUID; + +public record ArtistSubscriptionPaginationApiParam( + @Schema(description = "아티스트 ID") + UUID id, + @Schema(description = "아티스트 이미지 URL") + String imageUrl, + @Schema(description = "아티스트 한글 이름") + String koreanName, + @Schema(description = "아티스트 영문 이름") + String englishName +) { + + public static ArtistSubscriptionPaginationApiParam from(ArtistSubscriptionPaginationServiceParam param) { + return new ArtistSubscriptionPaginationApiParam( + param.artistId(), + param.artistImageUrl(), + param.artistKoreanName(), + param.artistEnglishName() + ); + } +} diff --git a/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistSubscriptionPaginationApiRequest.java b/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistSubscriptionPaginationApiRequest.java new file mode 100644 index 00000000..a55b3d72 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistSubscriptionPaginationApiRequest.java @@ -0,0 +1,43 @@ +package com.example.artist.controller.dto.request; + +import com.example.artist.service.dto.request.ArtistSubscriptionPaginationServiceRequest; +import com.example.artist.vo.ArtistSortStandardApiType; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.UUID; + +@Schema +public record ArtistSubscriptionPaginationApiRequest( + + @Parameter( + description = "정렬 기준, default: ENGLISH_NAME_ASC", + schema = @Schema(implementation = ArtistSortStandardApiType.class) + ) + ArtistSortStandardApiType sortStandard, + + @Parameter(description = "이전 페이지네이션 마지막 데이터의 ID / 최초 조회라면 null") + UUID cursor, + + @Parameter(description = "조회하는 데이터 개수") + int size +) { + + public ArtistSubscriptionPaginationApiRequest( + ArtistSortStandardApiType sortStandard, + UUID cursor, + int size + ) { + this.sortStandard = sortStandard == null ? ArtistSortStandardApiType.ENGLISH_NAME_ASC : sortStandard; + this.cursor = cursor; + this.size = size; + } + + public ArtistSubscriptionPaginationServiceRequest toServiceRequest(UUID userId) { + return ArtistSubscriptionPaginationServiceRequest.builder() + .size(size) + .sortStandard(sortStandard) + .cursor(cursor) + .userId(userId) + .build(); + } +} diff --git a/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistUnsubscriptionApiRequest.java b/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistUnsubscriptionApiRequest.java index cc3c9ee4..f15819ba 100644 --- a/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistUnsubscriptionApiRequest.java +++ b/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistUnsubscriptionApiRequest.java @@ -1,5 +1,6 @@ package com.example.artist.controller.dto.request; +import com.example.artist.service.dto.request.ArtistUnsubscriptionServiceRequest; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import java.util.List; @@ -12,4 +13,10 @@ public record ArtistUnsubscriptionApiRequest( List artistIds ) { + public ArtistUnsubscriptionServiceRequest toServiceRequest(UUID userId) { + return ArtistUnsubscriptionServiceRequest.builder() + .artistIds(artistIds()) + .userId(userId) + .build(); + } } diff --git a/app/api/show-api/src/main/java/com/example/artist/controller/dto/response/ArtistUnsubscriptionApiResponse.java b/app/api/show-api/src/main/java/com/example/artist/controller/dto/response/ArtistUnsubscriptionApiResponse.java new file mode 100644 index 00000000..ef77672f --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/artist/controller/dto/response/ArtistUnsubscriptionApiResponse.java @@ -0,0 +1,16 @@ +package com.example.artist.controller.dto.response; + +import com.example.artist.service.dto.response.ArtistUnsubscriptionServiceResponse; +import java.util.List; +import java.util.UUID; + +public record ArtistUnsubscriptionApiResponse( + List successUnsubscriptionArtistIds +) { + + public static ArtistUnsubscriptionApiResponse from( + ArtistUnsubscriptionServiceResponse response + ) { + return new ArtistUnsubscriptionApiResponse(response.successUnsubscriptionArtistIds()); + } +} diff --git a/app/api/show-api/src/main/java/com/example/artist/service/ArtistService.java b/app/api/show-api/src/main/java/com/example/artist/service/ArtistService.java index 15c43c85..f44c240e 100644 --- a/app/api/show-api/src/main/java/com/example/artist/service/ArtistService.java +++ b/app/api/show-api/src/main/java/com/example/artist/service/ArtistService.java @@ -1,11 +1,14 @@ package com.example.artist.service; +import com.example.artist.service.dto.param.ArtistSubscriptionPaginationServiceParam; +import com.example.artist.service.dto.request.ArtistSubscriptionPaginationServiceRequest; import com.example.artist.service.dto.request.ArtistSubscriptionServiceRequest; +import com.example.artist.service.dto.request.ArtistUnsubscriptionServiceRequest; +import com.example.artist.service.dto.response.ArtistSubscriptionPaginationServiceResponse; import com.example.artist.service.dto.response.ArtistSubscriptionServiceResponse; +import com.example.artist.service.dto.response.ArtistUnsubscriptionServiceResponse; import java.util.List; -import java.util.Map; import java.util.UUID; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.example.entity.ArtistSubscription; @@ -23,47 +26,63 @@ public class ArtistService { private final ArtistSubscriptionUseCase artistSubscriptionUseCase; public ArtistSubscriptionServiceResponse subscribe(ArtistSubscriptionServiceRequest request) { - List existArtists = artistUseCase.findAllArtistInIds(request.artistIds()); + List existArtistsInRequest = artistUseCase.findAllArtistInIds(request.artistIds()); + List existArtistIdsInRequest = existArtistsInRequest.stream() + .map(Artist::getId) + .toList(); - List newArtistSubscription = getNewArtistSubscription( - existArtists, + List subscriptions = artistSubscriptionUseCase.subscribe( + existArtistIdsInRequest, request.userId() ); - artistSubscriptionUseCase.subscribe(newArtistSubscription); - return ArtistSubscriptionServiceResponse.builder() .successSubscriptionArtistIds( - newArtistSubscription.stream() + subscriptions.stream() .map(ArtistSubscription::getArtistId) .toList() - ) - .build(); + ).build(); } - private List getNewArtistSubscription( - List artists, - UUID userId + public ArtistUnsubscriptionServiceResponse unsubscribe( + ArtistUnsubscriptionServiceRequest request ) { - var existSubscriptionByArtistId = getExistSubscriptionByArtistId(userId); - return artists.stream() - .filter(artist -> !existSubscriptionByArtistId.containsKey(artist.getId())) - .map(artist -> - ArtistSubscription.builder() - .artistId(artist.getId()) - .userId(userId) - .build() - ).toList(); + List unsubscribedArtists = artistSubscriptionUseCase.unsubscribe( + request.artistIds(), + request.userId() + ); + + return ArtistUnsubscriptionServiceResponse.builder() + .successUnsubscriptionArtistIds( + unsubscribedArtists.stream() + .map(ArtistSubscription::getArtistId) + .toList() + ).build(); } - private Map getExistSubscriptionByArtistId(UUID userId) { - return artistSubscriptionUseCase.findSubscriptionList(userId) - .stream() - .collect( - Collectors.toMap( - ArtistSubscription::getArtistId, - artistSubscription -> artistSubscription - ) - ); + public ArtistSubscriptionPaginationServiceResponse findArtistSubscriptions( + ArtistSubscriptionPaginationServiceRequest request + ) { + List subscriptions = artistSubscriptionUseCase.findSubscriptionList(request.userId()); + List subscriptionArtistIds = subscriptions.stream() + .map(ArtistSubscription::getArtistId) + .toList(); + + if (subscriptionArtistIds.isEmpty()) { + return ArtistSubscriptionPaginationServiceResponse.builder() + .data(List.of()) + .hasNext(false) + .build(); + } + + var response = artistUseCase.findAllArtistInCursorPagination(request.toDomainRequest(subscriptionArtistIds)); + List data = response.data().stream() + .map(ArtistSubscriptionPaginationServiceParam::new) + .toList(); + + return ArtistSubscriptionPaginationServiceResponse.builder() + .data(data) + .hasNext(response.hasNext()) + .build(); } } diff --git a/app/api/show-api/src/main/java/com/example/artist/service/dto/param/ArtistSubscriptionPaginationServiceParam.java b/app/api/show-api/src/main/java/com/example/artist/service/dto/param/ArtistSubscriptionPaginationServiceParam.java new file mode 100644 index 00000000..4b81534b --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/artist/service/dto/param/ArtistSubscriptionPaginationServiceParam.java @@ -0,0 +1,21 @@ +package com.example.artist.service.dto.param; + +import java.util.UUID; +import org.example.dto.artist.response.SimpleArtistResponse; + +public record ArtistSubscriptionPaginationServiceParam( + UUID artistId, + String artistImageUrl, + String artistKoreanName, + String artistEnglishName +) { + + public ArtistSubscriptionPaginationServiceParam(SimpleArtistResponse response) { + this( + response.id(), + response.image(), + response.koreanName(), + response.englishName() + ); + } +} diff --git a/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistCreateServiceRequest.java b/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistCreateServiceRequest.java index 9d85da69..c6307c54 100644 --- a/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistCreateServiceRequest.java +++ b/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistCreateServiceRequest.java @@ -6,8 +6,8 @@ import java.util.UUID; import lombok.Builder; import org.example.entity.artist.Artist; -import org.example.entity.artist.ArtistGender; -import org.example.entity.artist.ArtistType; +import org.example.vo.ArtistGender; +import org.example.vo.ArtistType; import org.springframework.web.multipart.MultipartFile; @Builder diff --git a/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistSubscriptionPaginationServiceRequest.java b/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistSubscriptionPaginationServiceRequest.java new file mode 100644 index 00000000..3a6b5974 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistSubscriptionPaginationServiceRequest.java @@ -0,0 +1,25 @@ +package com.example.artist.service.dto.request; + +import com.example.artist.vo.ArtistSortStandardApiType; +import java.util.List; +import java.util.UUID; +import lombok.Builder; +import org.example.dto.artist.request.ArtistPaginationDomainRequest; + +@Builder +public record ArtistSubscriptionPaginationServiceRequest( + int size, + ArtistSortStandardApiType sortStandard, + UUID cursor, + UUID userId +) { + + public ArtistPaginationDomainRequest toDomainRequest(List artistIds) { + return ArtistPaginationDomainRequest.builder() + .size(size) + .sortStandard(sortStandard.toDomainType()) + .artistIds(artistIds) + .cursor(cursor) + .build(); + } +} diff --git a/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistUnsubscriptionServiceRequest.java b/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistUnsubscriptionServiceRequest.java new file mode 100644 index 00000000..d12d66e0 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistUnsubscriptionServiceRequest.java @@ -0,0 +1,13 @@ +package com.example.artist.service.dto.request; + +import java.util.List; +import java.util.UUID; +import lombok.Builder; + +@Builder +public record ArtistUnsubscriptionServiceRequest( + List artistIds, + UUID userId +) { + +} diff --git a/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistUpdateServiceRequest.java b/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistUpdateServiceRequest.java index e702ce8d..c35e17a2 100644 --- a/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistUpdateServiceRequest.java +++ b/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistUpdateServiceRequest.java @@ -6,8 +6,8 @@ import java.util.UUID; import lombok.Builder; import org.example.entity.artist.Artist; -import org.example.entity.artist.ArtistGender; -import org.example.entity.artist.ArtistType; +import org.example.vo.ArtistGender; +import org.example.vo.ArtistType; import org.springframework.web.multipart.MultipartFile; @Builder diff --git a/app/api/show-api/src/main/java/com/example/artist/service/dto/response/ArtistSubscriptionPaginationServiceResponse.java b/app/api/show-api/src/main/java/com/example/artist/service/dto/response/ArtistSubscriptionPaginationServiceResponse.java new file mode 100644 index 00000000..0d7d1c70 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/artist/service/dto/response/ArtistSubscriptionPaginationServiceResponse.java @@ -0,0 +1,13 @@ +package com.example.artist.service.dto.response; + +import com.example.artist.service.dto.param.ArtistSubscriptionPaginationServiceParam; +import java.util.List; +import lombok.Builder; + +@Builder +public record ArtistSubscriptionPaginationServiceResponse( + boolean hasNext, + List data +) { + +} diff --git a/app/api/show-api/src/main/java/com/example/artist/service/dto/response/ArtistUnsubscriptionServiceResponse.java b/app/api/show-api/src/main/java/com/example/artist/service/dto/response/ArtistUnsubscriptionServiceResponse.java new file mode 100644 index 00000000..cee247ce --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/artist/service/dto/response/ArtistUnsubscriptionServiceResponse.java @@ -0,0 +1,12 @@ +package com.example.artist.service.dto.response; + +import java.util.List; +import java.util.UUID; +import lombok.Builder; + +@Builder +public record ArtistUnsubscriptionServiceResponse( + List successUnsubscriptionArtistIds +) { + +} diff --git a/app/api/show-api/src/main/java/com/example/artist/vo/ArtistSortStandardApiType.java b/app/api/show-api/src/main/java/com/example/artist/vo/ArtistSortStandardApiType.java new file mode 100644 index 00000000..d83ae026 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/artist/vo/ArtistSortStandardApiType.java @@ -0,0 +1,19 @@ +package com.example.artist.vo; + +import org.example.vo.ArtistSortStandardDomainType; + +public enum ArtistSortStandardApiType { + KOREAN_NAME_ASC, + KOREAN_NAME_DESC, + ENGLISH_NAME_ASC, + ENGLISH_NAME_DESC; + + public ArtistSortStandardDomainType toDomainType() { + return switch (this) { + case KOREAN_NAME_ASC -> ArtistSortStandardDomainType.KOREAN_NAME_ASC; + case KOREAN_NAME_DESC -> ArtistSortStandardDomainType.KOREAN_NAME_DESC; + case ENGLISH_NAME_ASC -> ArtistSortStandardDomainType.ENGLISH_NAME_ASC; + case ENGLISH_NAME_DESC -> ArtistSortStandardDomainType.ENGLISH_NAME_DESC; + }; + } +} diff --git a/app/domain/show-domain/src/main/java/org/example/dto/artist/request/ArtistPaginationDomainRequest.java b/app/domain/show-domain/src/main/java/org/example/dto/artist/request/ArtistPaginationDomainRequest.java new file mode 100644 index 00000000..48687900 --- /dev/null +++ b/app/domain/show-domain/src/main/java/org/example/dto/artist/request/ArtistPaginationDomainRequest.java @@ -0,0 +1,16 @@ +package org.example.dto.artist.request; + +import java.util.List; +import java.util.UUID; +import lombok.Builder; +import org.example.vo.ArtistSortStandardDomainType; + +@Builder +public record ArtistPaginationDomainRequest( + int size, + ArtistSortStandardDomainType sortStandard, + UUID cursor, + List artistIds +) { + +} diff --git a/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ArtistDetailPaginationResponse.java b/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ArtistDetailPaginationResponse.java new file mode 100644 index 00000000..0e14406e --- /dev/null +++ b/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ArtistDetailPaginationResponse.java @@ -0,0 +1,12 @@ +package org.example.dto.artist.response; + +import java.util.List; +import lombok.Builder; + +@Builder +public record ArtistDetailPaginationResponse( + List data, + boolean hasNext +) { + +} diff --git a/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ArtistDetailResponse.java b/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ArtistDetailResponse.java index 7cdffeb7..f13cee67 100644 --- a/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ArtistDetailResponse.java +++ b/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ArtistDetailResponse.java @@ -2,8 +2,8 @@ import java.util.List; import java.util.UUID; -import org.example.entity.artist.ArtistGender; -import org.example.entity.artist.ArtistType; +import org.example.vo.ArtistGender; +import org.example.vo.ArtistType; public record ArtistDetailResponse( UUID id, diff --git a/app/domain/show-domain/src/main/java/org/example/dto/artist/response/SimpleArtistResponse.java b/app/domain/show-domain/src/main/java/org/example/dto/artist/response/SimpleArtistResponse.java new file mode 100644 index 00000000..1eb25a7a --- /dev/null +++ b/app/domain/show-domain/src/main/java/org/example/dto/artist/response/SimpleArtistResponse.java @@ -0,0 +1,12 @@ +package org.example.dto.artist.response; + +import java.util.UUID; + +public record SimpleArtistResponse( + UUID id, + String koreanName, + String englishName, + String image +) { + +} diff --git a/app/domain/show-domain/src/main/java/org/example/entity/artist/Artist.java b/app/domain/show-domain/src/main/java/org/example/entity/artist/Artist.java index dd22bdf0..ae08fb58 100644 --- a/app/domain/show-domain/src/main/java/org/example/entity/artist/Artist.java +++ b/app/domain/show-domain/src/main/java/org/example/entity/artist/Artist.java @@ -12,6 +12,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.example.entity.BaseEntity; +import org.example.vo.ArtistGender; +import org.example.vo.ArtistType; @Entity @Getter diff --git a/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepository.java b/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepository.java index 6f2d2a5a..77c7aca8 100644 --- a/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepository.java +++ b/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepository.java @@ -3,6 +3,8 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import org.example.dto.artist.request.ArtistPaginationDomainRequest; +import org.example.dto.artist.response.ArtistDetailPaginationResponse; import org.example.dto.artist.response.ArtistDetailResponse; import org.example.dto.artist.response.ArtistKoreanNameResponse; import org.example.entity.artist.Artist; @@ -16,4 +18,6 @@ public interface ArtistQuerydslRepository { List findAllArtistKoreanName(); List findAllInIds(List ids); + + ArtistDetailPaginationResponse findAllWithCursorPagination(ArtistPaginationDomainRequest request); } diff --git a/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepositoryImpl.java b/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepositoryImpl.java index 19a89bc6..e3dc8c7a 100644 --- a/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepositoryImpl.java +++ b/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepositoryImpl.java @@ -6,6 +6,9 @@ import static org.example.entity.artist.QArtistGenre.artistGenre; import static org.example.entity.genre.QGenre.genre; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Predicate; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQuery; @@ -14,9 +17,13 @@ import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.example.dto.artist.request.ArtistPaginationDomainRequest; +import org.example.dto.artist.response.ArtistDetailPaginationResponse; import org.example.dto.artist.response.ArtistDetailResponse; import org.example.dto.artist.response.ArtistKoreanNameResponse; +import org.example.dto.artist.response.SimpleArtistResponse; import org.example.entity.artist.Artist; +import org.example.vo.ArtistSortStandardDomainType; import org.springframework.stereotype.Repository; @Repository @@ -91,6 +98,29 @@ public List findAllInIds(List ids) { .fetch(); } + @Override + public ArtistDetailPaginationResponse findAllWithCursorPagination(ArtistPaginationDomainRequest request) { + List result = jpaQueryFactory.select( + Projections.constructor( + SimpleArtistResponse.class, + artist.id, + artist.koreanName, + artist.englishName, + artist.image + ) + ).from(artist) + .where(getWhereClauseInCursorPagination(request.cursor(), request.artistIds())) + .orderBy(getOrderSpecifier(request.sortStandard())) + .limit(request.size() + 1) + .fetch(); + + boolean hasNext = result.size() == request.size() + 1; + return ArtistDetailPaginationResponse.builder() + .data(hasNext ? result.subList(0, request.size()) : result) + .hasNext(hasNext) + .build(); + } + private JPAQuery createArtistJoinArtistGenreAndGenreQuery() { return jpaQueryFactory .selectFrom(artist) @@ -106,4 +136,33 @@ private BooleanExpression isArtistGenreEqualArtistIdAndIsDeletedFalse() { private BooleanExpression isGenreEqualArtistIdAndIsDeletedFalse() { return artistGenre.genreId.eq(genre.id).and(genre.isDeleted.isFalse()); } + + private BooleanBuilder getWhereClauseInCursorPagination( + UUID cursor, + List artistIds + ) { + BooleanBuilder whereClause = new BooleanBuilder(); + whereClause.and(getDefaultPredicateInCursorPagination(cursor)); + + if (artistIds.isEmpty()) { + return whereClause; + } + + return whereClause.and(artist.id.in(artistIds)); + } + + private Predicate getDefaultPredicateInCursorPagination(UUID cursor) { + BooleanExpression defaultPredicate = artist.isDeleted.isFalse(); + + return cursor == null ? defaultPredicate : artist.id.gt(cursor).and(defaultPredicate); + } + + private OrderSpecifier getOrderSpecifier(ArtistSortStandardDomainType type) { + return switch (type) { + case KOREAN_NAME_ASC -> artist.koreanName.asc(); + case KOREAN_NAME_DESC -> artist.koreanName.desc(); + case ENGLISH_NAME_ASC -> artist.englishName.asc(); + case ENGLISH_NAME_DESC -> artist.englishName.desc(); + }; + } } diff --git a/app/domain/show-domain/src/main/java/org/example/usecase/artist/ArtistUseCase.java b/app/domain/show-domain/src/main/java/org/example/usecase/artist/ArtistUseCase.java index 9b6ee237..d0738bf2 100644 --- a/app/domain/show-domain/src/main/java/org/example/usecase/artist/ArtistUseCase.java +++ b/app/domain/show-domain/src/main/java/org/example/usecase/artist/ArtistUseCase.java @@ -3,6 +3,8 @@ import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.example.dto.artist.request.ArtistPaginationDomainRequest; +import org.example.dto.artist.response.ArtistDetailPaginationResponse; import org.example.dto.artist.response.ArtistDetailResponse; import org.example.dto.artist.response.ArtistKoreanNameResponse; import org.example.entity.BaseEntity; @@ -50,6 +52,10 @@ public List findAllArtistInIds(List ids) { return artistRepository.findAllInIds(ids); } + public ArtistDetailPaginationResponse findAllArtistInCursorPagination(ArtistPaginationDomainRequest request) { + return artistRepository.findAllWithCursorPagination(request); + } + @Transactional public void updateArtist(UUID id, Artist newArtist, List newGenreIds) { Artist artist = findArtistById(id); diff --git a/app/domain/show-domain/src/main/java/org/example/entity/artist/ArtistGender.java b/app/domain/show-domain/src/main/java/org/example/vo/ArtistGender.java similarity index 56% rename from app/domain/show-domain/src/main/java/org/example/entity/artist/ArtistGender.java rename to app/domain/show-domain/src/main/java/org/example/vo/ArtistGender.java index 291cfce4..d4485c52 100644 --- a/app/domain/show-domain/src/main/java/org/example/entity/artist/ArtistGender.java +++ b/app/domain/show-domain/src/main/java/org/example/vo/ArtistGender.java @@ -1,4 +1,4 @@ -package org.example.entity.artist; +package org.example.vo; public enum ArtistGender { MAN, WOMAN diff --git a/app/domain/show-domain/src/main/java/org/example/vo/ArtistSortStandardDomainType.java b/app/domain/show-domain/src/main/java/org/example/vo/ArtistSortStandardDomainType.java new file mode 100644 index 00000000..4e5b0ba9 --- /dev/null +++ b/app/domain/show-domain/src/main/java/org/example/vo/ArtistSortStandardDomainType.java @@ -0,0 +1,8 @@ +package org.example.vo; + +public enum ArtistSortStandardDomainType { + KOREAN_NAME_ASC, + KOREAN_NAME_DESC, + ENGLISH_NAME_ASC, + ENGLISH_NAME_DESC +} diff --git a/app/domain/show-domain/src/main/java/org/example/entity/artist/ArtistType.java b/app/domain/show-domain/src/main/java/org/example/vo/ArtistType.java similarity index 55% rename from app/domain/show-domain/src/main/java/org/example/entity/artist/ArtistType.java rename to app/domain/show-domain/src/main/java/org/example/vo/ArtistType.java index 2edef87e..17db33de 100644 --- a/app/domain/show-domain/src/main/java/org/example/entity/artist/ArtistType.java +++ b/app/domain/show-domain/src/main/java/org/example/vo/ArtistType.java @@ -1,4 +1,4 @@ -package org.example.entity.artist; +package org.example.vo; public enum ArtistType { SOLO, GROUP diff --git a/app/domain/show-domain/src/test/java/org/example/fixture/ArtistFixture.java b/app/domain/show-domain/src/test/java/org/example/fixture/ArtistFixture.java index 3274e712..740bb0f8 100644 --- a/app/domain/show-domain/src/test/java/org/example/fixture/ArtistFixture.java +++ b/app/domain/show-domain/src/test/java/org/example/fixture/ArtistFixture.java @@ -1,8 +1,8 @@ package org.example.fixture; import org.example.entity.artist.Artist; -import org.example.entity.artist.ArtistGender; -import org.example.entity.artist.ArtistType; +import org.example.vo.ArtistGender; +import org.example.vo.ArtistType; public class ArtistFixture { diff --git a/app/domain/user-domain/src/main/java/org/example/entity/ArtistSubscription.java b/app/domain/user-domain/src/main/java/org/example/entity/ArtistSubscription.java index 5bb4c604..2bb8b6e8 100644 --- a/app/domain/user-domain/src/main/java/org/example/entity/ArtistSubscription.java +++ b/app/domain/user-domain/src/main/java/org/example/entity/ArtistSubscription.java @@ -26,4 +26,12 @@ public ArtistSubscription(UUID userId, UUID artistId) { this.userId = userId; this.artistId = artistId; } + + public void subscribe() { + this.revive(); + } + + public void unsubscribe() { + this.softDelete(); + } } diff --git a/app/domain/user-domain/src/main/java/org/example/repository/subscription/ArtistSubscriptionQuerydslRepository.java b/app/domain/user-domain/src/main/java/org/example/repository/subscription/ArtistSubscriptionQuerydslRepository.java new file mode 100644 index 00000000..9c054aa8 --- /dev/null +++ b/app/domain/user-domain/src/main/java/org/example/repository/subscription/ArtistSubscriptionQuerydslRepository.java @@ -0,0 +1,10 @@ +package org.example.repository.subscription; + +import java.util.List; +import java.util.UUID; +import org.example.entity.ArtistSubscription; + +public interface ArtistSubscriptionQuerydslRepository { + + List findSubscriptionList(UUID userId); +} diff --git a/app/domain/user-domain/src/main/java/org/example/repository/subscription/ArtistSubscriptionQuerydslRepositoryImpl.java b/app/domain/user-domain/src/main/java/org/example/repository/subscription/ArtistSubscriptionQuerydslRepositoryImpl.java new file mode 100644 index 00000000..9953cbfd --- /dev/null +++ b/app/domain/user-domain/src/main/java/org/example/repository/subscription/ArtistSubscriptionQuerydslRepositoryImpl.java @@ -0,0 +1,25 @@ +package org.example.repository.subscription; + +import static org.example.entity.QArtistSubscription.artistSubscription; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.example.entity.ArtistSubscription; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class ArtistSubscriptionQuerydslRepositoryImpl implements ArtistSubscriptionQuerydslRepository { + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public List findSubscriptionList(UUID userId) { + return jpaQueryFactory.selectFrom(artistSubscription) + .where(artistSubscription.userId.eq(userId) + .and(artistSubscription.isDeleted.isFalse()) + ).fetch(); + } +} diff --git a/app/domain/user-domain/src/main/java/org/example/repository/subscription/ArtistSubscriptionRepository.java b/app/domain/user-domain/src/main/java/org/example/repository/subscription/ArtistSubscriptionRepository.java index 0de3e34f..50276bcc 100644 --- a/app/domain/user-domain/src/main/java/org/example/repository/subscription/ArtistSubscriptionRepository.java +++ b/app/domain/user-domain/src/main/java/org/example/repository/subscription/ArtistSubscriptionRepository.java @@ -5,7 +5,8 @@ import org.example.entity.ArtistSubscription; import org.springframework.data.jpa.repository.JpaRepository; -public interface ArtistSubscriptionRepository extends JpaRepository { +public interface ArtistSubscriptionRepository + extends JpaRepository, ArtistSubscriptionQuerydslRepository { - List findByUserId(UUID userId); + List findAllByUserId(UUID userId); } diff --git a/app/domain/user-domain/src/main/java/org/example/usecase/ArtistSubscriptionUseCase.java b/app/domain/user-domain/src/main/java/org/example/usecase/ArtistSubscriptionUseCase.java index 95df7e2e..b8cc183a 100644 --- a/app/domain/user-domain/src/main/java/org/example/usecase/ArtistSubscriptionUseCase.java +++ b/app/domain/user-domain/src/main/java/org/example/usecase/ArtistSubscriptionUseCase.java @@ -1,7 +1,9 @@ package org.example.usecase; +import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.example.entity.ArtistSubscription; import org.example.repository.subscription.ArtistSubscriptionRepository; @@ -14,12 +16,47 @@ public class ArtistSubscriptionUseCase { private final ArtistSubscriptionRepository artistSubscriptionRepository; + public List findSubscriptionList(UUID userId) { + return artistSubscriptionRepository.findSubscriptionList(userId); + } + @Transactional - public void subscribe(List subscriptions) { - artistSubscriptionRepository.saveAll(subscriptions); + public List subscribe(List requestArtistIds, UUID userId) { + var subscribedResult = new ArrayList(); + var newSubscriptions = new ArrayList(); + var allSubscriptionByArtistId = artistSubscriptionRepository.findAllByUserId(userId) + .stream() + .collect(Collectors.toMap(ArtistSubscription::getArtistId, it -> it)); + + for (UUID artistId : requestArtistIds) { + if (allSubscriptionByArtistId.containsKey(artistId)) { + var existSubscription = allSubscriptionByArtistId.get(artistId); + existSubscription.subscribe(); + subscribedResult.add(existSubscription); + continue; + } + + newSubscriptions.add(ArtistSubscription.builder() + .userId(userId) + .artistId(artistId) + .build() + ); + } + + artistSubscriptionRepository.saveAll(newSubscriptions); + subscribedResult.addAll(newSubscriptions); + return subscribedResult; } - public List findSubscriptionList(UUID userId) { - return artistSubscriptionRepository.findByUserId(userId); + @Transactional + public List unsubscribe(List artistIds, UUID userId) { + var subscriptions = artistSubscriptionRepository.findSubscriptionList(userId); + var filteredSubscription = subscriptions.stream() + .filter(it -> artistIds.contains(it.getArtistId())) + .toList(); + + filteredSubscription.forEach(ArtistSubscription::unsubscribe); + + return filteredSubscription; } }