diff --git a/src/main/java/com/movelog/domain/news/application/NewsService.java b/src/main/java/com/movelog/domain/news/application/NewsService.java index bd4a2d2..9f93d4d 100644 --- a/src/main/java/com/movelog/domain/news/application/NewsService.java +++ b/src/main/java/com/movelog/domain/news/application/NewsService.java @@ -4,9 +4,7 @@ import com.movelog.domain.news.domain.repository.NewsRepository; import com.movelog.domain.news.dto.request.CreateNewsReq; import com.movelog.domain.news.dto.request.NewsHeadLineReq; -import com.movelog.domain.news.dto.response.HeadLineRes; -import com.movelog.domain.news.dto.response.RecentKeywordsRes; -import com.movelog.domain.news.dto.response.RecentNewsRes; +import com.movelog.domain.news.dto.response.*; import com.movelog.domain.record.domain.Keyword; import com.movelog.domain.record.domain.VerbType; import com.movelog.domain.record.exception.KeywordNotFoundException; @@ -18,6 +16,7 @@ import com.movelog.global.config.security.token.UserPrincipal; import com.movelog.global.util.S3Util; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -85,7 +84,7 @@ public List getRecentKeywords(UserPrincipal userPrincipal) { .toList(); } - public List getRecentNews(UserPrincipal userPrincipal, Integer page) { + public Page getRecentNews(UserPrincipal userPrincipal, Integer page) { User user = validateUser(userPrincipal); // User user = userRepository.findById(5L).orElseThrow(UserNotFoundException::new); @@ -94,23 +93,68 @@ public List getRecentNews(UserPrincipal userPrincipal, Integer pa // 최근 일주일간 생성한 뉴스 목록 조회 LocalDateTime createdAt = LocalDateTime.now().minusDays(7); - List recentNews = newsRepository.findRecentNewsByUser(user, createdAt, pageable); - - // 최신순 정렬 - recentNews.sort((n1, n2) -> n2.getCreatedAt().compareTo(n1.getCreatedAt())); - - return recentNews.stream() - .map(news -> RecentNewsRes.builder() - .newsId(news.getNewsId()) - .newsImageUrl(news.getNewsUrl()) - .headLine(news.getHeadLine()) - .noun(news.getKeyword().getKeyword()) - .verb(VerbType.getStringVerbType(news.getKeyword().getVerbType())) - .createdAt(news.getCreatedAt()) - .build()) - .toList(); + Page recentNews = newsRepository.findRecentNewsByUser(user, createdAt, pageable); + + return recentNews.map(news -> RecentNewsRes.builder() + .newsId(news.getNewsId()) + .newsImageUrl(news.getNewsUrl()) + .headLine(news.getHeadLine()) + .noun(news.getKeyword().getKeyword()) + .verb(VerbType.getStringVerbType(news.getKeyword().getVerbType())) + .createdAt(news.getCreatedAt()) + .build()); + } + + public TodayNewsStatusRes getTodayNewsStatus(UserPrincipal userPrincipal) { + User user = validateUser(userPrincipal); + // User user = userRepository.findById(5L).orElseThrow(UserNotFoundException::new); + + // 사용자가 생성한 모든 뉴스 개수 조회 + List keywords = user.getKeywords(); + long totalNewsCount = keywords.stream() + .mapToLong(newsRepository::countByKeyword) + .sum(); + + long newsStatus = totalNewsCount % 5; + LocalDateTime today = LocalDateTime.now(); + + // 오늘 생성한 뉴스가 있으면 true, 없으면 false + boolean isTodayNews = !newsRepository.findRecentNewsByUser(user, today, PageRequest.of(0, 1)).isEmpty(); + + int result; + if(newsStatus == 0 && isTodayNews) { + result = 5; + } + else if(newsStatus == 0) { + result = 0; + } + else { + result = (int) newsStatus; + } + + return TodayNewsStatusRes.builder() + .newsStatus(result) + .build(); } + public Page getNewsByDate(UserPrincipal userPrincipal, String date, int page) { + User user = validateUser(userPrincipal); + // User user = userRepository.findById(5L).orElseThrow(UserNotFoundException::new); + + LocalDateTime start = LocalDateTime.parse(date + "T00:00:00"); + LocalDateTime end = LocalDateTime.parse(date + "T23:59:59"); + + Pageable pageable = PageRequest.of(0, 15); // 원하는 페이지와 크기를 지정 + Page newsList = newsRepository.findNewsByUserAndCreatedAtBetween(user, start, end, pageable); + + return newsList.map(news -> NewsCalendarRes.builder() + .newsId(news.getNewsId()) + .newsImageUrl(news.getNewsUrl()) + .noun(news.getKeyword().getKeyword()) + .verb(VerbType.getStringVerbType(news.getKeyword().getVerbType())) + .createdAt(news.getCreatedAt()) + .build()); + } // User 정보 검증 private User validateUser(UserPrincipal userPrincipal) { diff --git a/src/main/java/com/movelog/domain/news/domain/repository/NewsRepository.java b/src/main/java/com/movelog/domain/news/domain/repository/NewsRepository.java index 4a38445..74a7293 100644 --- a/src/main/java/com/movelog/domain/news/domain/repository/NewsRepository.java +++ b/src/main/java/com/movelog/domain/news/domain/repository/NewsRepository.java @@ -1,7 +1,9 @@ package com.movelog.domain.news.domain.repository; import com.movelog.domain.news.domain.News; +import com.movelog.domain.record.domain.Keyword; import com.movelog.domain.user.domain.User; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -9,7 +11,6 @@ import org.springframework.stereotype.Repository; import java.time.LocalDateTime; -import java.util.List; @Repository public interface NewsRepository extends JpaRepository { @@ -18,10 +19,20 @@ public interface NewsRepository extends JpaRepository { "JOIN n.keyword k " + "WHERE k.user = :user " + "AND n.createdAt > :createdAt " + - "ORDER BY n.createdAt DESC") - List findRecentNewsByUser( + "ORDER BY n.createdAt ASC") + Page findRecentNewsByUser( @Param("user") User user, @Param("createdAt") LocalDateTime createdAt, Pageable pageable ); + + long countByKeyword(Keyword keyword); + + @Query("SELECT n FROM News n " + + "JOIN n.keyword k " + + "WHERE k.user = :user " + + "AND n.createdAt BETWEEN :start AND :end " + + "ORDER BY n.createdAt ASC") + Page findNewsByUserAndCreatedAtBetween(User user, LocalDateTime start, LocalDateTime end, Pageable pageable); + } diff --git a/src/main/java/com/movelog/domain/news/dto/response/NewsCalendarRes.java b/src/main/java/com/movelog/domain/news/dto/response/NewsCalendarRes.java new file mode 100644 index 0000000..9a129fa --- /dev/null +++ b/src/main/java/com/movelog/domain/news/dto/response/NewsCalendarRes.java @@ -0,0 +1,31 @@ +package com.movelog.domain.news.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class NewsCalendarRes { + @Schema( type = "int", example ="1", description="뉴스 ID") + private Long newsId; + + @Schema( type = "String", example ="https://movelog.s3.ap-northeast-2.amazonaws.com/record/2021-08-01/1.jpg", description="뉴스 이미지 url") + private String newsImageUrl; + + @Schema( type = "String", example ="헬스", description="명사") + private String noun; + + @Schema( type = "String", example ="했어요", description="동사") + private String verb; + + @Schema( type = "LocalDateTime", example ="2025-08-01T00:00:00", description="뉴스 생성 시간") + private LocalDateTime createdAt; + +} diff --git a/src/main/java/com/movelog/domain/news/dto/response/TodayNewsStatusRes.java b/src/main/java/com/movelog/domain/news/dto/response/TodayNewsStatusRes.java new file mode 100644 index 0000000..5c50364 --- /dev/null +++ b/src/main/java/com/movelog/domain/news/dto/response/TodayNewsStatusRes.java @@ -0,0 +1,18 @@ +package com.movelog.domain.news.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class TodayNewsStatusRes { + + @Schema( type = "int", example ="0 ~ 5", description = "오늘 기준 뉴스 현황입니다. 0~5 사이의 값입니다.") + private int newsStatus; + +} diff --git a/src/main/java/com/movelog/domain/news/presentation/NewsController.java b/src/main/java/com/movelog/domain/news/presentation/NewsController.java index 85290f8..1fc1b62 100644 --- a/src/main/java/com/movelog/domain/news/presentation/NewsController.java +++ b/src/main/java/com/movelog/domain/news/presentation/NewsController.java @@ -3,9 +3,7 @@ import com.movelog.domain.news.application.NewsService; import com.movelog.domain.news.dto.request.CreateNewsReq; import com.movelog.domain.news.dto.request.NewsHeadLineReq; -import com.movelog.domain.news.dto.response.HeadLineRes; -import com.movelog.domain.news.dto.response.RecentKeywordsRes; -import com.movelog.domain.news.dto.response.RecentNewsRes; +import com.movelog.domain.news.dto.response.*; import com.movelog.global.config.security.token.CurrentUser; import com.movelog.global.config.security.token.UserPrincipal; import com.movelog.global.payload.Message; @@ -20,6 +18,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.ErrorResponse; @@ -106,7 +105,45 @@ public ResponseEntity getRecentNews( @Parameter(description = "뉴스 목록의 페이지 번호를 입력해주세요. **Page는 0부터 시작됩니다!**", required = true) @RequestParam(value = "page", required = false, defaultValue = "0") Integer page ) { - List response = newsService.getRecentNews(userPrincipal, page); + Page response = newsService.getRecentNews(userPrincipal, page); + return ResponseEntity.ok(ApiResponseUtil.success(response)); + } + + + @Operation(summary = "뉴스 기록 현황 조회 API", description = "오늘 기준 사용자의 뉴스 기록 현황을 조회합니다. ") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "뉴스 기록 현황 조회 성공", + content = @Content(mediaType = "application/json", + schema = @Schema(type = "array", implementation = TodayNewsStatusRes.class))), + @ApiResponse(responseCode = "400", description = "뉴스 기록 현황 조회 실패", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) + }) + @GetMapping("/today") + public ResponseEntity getTodayNewsStatus( + @Parameter(description = "Access Token을 입력해주세요.", required = true) @AuthenticationPrincipal UserPrincipal userPrincipal + ) { + TodayNewsStatusRes response = newsService.getTodayNewsStatus(userPrincipal); + return ResponseEntity.ok(ApiResponseUtil.success(response)); + } + + + + @Operation(summary = "날짜별 뉴스 목록 조회 API", description = "특정 날짜의 뉴스 목록을 1페이지 당 15개씩 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "날짜별 뉴스 목록 조회 성공", + content = @Content(mediaType = "application/json", + schema = @Schema(type = "array", implementation = NewsCalendarRes.class))), + @ApiResponse(responseCode = "400", description = "날짜별 뉴스 목록 조회 실패", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) + }) + @GetMapping("/calendar/{date}") + public ResponseEntity getNewsByDate( + @Parameter(description = "Access Token을 입력해주세요.", required = true) @AuthenticationPrincipal UserPrincipal userPrincipal, + @Parameter(description = "조회할 날짜를 입력해주세요. (yyyy-MM-dd 형식)", required = true) @PathVariable String date, + @Parameter(description = "뉴스 목록의 페이지 번호를 입력해주세요. **Page는 0부터 시작됩니다!**", required = true) + @RequestParam(value = "page", required = false, defaultValue = "0") Integer page + ) { + Page response = newsService.getNewsByDate(userPrincipal, date, page); return ResponseEntity.ok(ApiResponseUtil.success(response)); }