Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import EatPic.spring.domain.card.dto.request.CardCreateRequest;
import EatPic.spring.domain.card.dto.request.CardCreateRequest.CardUpdateRequest;
import EatPic.spring.domain.card.dto.response.CardResponse;
import EatPic.spring.domain.card.dto.response.CardResponse.CardDetailResponse;
import EatPic.spring.domain.card.dto.response.CardResponse.CardFeedResponse;
import EatPic.spring.domain.card.dto.response.CardResponse.CreateCardResponse;
import EatPic.spring.domain.card.dto.response.CardResponse.TodayCardResponse;
import EatPic.spring.domain.card.entity.Card;
import EatPic.spring.domain.card.repository.CardRepository;
import EatPic.spring.domain.card.service.CardService;
import EatPic.spring.domain.comment.dto.CommentResponseDTO;
import EatPic.spring.global.common.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand All @@ -20,14 +22,7 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
Expand Down Expand Up @@ -108,4 +103,15 @@ public ResponseEntity<ApiResponse<CardDetailResponse>> updateCard(
return ResponseEntity.ok(ApiResponse.onSuccess(cardService.updateCard(cardId, userId, request)));
}

@Operation(
summary = "피드 조회",
description = "특정 사용자(null이면 전체 사용자)의 최근 7일 동안의 피드를 조회합니다.(전체, 본인의 경우 전체 피드를 조회합니다)")
@GetMapping("/community/feeds")
public ApiResponse<CardResponse.PagedCardFeedResponseDto> getFeeds(@RequestParam(required = false) Long userId,
@RequestParam(required = false) Long cursor,
@RequestParam(defaultValue = "15") int size) {
return ApiResponse.onSuccess(cardService.getCardFeedByCursor(userId,size,cursor));
}


}
109 changes: 60 additions & 49 deletions src/main/java/EatPic/spring/domain/card/converter/CardConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import EatPic.spring.domain.card.mapping.CardHashtag;
import EatPic.spring.domain.reaction.entity.Reaction;
import EatPic.spring.domain.user.entity.User;
import org.springframework.data.domain.Slice;

