diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedApi.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedApi.java index cfef9010..8c66dab4 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedApi.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedApi.java @@ -1,42 +1,49 @@ package ddingdong.ddingdongBE.domain.feed.api; -import ddingdong.ddingdongBE.domain.feed.controller.dto.response.FeedListResponse; +import ddingdong.ddingdongBE.domain.feed.controller.dto.response.ClubFeedPageResponse; import ddingdong.ddingdongBE.domain.feed.controller.dto.response.FeedResponse; -import ddingdong.ddingdongBE.domain.feed.controller.dto.response.NewestFeedListResponse; +import ddingdong.ddingdongBE.domain.feed.controller.dto.response.NewestFeedPerClubPageResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; @Tag(name = "Feed - User", description = "Feed API") @RequestMapping("/server") public interface FeedApi { - @Operation(summary = "동아리 피드 전체 조회 API") - @ApiResponse(responseCode = "200", description = "동아리 피드 전체 조회 성공", - content = @Content(schema = @Schema(implementation = FeedListResponse.class))) - @ResponseStatus(HttpStatus.OK) - @GetMapping("/clubs/{clubId}/feeds") - List getAllFeedByClubId(@PathVariable Long clubId); + @Operation(summary = "특정 동아리 피드 페이지 조회 API") + @ApiResponse(responseCode = "200", description = "특정 동아리 피드 페이지 조회 성공", + content = @Content(schema = @Schema(implementation = ClubFeedPageResponse.class))) + @ResponseStatus(HttpStatus.OK) + @GetMapping("/clubs/{clubId}/feeds") + ClubFeedPageResponse getFeedPageByClub( + @PathVariable("clubId") Long clubId, + @RequestParam(value = "size", defaultValue = "9") int size, + @RequestParam(value = "currentCursorId", defaultValue = "-1") Long currentCursorId + ); - @Operation(summary = "전체 동아리 최신 피드 조회 API") - @ApiResponse(responseCode = "200", description = "전체 동아리 최신 피드 조회 성공", - content = @Content(schema = @Schema(implementation = NewestFeedListResponse.class))) - @ResponseStatus(HttpStatus.OK) - @GetMapping("/feeds") - List getNewestAllFeed(); + @Operation(summary = "모든 동아리 최신 피드 페이지 조회 API") + @ApiResponse(responseCode = "200", description = "모든 동아리 최신 피드 페이지 조회 성공", + content = @Content(schema = @Schema(implementation = NewestFeedPerClubPageResponse.class))) + @ResponseStatus(HttpStatus.OK) + @GetMapping("/feeds") + NewestFeedPerClubPageResponse getNewestFeedPerClub( + @RequestParam(value = "size", defaultValue = "9") int size, + @RequestParam(value = "currentCursorId", defaultValue = "-1") Long currentCursorId + ); - @Operation(summary = "동아리 피드 상세 조회 API") - @ApiResponse(responseCode = "200", description = "동아리 피드 상세 조회 API", - content = @Content(schema = @Schema(implementation = FeedResponse.class))) - @ResponseStatus(HttpStatus.OK) - @GetMapping("/feeds/{feedId}") - FeedResponse getByFeedId(@PathVariable("feedId") Long feedId); + @Operation(summary = "동아리 피드 상세 조회 API") + @ApiResponse(responseCode = "200", description = "동아리 피드 상세 조회 API", + content = @Content(schema = @Schema(implementation = FeedResponse.class))) + @ResponseStatus(HttpStatus.OK) + @GetMapping("/feeds/{feedId}") + FeedResponse getByFeedId(@PathVariable("feedId") Long feedId); } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedController.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedController.java index d87dd2f6..28cf01ea 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedController.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedController.java @@ -1,13 +1,13 @@ package ddingdong.ddingdongBE.domain.feed.controller; import ddingdong.ddingdongBE.domain.feed.api.FeedApi; -import ddingdong.ddingdongBE.domain.feed.controller.dto.response.FeedListResponse; +import ddingdong.ddingdongBE.domain.feed.controller.dto.response.ClubFeedPageResponse; import ddingdong.ddingdongBE.domain.feed.controller.dto.response.FeedResponse; -import ddingdong.ddingdongBE.domain.feed.controller.dto.response.NewestFeedListResponse; +import ddingdong.ddingdongBE.domain.feed.controller.dto.response.NewestFeedPerClubPageResponse; import ddingdong.ddingdongBE.domain.feed.service.FacadeFeedService; -import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedListQuery; +import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubFeedPageQuery; import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedQuery; -import java.util.List; +import ddingdong.ddingdongBE.domain.feed.service.dto.query.NewestFeedPerClubPageQuery; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.RestController; @@ -18,19 +18,22 @@ public class FeedController implements FeedApi { private final FacadeFeedService facadeFeedService; @Override - public List getAllFeedByClubId(Long clubId) { - List feedListQueries = facadeFeedService.getAllByClubId(clubId); - return feedListQueries.stream() - .map(FeedListResponse::from) - .toList(); + public ClubFeedPageResponse getFeedPageByClub( + Long clubId, + int size, + Long currentCursorId + ) { + ClubFeedPageQuery clubFeedPageQuery = facadeFeedService.getFeedPageByClub(clubId, size, currentCursorId); + return ClubFeedPageResponse.from(clubFeedPageQuery); } @Override - public List getNewestAllFeed() { - List newestFeedListQueries = facadeFeedService.getNewestAll(); - return newestFeedListQueries.stream() - .map(NewestFeedListResponse::from) - .toList(); + public NewestFeedPerClubPageResponse getNewestFeedPerClub( + int size, + Long currentCursorId + ) { + NewestFeedPerClubPageQuery newestFeedPerClubPageQuery = facadeFeedService.getNewestFeedPerClubPage(size, currentCursorId); + return NewestFeedPerClubPageResponse.from(newestFeedPerClubPageQuery); } @Override diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/ClubFeedPageResponse.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/ClubFeedPageResponse.java new file mode 100644 index 00000000..68efadc1 --- /dev/null +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/ClubFeedPageResponse.java @@ -0,0 +1,45 @@ +package ddingdong.ddingdongBE.domain.feed.controller.dto.response; + +import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubFeedPageQuery; +import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedListQuery; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import lombok.Builder; + +public record ClubFeedPageResponse( + @ArraySchema(schema = @Schema(name = "동아리 피드 정보", implementation = ClubFeedListResponse.class)) + List clubFeeds, + @Schema(name = "피드 페이지 정보", implementation = PagingResponse.class) + PagingResponse pagingInfo +) { + + public static ClubFeedPageResponse from(ClubFeedPageQuery clubFeedPageQuery) { + List clubFeeds = clubFeedPageQuery.feedListQueries().stream() + .map(ClubFeedListResponse::from) + .toList(); + return new ClubFeedPageResponse(clubFeeds, PagingResponse.from(clubFeedPageQuery.pagingQuery())); + } + + @Builder + record ClubFeedListResponse( + @Schema(description = "피드 ID", example = "1") + Long id, + @Schema(description = "피드 썸네일 CDN URL", example = "https://%s.s3.%s.amazonaws.com/%s/%s/%s") + String thumbnailCdnUrl, + @Schema(description = "피드 썸네일 S3 URL", example = "https://%s.s3.%s.amazonaws.com/%s/%s/%s") + String thumbnailOriginUrl, + @Schema(description = "피드 타입", example = "IMAGE") + String feedType + ) { + + public static ClubFeedListResponse from(FeedListQuery feedListQuery) { + return ClubFeedListResponse.builder() + .id(feedListQuery.id()) + .thumbnailCdnUrl(feedListQuery.thumbnailCdnUrl()) + .thumbnailOriginUrl(feedListQuery.thumbnailOriginUrl()) + .feedType(feedListQuery.feedType()) + .build(); + } + } +} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/FeedListResponse.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/FeedListResponse.java deleted file mode 100644 index 8b2a8456..00000000 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/FeedListResponse.java +++ /dev/null @@ -1,24 +0,0 @@ -package ddingdong.ddingdongBE.domain.feed.controller.dto.response; - -import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedListQuery; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; - -@Builder -public record FeedListResponse( - @Schema(description = "피드 ID", example = "1") - Long id, - @Schema(description = "피드 썸네일 URL", example = "https://%s.s3.%s.amazonaws.com/%s/%s/%s") - String thumbnailUrl, - @Schema(description = "피드 타입", example = "IMAGE") - String feedType -) { - - public static FeedListResponse from(FeedListQuery info) { - return FeedListResponse.builder() - .id(info.id()) - .thumbnailUrl(info.thumbnailUrl()) - .feedType(info.feedType()) - .build(); - } -} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/FeedResponse.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/FeedResponse.java index e832c6ea..979e166c 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/FeedResponse.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/FeedResponse.java @@ -2,6 +2,7 @@ import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedQuery; import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubProfileQuery; +import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedFileUrlQuery; import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDate; import lombok.Builder; @@ -10,44 +11,68 @@ public record FeedResponse( @Schema(description = "피드 ID", example = "1") Long id, - @Schema(description = "동아리 정보") - ClubProfileResponse clubProfile, @Schema(description = "활동 내용", example = "안녕하세요. 카우 피드에요") String activityContent, - @Schema(description = "CDN URL", example = "https://example.cloudfront.net") - String fileUrl, @Schema(description = "피드 유형", example = "IMAGE") String feedType, @Schema(description = "생성 날짜", example = "2024-08-31") - LocalDate createdDate -) { - - @Builder - record ClubProfileResponse( - @Schema(description = "동아리 ID", example = "1") - Long id, - @Schema(description = "동아리 이름", example = "카우") - String name, - @Schema(description = "동아리 프로필 이미지 url", example = "https://%s.s3.%s.amazonaws.com/%s/%s/%s") - String profileImageUrl - ) { - public static ClubProfileResponse from(ClubProfileQuery query) { - return ClubProfileResponse.builder() - .id(query.id()) - .name(query.name()) - .profileImageUrl(query.profileImageUrl()) - .build(); + LocalDate createdDate, + @Schema(description = "URL 정보", implementation = FileUrlResponse.class) + FileUrlResponse fileUrls, + @Schema(description = "동아리 정보") + ClubProfileResponse clubProfile + ) { + + @Builder + record ClubProfileResponse( + @Schema(description = "동아리 ID", example = "1") + Long id, + @Schema(description = "동아리 이름", example = "카우") + String name, + @Schema(description = "동아리 프로필 이미지 url", example = "https://%s.s3.%s.amazonaws.com/%s/%s/%s") + String profileImageOriginUrl, + @Schema(description = "동아리 프로필 이미지 url", example = "https://%s.s3.%s.amazonaws.com/%s/%s/%s") + String profileImageCdnUrl + + ) { + + public static ClubProfileResponse from(ClubProfileQuery query) { + return ClubProfileResponse.builder() + .id(query.id()) + .name(query.name()) + .profileImageCdnUrl(query.profileImageCdnUrl()) + .profileImageOriginUrl(query.profileImageOriginUrl()) + .build(); + } + } + + @Builder + record FileUrlResponse( + @Schema(description = "파일 식별자", example = "0192c828-ffce-7ee8-94a8-d9d4c8cdec00") + String id, + @Schema(description = "원본 url", example = "url") + String originUrl, + @Schema(description = "cdn url", example = "url") + String cdnUrl + ) { + + public static FileUrlResponse from(FeedFileUrlQuery feedFileUrlQuery) { + return FileUrlResponse.builder() + .id(feedFileUrlQuery.id()) + .originUrl(feedFileUrlQuery.originUrl()) + .cdnUrl(feedFileUrlQuery.cdnUrl()) + .build(); + } + } + + public static FeedResponse from(FeedQuery query) { + return FeedResponse.builder() + .id(query.id()) + .clubProfile(ClubProfileResponse.from(query.clubProfileQuery())) + .activityContent(query.activityContent()) + .fileUrls(FileUrlResponse.from(query.feedFileUrlQuery())) + .feedType(query.feedType()) + .createdDate(query.createdDate()) + .build(); } - } - - public static FeedResponse from(FeedQuery info) { - return FeedResponse.builder() - .id(info.id()) - .clubProfile(ClubProfileResponse.from(info.clubProfileQuery())) - .activityContent(info.activityContent()) - .fileUrl(info.fileUrl()) - .feedType(info.feedType()) - .createdDate(info.createdDate()) - .build(); - } } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/NewestFeedListResponse.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/NewestFeedListResponse.java deleted file mode 100644 index 6f0aa2aa..00000000 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/NewestFeedListResponse.java +++ /dev/null @@ -1,24 +0,0 @@ -package ddingdong.ddingdongBE.domain.feed.controller.dto.response; - -import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedListQuery; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; - -@Builder -public record NewestFeedListResponse( - @Schema(description = "피드 ID", example = "1") - Long id, - @Schema(description = "피드 썸네일 URL", example = "https://%s.s3.%s.amazonaws.com/%s/%s/%s") - String thumbnailUrl, - @Schema(description = "피드 타입", example = "IMAGE") - String feedType -) { - - public static NewestFeedListResponse from(FeedListQuery info) { - return NewestFeedListResponse.builder() - .id(info.id()) - .thumbnailUrl(info.thumbnailUrl()) - .feedType(info.feedType()) - .build(); - } -} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/NewestFeedPerClubPageResponse.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/NewestFeedPerClubPageResponse.java new file mode 100644 index 00000000..f4283a96 --- /dev/null +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/NewestFeedPerClubPageResponse.java @@ -0,0 +1,46 @@ +package ddingdong.ddingdongBE.domain.feed.controller.dto.response; + +import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedListQuery; +import ddingdong.ddingdongBE.domain.feed.service.dto.query.NewestFeedPerClubPageQuery; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import lombok.Builder; + +public record NewestFeedPerClubPageResponse( + @ArraySchema(schema = @Schema(name = "동아리 최신 피드 정보", implementation = NewestFeedListResponse.class)) + List newestFeeds, + @Schema(name = "피드 페이지 정보", implementation = PagingResponse.class) + PagingResponse pagingInfo +) { + + public static NewestFeedPerClubPageResponse from(NewestFeedPerClubPageQuery newestFeedPerClubPageQuery) { + List newestFeeds = newestFeedPerClubPageQuery.feedListQueries().stream() + .map(NewestFeedListResponse::from) + .toList(); + return new NewestFeedPerClubPageResponse(newestFeeds, + PagingResponse.from(newestFeedPerClubPageQuery.pagingQuery())); + } + + @Builder + public record NewestFeedListResponse( + @Schema(description = "피드 ID", example = "1") + Long id, + @Schema(description = "피드 썸네일 CDN URL", example = "https://%s.s3.%s.amazonaws.com/%s/%s/%s") + String thumbnailCdnUrl, + @Schema(description = "피드 썸네일 S3 URL", example = "https://%s.s3.%s.amazonaws.com/%s/%s/%s") + String thumbnailOriginUrl, + @Schema(description = "피드 타입", example = "IMAGE") + String feedType + ) { + + public static NewestFeedListResponse from(FeedListQuery query) { + return NewestFeedListResponse.builder() + .id(query.id()) + .thumbnailOriginUrl(query.thumbnailOriginUrl()) + .thumbnailCdnUrl(query.thumbnailCdnUrl()) + .feedType(query.feedType()) + .build(); + } + } +} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/PagingResponse.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/PagingResponse.java new file mode 100644 index 00000000..96d26db2 --- /dev/null +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/PagingResponse.java @@ -0,0 +1,18 @@ +package ddingdong.ddingdongBE.domain.feed.controller.dto.response; + +import ddingdong.ddingdongBE.domain.feed.service.dto.query.PagingQuery; +import io.swagger.v3.oas.annotations.media.Schema; + +public record PagingResponse( + @Schema(name = "현재 커서 id", description = "9") + Long currentCursorId, + @Schema(name = "다음 커서 id", description = "10") + Long nextCursorId, + @Schema(name = "다음 커서 존재 여부", description = "true") + boolean hasNext +) { + + public static PagingResponse from(PagingQuery pagingQuery) { + return new PagingResponse(pagingQuery.currentCursorId(), pagingQuery.nextCursorId(), pagingQuery.hasNext()); + } +} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java index 2adfe775..a923c471 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java @@ -1,31 +1,43 @@ package ddingdong.ddingdongBE.domain.feed.repository; import ddingdong.ddingdongBE.domain.feed.entity.Feed; -import java.util.List; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; public interface FeedRepository extends JpaRepository { - @Query(value = """ - SELECT * FROM feed - WHERE club_id =:clubId - AND deleted_at IS NULL - ORDER BY id DESC - """ - , nativeQuery = true) - List findAllByClubIdOrderById(@Param("clubId") Long clubId); + @Query(value = """ + select * + from feed f + where (:currentCursorId = -1 or id < :currentCursorId) + and f.club_id = :clubId + and deleted_at is null + order by f.id DESC + limit :size + """, nativeQuery = true) + Slice findPageByClubIdOrderById( + @Param("clubId") Long clubId, + @Param("size") int size, + @Param("currentCursorId") Long currentCursorId + ); + + @Query(value = """ + select * from feed f + where f.id in + (select max(id) + from feed + where deleted_at is null + GROUP BY club_id) + and (:currentCursorId = -1 or id < :currentCursorId) + ORDER BY id DESC + limit :size + """, + nativeQuery = true) + Slice findNewestPerClubPage( + @Param("size") int size, + @Param("currentCursorId") Long currentCursorId + ); - @Query(value = """ - SELECT * FROM feed f - WHERE f.id in - (SELECT max(id) - FROM feed - WHERE deleted_at IS NULL - GROUP BY club_id) - ORDER BY id DESC - """ - , nativeQuery = true) - List findNewestAll(); } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedService.java index 0ed641f6..0fd3af69 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedService.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedService.java @@ -1,16 +1,24 @@ package ddingdong.ddingdongBE.domain.feed.service; -import static ddingdong.ddingdongBE.domain.fileinformation.entity.FileDomainCategory.CLUB_PROFILE; -import static ddingdong.ddingdongBE.domain.fileinformation.entity.FileTypeCategory.IMAGE; - +import ddingdong.ddingdongBE.common.exception.PersistenceException.ResourceNotFound; import ddingdong.ddingdongBE.domain.club.entity.Club; import ddingdong.ddingdongBE.domain.feed.entity.Feed; +import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubFeedPageQuery; import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubProfileQuery; +import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedFileUrlQuery; import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedListQuery; import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedQuery; -import ddingdong.ddingdongBE.domain.fileinformation.service.FileInformationService; +import ddingdong.ddingdongBE.domain.feed.service.dto.query.NewestFeedPerClubPageQuery; +import ddingdong.ddingdongBE.domain.feed.service.dto.query.PagingQuery; +import ddingdong.ddingdongBE.domain.filemetadata.entity.DomainType; +import ddingdong.ddingdongBE.domain.filemetadata.entity.FileMetaData; +import ddingdong.ddingdongBE.domain.filemetadata.service.FileMetaDataService; +import ddingdong.ddingdongBE.file.service.S3FileService; +import ddingdong.ddingdongBE.file.service.dto.query.UploadedFileUrlQuery; +import ddingdong.ddingdongBE.file.service.dto.query.UploadedVideoUrlQuery; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,41 +28,72 @@ public class FacadeFeedService { private final FeedService feedService; - private final FileInformationService fileInformationService; + private final FileMetaDataService fileMetaDataService; + private final S3FileService s3FileService; - public List getAllByClubId(Long clubId) { - List feeds = feedService.getAllByClubId(clubId); - return feeds.stream() - .map(FeedListQuery::from) - .toList(); + public ClubFeedPageQuery getFeedPageByClub(Long clubId, int size, Long currentCursorId) { + Slice feedPage = feedService.getFeedPageByClubId(clubId, size, currentCursorId); + List feeds = feedPage.getContent(); + + List feedListQueries = buildFeedListQuery(feeds); + PagingQuery pagingQuery = PagingQuery.of(currentCursorId, feeds.get(feeds.size() -1).getId(), feedPage.hasNext()); + + return ClubFeedPageQuery.of(feedListQueries, pagingQuery); } - public List getNewestAll() { - List feeds = feedService.getNewestAll(); - return feeds.stream() - .map(FeedListQuery::from) - .toList(); + public NewestFeedPerClubPageQuery getNewestFeedPerClubPage(int size, Long currentCursorId) { + Slice feedPage = feedService.getNewestFeedPerClubPage(size, currentCursorId); + List feeds = feedPage.getContent(); + + List feedListQueries = buildFeedListQuery(feeds); + PagingQuery pagingQuery = PagingQuery.of(currentCursorId, feeds.get(feeds.size() -1).getId(), feedPage.hasNext()); + + return NewestFeedPerClubPageQuery.of(feedListQueries, pagingQuery); } public FeedQuery getById(Long feedId) { Feed feed = feedService.getById(feedId); ClubProfileQuery clubProfileQuery = extractClubInfo(feed.getClub()); - return FeedQuery.of(feed, clubProfileQuery); + FeedFileUrlQuery feedFileUrlQuery = extractFeedFileInfo(feed); + return FeedQuery.of(feed, clubProfileQuery, feedFileUrlQuery); + } + + private FeedFileUrlQuery extractFeedFileInfo(Feed feed) { + FileMetaData fileMetaData = getFileMetaData(feed.getFeedType().getDomainType(), feed.getId()); + if (feed.isImage()) { + UploadedFileUrlQuery urlQuery = s3FileService.getUploadedFileUrl(fileMetaData.getFileKey()); + return new FeedFileUrlQuery(urlQuery.id(), urlQuery.originUrl(), urlQuery.cdnUrl()); + } + + if (feed.isVideo()) { + UploadedVideoUrlQuery urlQuery = s3FileService.getUploadedVideoUrl(fileMetaData.getFileKey()); + return new FeedFileUrlQuery(fileMetaData.getId().toString(), urlQuery.videoOriginUrl(), urlQuery.videoCdnUrl()); + } + + throw new IllegalArgumentException("FeedType은 Image 혹은 Video여야 합니다."); } private ClubProfileQuery extractClubInfo(Club club) { String clubName = club.getName(); - List profileImageUrls = fileInformationService.getImageUrls( - IMAGE.getFileType() + CLUB_PROFILE.getFileDomain() + club.getId() - ); - String profileImageUrl = profileImageUrls.stream() + FileMetaData fileMetaData = getFileMetaData(DomainType.CLUB_PROFILE, club.getId()); + UploadedFileUrlQuery urlQuery = s3FileService.getUploadedFileUrl(fileMetaData.getFileKey()); + return new ClubProfileQuery(club.getId(), clubName, urlQuery.originUrl(), urlQuery.cdnUrl()); + } + + private FileMetaData getFileMetaData(DomainType domainType, Long id) { + return fileMetaDataService.getCoupledAllByDomainTypeAndEntityId(domainType, id) + .stream() .findFirst() - .orElse(null); + .orElseThrow(() -> new ResourceNotFound("해당 FileMetaData(feedId: " + id + ")를 찾을 수 없습니다.)")); + } - return ClubProfileQuery.builder() - .id(club.getId()) - .name(clubName) - .profileImageUrl(profileImageUrl) - .build(); + private List buildFeedListQuery(List feeds) { + return feeds.stream() + .map(feed -> { + FileMetaData fileMetaData = getFileMetaData(feed.getFeedType().getDomainType(), feed.getId()); + UploadedVideoUrlQuery urlQuery = s3FileService.getUploadedVideoUrl(fileMetaData.getFileKey()); + return FeedListQuery.of(feed, urlQuery); + }).toList(); } + } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedService.java index 10bc31a9..737a0263 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedService.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedService.java @@ -1,14 +1,14 @@ package ddingdong.ddingdongBE.domain.feed.service; import ddingdong.ddingdongBE.domain.feed.entity.Feed; -import java.util.List; +import org.springframework.data.domain.Slice; import java.util.Optional; public interface FeedService { - List getAllByClubId(Long clubId); + Slice getFeedPageByClubId(Long clubId, int size, Long currentCursorId); - List getNewestAll(); + Slice getNewestFeedPerClubPage(int size, Long currentCursorId); Feed getById(Long feedId); diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedService.java index 4aafa9c3..e2b6b037 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedService.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedService.java @@ -3,9 +3,13 @@ import ddingdong.ddingdongBE.common.exception.PersistenceException.ResourceNotFound; import ddingdong.ddingdongBE.domain.feed.entity.Feed; import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,13 +21,14 @@ public class GeneralFeedService implements FeedService { private final FeedRepository feedRepository; @Override - public List getAllByClubId(Long clubId) { - return feedRepository.findAllByClubIdOrderById(clubId); + public Slice getFeedPageByClubId(Long clubId, int size, Long currentCursorId) { + Slice feedPages = feedRepository.findPageByClubIdOrderById(clubId, size + 1, currentCursorId); + return buildSlice(feedPages, size); } - @Override - public List getNewestAll() { - return feedRepository.findNewestAll(); + public Slice getNewestFeedPerClubPage(int size, Long currentCursorId) { + Slice feedPages = feedRepository.findNewestPerClubPage(size + 1, currentCursorId); + return buildSlice(feedPages, size); } @Override @@ -49,4 +54,19 @@ public Long create(Feed feed) { public void delete(Feed feed) { feedRepository.delete(feed); } + + private Slice buildSlice(Slice originalSlice, int size) { + List content = new ArrayList<>(originalSlice.getContent()); + if (content.isEmpty()) { + throw new ResourceNotFound("Feed 페이지 내 콘텐츠를 찾을 수 없습니다."); + } + + boolean hasNext = content.size() > size; + + if (hasNext) { + content.remove(content.size() - 1); + } + + return new SliceImpl<>(content, PageRequest.of(originalSlice.getNumber(), size), hasNext); + } } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubFeedPageQuery.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubFeedPageQuery.java new file mode 100644 index 00000000..d5bc8191 --- /dev/null +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubFeedPageQuery.java @@ -0,0 +1,13 @@ +package ddingdong.ddingdongBE.domain.feed.service.dto.query; + +import java.util.List; + +public record ClubFeedPageQuery( + List feedListQueries, + PagingQuery pagingQuery +) { + + public static ClubFeedPageQuery of(List feedListQueries, PagingQuery pagingQuery) { + return new ClubFeedPageQuery(feedListQueries, pagingQuery); + } +} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubProfileQuery.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubProfileQuery.java index 49b4e64c..83136188 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubProfileQuery.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubProfileQuery.java @@ -6,7 +6,8 @@ public record ClubProfileQuery( Long id, String name, - String profileImageUrl + String profileImageOriginUrl, + String profileImageCdnUrl ) { } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedFileUrlQuery.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedFileUrlQuery.java new file mode 100644 index 00000000..38bf3f53 --- /dev/null +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedFileUrlQuery.java @@ -0,0 +1,13 @@ +package ddingdong.ddingdongBE.domain.feed.service.dto.query; + +public record FeedFileUrlQuery( + String id, + String originUrl, + String cdnUrl +) { + + public static FeedFileUrlQuery of(String id, String originUrl, String cdnUrl) { + return new FeedFileUrlQuery(id, originUrl, cdnUrl); + } + +} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedListQuery.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedListQuery.java index 25901ec5..094941c9 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedListQuery.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedListQuery.java @@ -1,19 +1,22 @@ package ddingdong.ddingdongBE.domain.feed.service.dto.query; import ddingdong.ddingdongBE.domain.feed.entity.Feed; +import ddingdong.ddingdongBE.file.service.dto.query.UploadedVideoUrlQuery; import lombok.Builder; @Builder public record FeedListQuery( Long id, - String thumbnailUrl, + String thumbnailCdnUrl, + String thumbnailOriginUrl, String feedType ) { - public static FeedListQuery from(Feed feed) { + public static FeedListQuery of(Feed feed, UploadedVideoUrlQuery urlQuery) { return FeedListQuery.builder() .id(feed.getId()) - .thumbnailUrl(null) + .thumbnailCdnUrl(urlQuery.thumbnailCdnUrl()) + .thumbnailOriginUrl(urlQuery.thumbnailOriginUrl()) .feedType(feed.getFeedType().toString()) .build(); } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedQuery.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedQuery.java index 44b69d78..27944784 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedQuery.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/FeedQuery.java @@ -7,19 +7,19 @@ @Builder public record FeedQuery( Long id, - ClubProfileQuery clubProfileQuery, String activityContent, - String fileUrl, String feedType, - LocalDate createdDate -) { + LocalDate createdDate, + FeedFileUrlQuery feedFileUrlQuery, + ClubProfileQuery clubProfileQuery + ) { - public static FeedQuery of(Feed feed, ClubProfileQuery clubProfileQuery) { + public static FeedQuery of(Feed feed, ClubProfileQuery clubProfileQuery, FeedFileUrlQuery feedFileUrlQuery) { return FeedQuery.builder() .id(feed.getId()) .clubProfileQuery(clubProfileQuery) .activityContent(feed.getActivityContent()) - .fileUrl(null) + .feedFileUrlQuery(feedFileUrlQuery) .feedType(feed.getFeedType().toString()) .createdDate(LocalDate.from(feed.getCreatedAt())) .build(); diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/NewestFeedPerClubPageQuery.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/NewestFeedPerClubPageQuery.java new file mode 100644 index 00000000..4b9b8816 --- /dev/null +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/NewestFeedPerClubPageQuery.java @@ -0,0 +1,13 @@ +package ddingdong.ddingdongBE.domain.feed.service.dto.query; + +import java.util.List; + +public record NewestFeedPerClubPageQuery( + List feedListQueries, + PagingQuery pagingQuery +) { + + public static NewestFeedPerClubPageQuery of(List feedListQueries, PagingQuery pagingQuery) { + return new NewestFeedPerClubPageQuery(feedListQueries, pagingQuery); + } +} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/PagingQuery.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/PagingQuery.java new file mode 100644 index 00000000..f2f8168b --- /dev/null +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/PagingQuery.java @@ -0,0 +1,12 @@ +package ddingdong.ddingdongBE.domain.feed.service.dto.query; + +public record PagingQuery( + Long currentCursorId, + Long nextCursorId, + boolean hasNext +) { + + public static PagingQuery of(Long currentCursorId, Long nextCursorId, boolean hasNext) { + return new PagingQuery(currentCursorId, nextCursorId, hasNext); + } +} diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepositoryTest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepositoryTest.java index c7ae5568..f2e9b7e4 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepositoryTest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepositoryTest.java @@ -1,5 +1,7 @@ package ddingdong.ddingdongBE.domain.feed.repository; +import static org.assertj.core.api.Assertions.assertThat; + import com.navercorp.fixturemonkey.FixtureMonkey; import ddingdong.ddingdongBE.common.support.DataJpaTestSupport; import ddingdong.ddingdongBE.common.support.FixtureMonkeyFactory; @@ -10,10 +12,11 @@ import ddingdong.ddingdongBE.domain.scorehistory.entity.Score; import java.math.BigDecimal; import java.util.List; -import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Slice; class FeedRepositoryTest extends DataJpaTestSupport { @@ -25,46 +28,183 @@ class FeedRepositoryTest extends DataJpaTestSupport { private final FixtureMonkey fixture = FixtureMonkeyFactory.getNotNullBuilderIntrospectorMonkey(); - @DisplayName("동아리 ID로 해당 동아리의 모든 피드를 최신순으로 조회한다.") + @BeforeEach + void setUp() { + feedRepository.deleteAll(); + feedRepository.flush(); + clubRepository.deleteAll(); + clubRepository.flush(); + } + + @DisplayName("모든 동아리의 최신 피드 페이지를 주어진 정보에 맞춰 반환한다.") @Test - void findAllByClubIdOrderById() { + void findNewestPerClubPage() { + // given + Club club1 = fixture.giveMeBuilder(Club.class) + .set("name", "카우1") + .set("user", null) + .set("score", Score.from(BigDecimal.ZERO)) + .set("clubMembers", null) + .sample(); + Club club2 = fixture.giveMeBuilder(Club.class) + .set("name", "카우2") + .set("user", null) + .set("score", Score.from(BigDecimal.ZERO)) + .set("clubMembers", null) + .sample(); + Club club3 = fixture.giveMeBuilder(Club.class) + .set("name", "카우3") + .set("user", null) + .set("score", Score.from(BigDecimal.ZERO)) + .set("clubMembers", null) + .sample(); + Club savedClub1 = clubRepository.save(club1); + Club savedClub2 = clubRepository.save(club2); + Club savedClub3 = clubRepository.save(club3); + + Feed feed1 = fixture.giveMeBuilder(Feed.class) + .set("id", 1L) + .set("club", savedClub1) + .set("activityContent", "내용 1 올드") + .sample(); + Feed feed2 = fixture.giveMeBuilder(Feed.class) + .set("id", 2L) + .set("club", savedClub1) + .set("activityContent", "내용 1 최신") + .sample(); + Feed feed3 = fixture.giveMeBuilder(Feed.class) + .set("id", 3L) + .set("club", savedClub2) + .set("activityContent", "내용 2 올드") + .sample(); + Feed feed4 = fixture.giveMeBuilder(Feed.class) + .set("id", 4L) + .set("club", savedClub2) + .set("activityContent", "내용 2 최신") + .sample(); + Feed feed5 = fixture.giveMeBuilder(Feed.class) + .set("id", 5L) + .set("club", savedClub3) + .set("activityContent", "내용 3 올드") + .sample(); + Feed feed6 = fixture.giveMeBuilder(Feed.class) + .set("id", 6L) + .set("club", savedClub3) + .set("activityContent", "내용 3 최신") + .sample(); + feedRepository.saveAll(List.of(feed1, feed2, feed3, feed4, feed5, feed6)); + + int size = 2; + Long currentCursorId = -1L; + // when + Slice newestFeeds = feedRepository.findNewestPerClubPage(size, currentCursorId); + + // then + List feeds = newestFeeds.getContent(); + assertThat(feeds.size()).isEqualTo(2); + assertThat(feeds.get(0).getId()).isEqualTo(6); + assertThat(feeds.get(1).getId()).isEqualTo(4); + } + + @DisplayName("size 개수보다 남은 feed의 개수가 적다면, 그 수만큼 페이지로 반환한다.") + @Test + void 페이지네이션_남은_개수가_사이즈보다_적은경우() { // given Club club = fixture.giveMeBuilder(Club.class) - .set("name", "카우") - .set("user", null) - .set("score", Score.from(BigDecimal.ZERO)) - .set("clubMembers", null) - .sample(); + .set("name", "카우") + .set("user", null) + .set("score", Score.from(BigDecimal.ZERO)) + .set("clubMembers", null) + .sample(); Club savedClub = clubRepository.save(club); Feed feed1 = fixture.giveMeBuilder(Feed.class) - .set("club", savedClub) - .set("activityContent", "내용1") - .set("feedType", FeedType.IMAGE) - .sample(); + .set("id", 1L) + .set("club", savedClub) + .set("activityContent", "내용1") + .set("feedType", FeedType.IMAGE) + .sample(); Feed feed2 = fixture.giveMeBuilder(Feed.class) - .set("club", savedClub) - .set("activityContent", "내용2") - .set("feedType", FeedType.VIDEO) - .sample(); + .set("id", 2L) + .set("club", savedClub) + .set("activityContent", "내용2") + .set("feedType", FeedType.VIDEO) + .sample(); Feed feed3 = fixture.giveMeBuilder(Feed.class) - .set("club", savedClub) - .set("activityContent", "내용3") - .set("feedType", FeedType.IMAGE) - .sample(); - feedRepository.save(feed1); - feedRepository.save(feed2); - feedRepository.save(feed3); + .set("id", 3L) + .set("club", savedClub) + .set("activityContent", "내용3") + .set("feedType", FeedType.IMAGE) + .sample(); + Feed feed4 = fixture.giveMeBuilder(Feed.class) + .set("id", 4L) + .set("club", savedClub) + .set("activityContent", "내용4") + .set("feedType", FeedType.IMAGE) + .sample(); + feedRepository.saveAll(List.of(feed1, feed2, feed3, feed4)); + Long clubId = savedClub.getId(); + int size = 2; + Long cursorId = 2L; // when - List feeds = feedRepository.findAllByClubIdOrderById(savedClub.getId()); + Slice page = feedRepository.findPageByClubIdOrderById(clubId, size, cursorId); + // then + List feeds = page.getContent(); + assertThat(feeds.size()).isEqualTo(1); + assertThat(feeds.get(0).getId()).isEqualTo(1); + assertThat(feeds.get(0).getActivityContent()).isEqualTo(feed1.getActivityContent()); + } + @DisplayName("cursorId보다 작은 Feed를 size 개수만큼 페이지로 반환한다.") + @Test + void findPageByClubIdOrderById() { + // given + Club club = fixture.giveMeBuilder(Club.class) + .set("name", "카우") + .set("user", null) + .set("score", Score.from(BigDecimal.ZERO)) + .set("clubMembers", null) + .sample(); + Club savedClub = clubRepository.save(club); + + Feed feed1 = fixture.giveMeBuilder(Feed.class) + .set("id", 1L) + .set("club", savedClub) + .set("activityContent", "내용1") + .set("feedType", FeedType.IMAGE) + .sample(); + Feed feed2 = fixture.giveMeBuilder(Feed.class) + .set("id", 2L) + .set("club", savedClub) + .set("activityContent", "내용2") + .set("feedType", FeedType.VIDEO) + .sample(); + Feed feed3 = fixture.giveMeBuilder(Feed.class) + .set("id", 3L) + .set("club", savedClub) + .set("activityContent", "내용3") + .set("feedType", FeedType.IMAGE) + .sample(); + Feed feed4 = fixture.giveMeBuilder(Feed.class) + .set("id", 4L) + .set("club", savedClub) + .set("activityContent", "내용4") + .set("feedType", FeedType.IMAGE) + .sample(); + feedRepository.saveAll(List.of(feed1, feed2, feed3, feed4)); + + Long clubId = savedClub.getId(); + int size = 2; + Long cursorId = 4L; + // when + Slice page = feedRepository.findPageByClubIdOrderById(clubId, size, cursorId); // then - Assertions.assertThat(feeds.get(0).getActivityContent()).isEqualTo("내용3"); - Assertions.assertThat(feeds.get(0).getId()).isEqualTo(3L); - Assertions.assertThat(feeds.get(1).getActivityContent()).isEqualTo("내용2"); - Assertions.assertThat(feeds.get(1).getId()).isEqualTo(2L); - Assertions.assertThat(feeds.get(2).getActivityContent()).isEqualTo("내용1"); - Assertions.assertThat(feeds.get(2).getId()).isEqualTo(1L); + List feeds = page.getContent(); + assertThat(feeds.size()).isEqualTo(2); + assertThat(feeds.get(0).getId()).isEqualTo(feed3.getId()); + assertThat(feeds.get(0).getActivityContent()).isEqualTo(feed3.getActivityContent()); + assertThat(feeds.get(1).getId()).isEqualTo(feed2.getId()); + assertThat(feeds.get(1).getActivityContent()).isEqualTo(feed2.getActivityContent()); } } diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedServiceTest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedServiceTest.java index 404b9dba..b64e1de9 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedServiceTest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedServiceTest.java @@ -1,10 +1,9 @@ package ddingdong.ddingdongBE.domain.feed.service; -import static ddingdong.ddingdongBE.domain.fileinformation.entity.FileDomainCategory.CLUB_PROFILE; -import static ddingdong.ddingdongBE.domain.fileinformation.entity.FileTypeCategory.IMAGE; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; +import static org.mockito.ArgumentMatchers.any; +import com.github.f4b6a3.uuid.UuidCreator; import com.navercorp.fixturemonkey.FixtureMonkey; import ddingdong.ddingdongBE.common.support.FixtureMonkeyFactory; import ddingdong.ddingdongBE.common.support.TestContainerSupport; @@ -13,17 +12,23 @@ import ddingdong.ddingdongBE.domain.feed.entity.Feed; import ddingdong.ddingdongBE.domain.feed.entity.FeedType; import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository; -import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedListQuery; import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedQuery; -import ddingdong.ddingdongBE.domain.fileinformation.service.FileInformationService; +import ddingdong.ddingdongBE.domain.filemetadata.entity.DomainType; +import ddingdong.ddingdongBE.domain.filemetadata.entity.FileMetaData; +import ddingdong.ddingdongBE.domain.filemetadata.entity.FileStatus; +import ddingdong.ddingdongBE.domain.filemetadata.repository.FileMetaDataRepository; import ddingdong.ddingdongBE.domain.scorehistory.entity.Score; +import ddingdong.ddingdongBE.file.service.S3FileService; +import ddingdong.ddingdongBE.file.service.dto.query.UploadedFileUrlQuery; +import ddingdong.ddingdongBE.file.service.dto.query.UploadedVideoUrlQuery; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.BDDMockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; @@ -40,128 +45,74 @@ class FacadeFeedServiceTest extends TestContainerSupport { @Autowired private FacadeFeedService facadeFeedService; + @Autowired + private FileMetaDataRepository fileMetaDataRepository; + @MockBean - private FileInformationService fileInformationService; + private S3FileService s3FileService; private final FixtureMonkey fixture = FixtureMonkeyFactory.getNotNullBuilderIntrospectorMonkey(); - @DisplayName("모든 사용자는 피드를 조회할 수 있다.") - @Test - void getAllFeed() { - // given - Club club = fixture.giveMeBuilder(Club.class) - .set("name", "카우") - .set("user", null) - .set("score", Score.from(BigDecimal.ZERO)) - .set("clubMembers", null) - .sample(); - Club savedClub = clubRepository.save(club); - - Feed feed1 = fixture.giveMeBuilder(Feed.class) - .set("club", savedClub) - .set("feedType", FeedType.IMAGE) - .sample(); - Feed feed2 = fixture.giveMeBuilder(Feed.class) - .set("club", savedClub) - .set("feedType", FeedType.VIDEO) - .sample(); - Feed feed3 = fixture.giveMeBuilder(Feed.class) - .set("club", savedClub) - .set("feedType", FeedType.IMAGE) - .sample(); - feedRepository.saveAll(List.of(feed1, feed2, feed3)); - - // when - List infos = facadeFeedService.getAllByClubId(1L); - - // then - assertThat(infos).hasSize(3); + @BeforeEach + void setUp() { + feedRepository.deleteAll(); + feedRepository.flush(); + clubRepository.deleteAll(); + clubRepository.flush(); } -//TODO: Feed 조회 API 개발 완료 후 재작성 - -// @DisplayName("모든 사용자는 전체 동아리의 최신 피드를 조회할 수 있다.") -// @Test -// void getNewestAll() { -// // given -// Club club1 = fixture.giveMeBuilder(Club.class) -// .set("name", "카우1") -// .set("user", null) -// .set("score", Score.from(BigDecimal.ZERO)) -// .set("clubMembers", null) -// .sample(); -// Club club2 = fixture.giveMeBuilder(Club.class) -// .set("name", "카우2") -// .set("user", null) -// .set("score", Score.from(BigDecimal.ZERO)) -// .set("clubMembers", null) -// .sample(); -// Club club3 = fixture.giveMeBuilder(Club.class) -// .set("name", "카우3") -// .set("user", null) -// .set("score", Score.from(BigDecimal.ZERO)) -// .set("clubMembers", null) -// .sample(); -// Club savedClub1 = clubRepository.save(club1); -// Club savedClub2 = clubRepository.save(club2); -// Club savedClub3 = clubRepository.save(club3); -// -// Feed feed1 = fixture.giveMeBuilder(Feed.class) -// .set("club", savedClub1) -// .sample(); -// Feed feed2 = fixture.giveMeBuilder(Feed.class) -// .set("club", savedClub1) -// .sample(); -// Feed feed3 = fixture.giveMeBuilder(Feed.class) -// .set("club", savedClub2) -// .sample(); -// Feed feed4 = fixture.giveMeBuilder(Feed.class) -// .set("club", savedClub2) -// .sample(); -// Feed feed5 = fixture.giveMeBuilder(Feed.class) -// .set("club", savedClub3) -// .sample(); -// Feed feed6 = fixture.giveMeBuilder(Feed.class) -// .set("club", savedClub3) -// .sample(); -// feedRepository.saveAll(List.of(feed1, feed2, feed3, feed4, feed5, feed6)); -// -// // when -// List infos = facadeFeedService.getNewestAll(); -// -// // then -// assertThat(infos).hasSize(3); -// assertThat(infos.get(0).id()).isEqualTo(feed6.getId()); -// assertThat(infos.get(1).id()).isEqualTo(feed4.getId()); -// assertThat(infos.get(2).id()).isEqualTo(feed2.getId()); -// } - @DisplayName("모든 사용자는 동아리 피드에 대해 상세 조회할 수 있다.") @Test void getFeedById() { // given Club club = fixture.giveMeBuilder(Club.class) - .set("name", "카우") - .set("user", null) - .set("score", Score.from(BigDecimal.ZERO)) - .set("clubMembers", null) - .set("deletedAt", null) - .sample(); + .set("id", 1L) + .set("name", "카우") + .set("user", null) + .set("score", Score.from(BigDecimal.ZERO)) + .set("clubMembers", null) + .set("deletedAt", null) + .sample(); Club savedClub = clubRepository.save(club); - - given(fileInformationService.getImageUrls( - IMAGE.getFileType() + CLUB_PROFILE.getFileDomain() + savedClub.getId())) - .willReturn(new ArrayList<>()); + DomainType clubDomainType = DomainType.CLUB_PROFILE; + Long clubEntityId = 1L; + UUID id1 = UuidCreator.getTimeOrderedEpoch(); + fileMetaDataRepository.save( + fixture.giveMeBuilder(FileMetaData.class) + .set("id", id1) + .set("domainType", clubDomainType) + .set("entityId", clubEntityId) + .set("fileStatus", FileStatus.COUPLED) + .sample() + ); LocalDateTime now = LocalDateTime.now(); Feed feed = fixture.giveMeBuilder(Feed.class) - .set("club", savedClub) - .set("activityContent", "카우 활동내역") - .set("feedType", FeedType.IMAGE) - .set("createdAt", now) - .sample(); + .set("id", 1L) + .set("club", savedClub) + .set("activityContent", "카우 활동내역") + .set("feedType", FeedType.IMAGE) + .set("createdAt", now) + .sample(); Feed savedFeed = feedRepository.save(feed); + DomainType domainType = DomainType.FEED_IMAGE; + Long entityId = 1L; + UUID id2 = UuidCreator.getTimeOrderedEpoch(); + fileMetaDataRepository.save( + fixture.giveMeBuilder(FileMetaData.class) + .set("id", id2) + .set("domainType", domainType) + .set("entityId", entityId) + .set("fileStatus", FileStatus.COUPLED) + .sample() + ); + + BDDMockito.given(s3FileService.getUploadedFileUrl(any())) + .willReturn(new UploadedFileUrlQuery(null, null, null)); + BDDMockito.given(s3FileService.getUploadedVideoUrl(any())) + .willReturn(new UploadedVideoUrlQuery(null, null, null,null)); + // when FeedQuery info = facadeFeedService.getById(savedFeed.getId()); @@ -171,7 +122,6 @@ void getFeedById() { assertThat(info.clubProfileQuery().name()).isEqualTo(savedClub.getName()); assertThat(info.activityContent()).isEqualTo(savedFeed.getActivityContent()); assertThat(info.feedType()).isEqualTo(savedFeed.getFeedType().toString()); - assertThat(info.clubProfileQuery().profileImageUrl()).isEqualTo(null); assertThat(info.createdDate()).isEqualTo(LocalDate.from(now)); } } diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedServiceTest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedServiceTest.java index b6375746..94698b41 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedServiceTest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedServiceTest.java @@ -5,13 +5,19 @@ import com.navercorp.fixturemonkey.FixtureMonkey; import ddingdong.ddingdongBE.common.support.FixtureMonkeyFactory; import ddingdong.ddingdongBE.common.support.TestContainerSupport; +import ddingdong.ddingdongBE.domain.club.entity.Club; +import ddingdong.ddingdongBE.domain.club.repository.ClubRepository; import ddingdong.ddingdongBE.domain.feed.entity.Feed; import ddingdong.ddingdongBE.domain.feed.entity.FeedType; import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository; +import ddingdong.ddingdongBE.domain.scorehistory.entity.Score; +import java.math.BigDecimal; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Slice; @SpringBootTest class GeneralFeedServiceTest extends TestContainerSupport { @@ -19,6 +25,9 @@ class GeneralFeedServiceTest extends TestContainerSupport { @Autowired private FeedService feedService; + @Autowired + private ClubRepository clubRepository; + @Autowired private FeedRepository feedRepository; @@ -42,4 +51,51 @@ void create() { assertThat(finded.getActivityContent()).isEqualTo("활동내용"); assertThat(finded.getFeedType()).isEqualTo(FeedType.IMAGE); } + + @DisplayName("입력한 파라미터의 정보에 따라 해당하는 페이지의 정보를 반환한다.") + @Test + void getFeedPageByClubId() { + // given + Club club = fixtureMonkey.giveMeBuilder(Club.class) + .set("name", "카우") + .set("user", null) + .set("score", Score.from(BigDecimal.ZERO)) + .set("clubMembers", null) + .sample(); + Club savedClub = clubRepository.save(club); + + Feed feed1 = fixtureMonkey.giveMeBuilder(Feed.class) + .set("club", savedClub) + .set("activityContent", "내용1") + .set("feedType", FeedType.IMAGE) + .sample(); + Feed feed2 = fixtureMonkey.giveMeBuilder(Feed.class) + .set("club", savedClub) + .set("activityContent", "내용2") + .set("feedType", FeedType.VIDEO) + .sample(); + Feed feed3 = fixtureMonkey.giveMeBuilder(Feed.class) + .set("club", savedClub) + .set("activityContent", "내용3") + .set("feedType", FeedType.IMAGE) + .sample(); + Feed feed4 = fixtureMonkey.giveMeBuilder(Feed.class) + .set("club", savedClub) + .set("activityContent", "내용4") + .set("feedType", FeedType.IMAGE) + .sample(); + feedRepository.saveAll(List.of(feed1, feed2, feed3, feed4)); + + Long clubId = savedClub.getId(); + int size = 2; + Long cursorId = -1L; + // when + Slice page = feedService.getFeedPageByClubId(clubId, size, cursorId); + // then + + assertThat(page.getContent().size()).isEqualTo(2); + assertThat(page.getNumber()).isEqualTo(0); + assertThat(page.getContent().get(page.getContent().size() - 1).getId()).isEqualTo(3); + assertThat(page.hasNext()).isTrue(); + } }