Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DDING-65] Feed 조회 API 구현 및 커서 기반 페이지네이션 적용 #206

Merged
merged 18 commits into from
Jan 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
46c4006
feat : 페이지네이션 적용에 따른 명세 및 dto 수정
KoSeonJe Jan 10, 2025
a2960b3
feat : 커서 기반 페이지네이션 쿼리 작성
KoSeonJe Jan 10, 2025
1094f18
feat : 페이지네이션 적용에 따른 변경 로직 및 FileMetaData 적용에 따른 변경 로직 구현
KoSeonJe Jan 10, 2025
b121c94
feat : feed상세 조회 API 명세 수정에 따른 dto 수정
KoSeonJe Jan 10, 2025
3c34283
feat : feed url 처리 로직 수정
KoSeonJe Jan 10, 2025
bed1bdf
feat : page찾기 메소드 이름 수정
KoSeonJe Jan 10, 2025
5ade9f3
test : 커서 기반 페이지네이션 쿼리 테스트 작성
KoSeonJe Jan 10, 2025
f679b60
test : page 정확한 정보 반환하는지 테스트 작성
KoSeonJe Jan 11, 2025
c5b4866
feat : 다음 페이지 존재 여부 로직 추가
KoSeonJe Jan 11, 2025
eb7cf83
refactor: pagingQuery 입력 변수 수정
KoSeonJe Jan 11, 2025
b598176
test : repository 테스트 일부 수정
KoSeonJe Jan 11, 2025
aa2adb5
feat : 모든 동아리 최신 피드 페이지 조회 API 구현
KoSeonJe Jan 11, 2025
c82b50d
test : 모든 동아리 최신 피드 페이지 조회 쿼리 테스트 작성
KoSeonJe Jan 11, 2025
78c3698
refactor : 사용하지 않은 쿼리 제거
KoSeonJe Jan 11, 2025
e561ec8
merge develop
KoSeonJe Jan 11, 2025
edadc87
test : test 에러 수정
KoSeonJe Jan 11, 2025
9ca4756
refactor : pathVariable 파라미터 지정
KoSeonJe Jan 12, 2025
7374d2f
feat : page의 콘텐츠가 빈 값일 때 예외 추가
KoSeonJe Jan 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 28 additions & 21 deletions src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedApi.java
Original file line number Diff line number Diff line change
@@ -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<FeedListResponse> 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<NewestFeedListResponse> 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);
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -18,19 +18,22 @@ public class FeedController implements FeedApi {
private final FacadeFeedService facadeFeedService;

@Override
public List<FeedListResponse> getAllFeedByClubId(Long clubId) {
List<FeedListQuery> 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<NewestFeedListResponse> getNewestAllFeed() {
List<FeedListQuery> 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ClubFeedListResponse> clubFeeds,
@Schema(name = "피드 페이지 정보", implementation = PagingResponse.class)
PagingResponse pagingInfo
) {

public static ClubFeedPageResponse from(ClubFeedPageQuery clubFeedPageQuery) {
List<ClubFeedListResponse> 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();
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<NewestFeedListResponse> newestFeeds,
@Schema(name = "피드 페이지 정보", implementation = PagingResponse.class)
PagingResponse pagingInfo
) {

public static NewestFeedPerClubPageResponse from(NewestFeedPerClubPageQuery newestFeedPerClubPageQuery) {
List<NewestFeedListResponse> 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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
Comment on lines +15 to +17
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

from 메서드에 null 체크 추가 필요

pagingQuery가 null인 경우에 대한 처리가 누락되었습니다.

public static PagingResponse from(PagingQuery pagingQuery) {
    Objects.requireNonNull(pagingQuery, "PagingQuery must not be null");
    return new PagingResponse(
        pagingQuery.currentCursorId(),
        pagingQuery.nextCursorId(),
        pagingQuery.hasNext()
    );
}

}
Loading
Loading