import java.util.List;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -59,62 +61,71 @@ public static CardResponse.CreateCardResponse toCreateCardResponse(Card card) {

public static CardDetailResponse toCardDetailResponse(Card card, Long nextCardId) {
return CardDetailResponse.builder()
.cardId(card.getId())
.imageUrl(card.getCardImageUrl())
.date(card.getCreatedAt().toLocalDate())
.time(card.getCreatedAt().toLocalTime())
.mealType(card.getMeal())
.recipeUrl(card.getRecipeUrl())
.latitude(card.getLatitude())
.longitude(card.getLongitude())
.locationText(card.getLocationText())
.memo(card.getMemo())
.recipe(card.getRecipe())
.nextMeal(nextCardId != null ?
NextMealCard.builder().cardId(nextCardId).build() : null)
.build();
.cardId(card.getId())
.imageUrl(card.getCardImageUrl())
.date(card.getCreatedAt().toLocalDate())
.time(card.getCreatedAt().toLocalTime())
.mealType(card.getMeal())
.recipeUrl(card.getRecipeUrl())
.latitude(card.getLatitude())
.longitude(card.getLongitude())
.locationText(card.getLocationText())
.memo(card.getMemo())
.recipe(card.getRecipe())
.nextMeal(nextCardId != null ?
NextMealCard.builder().cardId(nextCardId).build() : null)
.build();
}

public static CardResponse.CardFeedResponse toFeedResponse(
Card card,
List<CardHashtag> cardHashtags,
User writer,
Reaction userReaction,
int totalReactionCount,
int commentCount,
boolean isBookmarked) {
Card card,
List<CardHashtag> cardHashtags,
User writer,
Reaction userReaction,
int totalReactionCount,
int commentCount,
boolean isBookmarked) {
return CardResponse.CardFeedResponse.builder()
.cardId(card.getId())
.imageUrl(card.getCardImageUrl())
.date(card.getCreatedAt().toLocalDate())
.time(card.getCreatedAt().toLocalTime())
.meal(card.getMeal())
.memo(card.getMemo())
.recipe(card.getRecipe())
.recipeUrl(card.getRecipeUrl())
.latitude(card.getLatitude())
.longitude(card.getLongitude())
.locationText(card.getLocationText())
.hashtags(cardHashtags.stream()
.map(ch -> ch.getHashtag().getHashtagName())
.collect(Collectors.toList()))
.user(CardResponse.CardFeedUserDTO.builder()
.userId(writer.getId())
.nickname(writer.getNickname())
.profileImageUrl(writer.getProfileImageUrl())
.build())
.reactionCount(totalReactionCount)
.userReaction(userReaction != null ? userReaction.getReactionType().name() : null)
.commentCount(commentCount)
.isBookmarked(isBookmarked)
.build();
.cardId(card.getId())
.imageUrl(card.getCardImageUrl())
.date(card.getCreatedAt().toLocalDate())
.time(card.getCreatedAt().toLocalTime())
.meal(card.getMeal())
.memo(card.getMemo())
.recipe(card.getRecipe())
.recipeUrl(card.getRecipeUrl())
.latitude(card.getLatitude())
.longitude(card.getLongitude())
.locationText(card.getLocationText())
.hashtags(cardHashtags.stream()
.map(ch -> ch.getHashtag().getHashtagName())
.collect(Collectors.toList()))
.user(CardResponse.CardFeedUserDTO.builder()
.userId(writer.getId())
.nickname(writer.getNickname())
.profileImageUrl(writer.getProfileImageUrl())
.build())
.reactionCount(totalReactionCount)
.userReaction(userReaction != null ? userReaction.getReactionType().name() : null)
.commentCount(commentCount)
.isBookmarked(isBookmarked)
.build();
}

public static TodayCardResponse toTodayCard(Card card) {
return TodayCardResponse.builder()
.cardId(card.getId())
.cardImageUrl(card.getCardImageUrl())
.meal(card.getMeal())
.build();
.cardId(card.getId())
.cardImageUrl(card.getCardImageUrl())
.meal(card.getMeal())
.build();
}

public static CardResponse.PagedCardFeedResponseDto toPagedCardFeedResponseDTto(Long userId, Slice<Card> cardSlice, List<CardFeedResponse> feedList) {
return CardResponse.PagedCardFeedResponseDto.builder()
.selectedId(userId)
.hasNext(cardSlice.hasNext())
.nextCursor(cardSlice.hasNext() ? cardSlice.getContent().get(cardSlice.getContent().size() - 1).getId() : null)
.cardFeedList(feedList)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,16 @@ public static class TodayCardResponse {
private Meal meal;
}

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class PagedCardFeedResponseDto{
private Long selectedId;
private boolean hasNext;
private Long nextCursor;
private List<CardFeedResponse> cardFeedList;
}



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,14 @@ public interface CardRepository extends JpaRepository<Card, Long> {
Optional<Card> findByIdAndIsDeletedFalse(Long id);

List<Card> findAllByUserAndCreatedAtBetween(User user, LocalDateTime start, LocalDateTime end);
}

Slice<Card> findByIsDeletedFalseAndIsSharedTrueOrderByIdDesc(Pageable pageable);
Slice<Card> findByIsDeletedFalseAndIsSharedTrueAndIdLessThanOrderByIdDesc(Long cursor, Pageable pageable);

Slice<Card> findByIsDeletedFalseAndUserIdOrderByIdDesc(Long userId, Pageable pageable);
Slice<Card> findByIsDeletedFalseAndIsSharedTrueAndUserIdAndIdLessThanOrderByIdDesc(Long userId, Long cursor, Pageable pageable);

Slice<Card> findByIsDeletedFalseAndUserIdAndCreatedAtAfterOrderByIdDesc(Long userId, LocalDateTime sevenDaysAgo, Pageable pageable);
Slice<Card> findByIsDeletedFalseAndUserIdAndCreatedAtAfterAndIdLessThanOrderByIdDesc(Long userId, LocalDateTime sevenDaysAgo, Long cursor, Pageable pageable);

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ public interface CardService {
void deleteCard(Long cardId, Long userId);
List<TodayCardResponse> getTodayCards(Long userId);
CardDetailResponse updateCard(Long cardId, Long userId, CardUpdateRequest request);
CardResponse.PagedCardFeedResponseDto getCardFeedByCursor(Long userId, int size, Long cursor);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import EatPic.spring.domain.user.entity.User;
import EatPic.spring.domain.user.repository.UserRepository;
import EatPic.spring.global.common.code.status.ErrorStatus;
import EatPic.spring.global.common.exception.GeneralException;
import EatPic.spring.global.common.exception.handler.ExceptionHandler;
import java.time.LocalDate;
import java.time.LocalDateTime;
Expand All @@ -27,9 +28,15 @@
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.security.core.parameters.P;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static EatPic.spring.global.common.code.status.ErrorStatus.*;

@Service
@RequiredArgsConstructor
@Slf4j
Expand Down Expand Up @@ -196,4 +203,47 @@ public CardDetailResponse updateCard(Long cardId, Long userId, CardUpdateRequest
// 수정 후 최신 데이터로 응답
return CardConverter.toCardDetailResponse(card, null); // nextCardId는 수정 시점에는 null로
}

@Override
@Transactional(readOnly = true)
public CardResponse.PagedCardFeedResponseDto getCardFeedByCursor(Long userId, int size, Long cursor) {

Slice<Card> cardSlice;
Pageable pageable = PageRequest.of(0, size);
if(userId == null) { // 전체 선택
if (cursor == null) {
cardSlice = cardRepository.findByIsDeletedFalseAndIsSharedTrueOrderByIdDesc(pageable);
} else {
cardSlice = cardRepository.findByIsDeletedFalseAndIsSharedTrueAndIdLessThanOrderByIdDesc(cursor, pageable);
}
}else if(userId == 1L){ // 내 피드 조회 todo: 로그인 유저로
// 전체 기록
if(cursor == null){
cardSlice = cardRepository.findByIsDeletedFalseAndUserIdOrderByIdDesc(userId,pageable);
}else{
cardSlice = cardRepository.findByIsDeletedFalseAndIsSharedTrueAndUserIdAndIdLessThanOrderByIdDesc(userId,cursor,pageable);
}
}else{ // 선택한 사용자
//최근 7일 기록
LocalDateTime sevenDaysAgo = LocalDateTime.now().minusDays(7);
if(cursor == null){
cardSlice = cardRepository.findByIsDeletedFalseAndUserIdAndCreatedAtAfterOrderByIdDesc(
userId, sevenDaysAgo, pageable);
} else {
cardSlice = cardRepository.findByIsDeletedFalseAndUserIdAndCreatedAtAfterAndIdLessThanOrderByIdDesc(
userId, sevenDaysAgo, cursor, pageable);
}
if(cardSlice.isEmpty()){
throw new ExceptionHandler(NO_RECENT_CARDS);
}
}
if(cardSlice.isEmpty()){
throw new ExceptionHandler(CARD_NOT_FOUND);
}
List<CardFeedResponse> feedList = cardSlice.stream()
.map(card -> getCardFeed(card.getId(),userId))
.toList();

return CardConverter.toPagedCardFeedResponseDTto(userId,cardSlice,feedList);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public enum ErrorStatus implements BaseErrorCode {
// 같은 날짜에 같은 meal 중복 에러
DUPLICATE_MEAL_CARD(HttpStatus.CONFLICT, "CARD_002", "이미 같은 날짜와 같은 식사 유형의 카드가 존재합니다."),
CARD_UPDATE_FORBIDDEN(HttpStatus.FORBIDDEN, "CARD_003", "해당 카드를 수정할 수 있는 권한이 없습니다."),
ALREADY_BOOKMARKED(HttpStatus.CONFLICT, "CARD_004", "이미 저장된 카드입니다."),
NO_RECENT_CARDS(HttpStatus.NOT_FOUND,"CARD_004","최근 7일간 작성된 피드가 없습니다,"),
ALREADY_BOOKMARKED(HttpStatus.CONFLICT, "CARD_006", "이미 저장된 카드입니다."),
BOOKMARK_NOT_FOUND(HttpStatus.NOT_FOUND, "CARD_005", "카드를 저장한 기록이 없습니다"),


Expand Down
Loading