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
82 changes: 63 additions & 19 deletions src/main/java/com/movelog/domain/news/application/NewsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -85,7 +84,7 @@ public List<RecentKeywordsRes> getRecentKeywords(UserPrincipal userPrincipal) {
.toList();
}

public List<RecentNewsRes> getRecentNews(UserPrincipal userPrincipal, Integer page) {
public Page<RecentNewsRes> getRecentNews(UserPrincipal userPrincipal, Integer page) {
User user = validateUser(userPrincipal);
// User user = userRepository.findById(5L).orElseThrow(UserNotFoundException::new);

Expand All @@ -94,23 +93,68 @@ public List<RecentNewsRes> getRecentNews(UserPrincipal userPrincipal, Integer pa

// 최근 일주일간 생성한 뉴스 목록 조회
LocalDateTime createdAt = LocalDateTime.now().minusDays(7);
List<News> 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<News> 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<Keyword> 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<NewsCalendarRes> 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<News> 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
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;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.List;

@Repository
public interface NewsRepository extends JpaRepository<News, Long> {
Expand All @@ -18,10 +19,20 @@ public interface NewsRepository extends JpaRepository<News, Long> {
"JOIN n.keyword k " +
"WHERE k.user = :user " +
"AND n.createdAt > :createdAt " +
"ORDER BY n.createdAt DESC")
List<News> findRecentNewsByUser(
"ORDER BY n.createdAt ASC")
Page<News> 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<News> findNewsByUserAndCreatedAtBetween(User user, LocalDateTime start, LocalDateTime end, Pageable pageable);

}
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -106,7 +105,45 @@ public ResponseEntity<?> getRecentNews(
@Parameter(description = "뉴스 목록의 페이지 번호를 입력해주세요. **Page는 0부터 시작됩니다!**", required = true)
@RequestParam(value = "page", required = false, defaultValue = "0") Integer page
) {
List<RecentNewsRes> response = newsService.getRecentNews(userPrincipal, page);
Page<RecentNewsRes> 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<NewsCalendarRes> response = newsService.getNewsByDate(userPrincipal, date, page);
return ResponseEntity.ok(ApiResponseUtil.success(response));
}

Expand Down