Skip to content

Commit 71d317b

Browse files
authored
Merge pull request #59 from Move-Log/develop
Merge develop to main
2 parents e5871fe + f9a3d9f commit 71d317b

9 files changed

Lines changed: 200 additions & 2 deletions

File tree

src/main/java/com/movelog/domain/news/application/NewsService.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.movelog.domain.news.dto.request.NewsHeadLineReq;
77
import com.movelog.domain.news.dto.response.HeadLineRes;
88
import com.movelog.domain.news.dto.response.RecentKeywordsRes;
9+
import com.movelog.domain.news.dto.response.RecentNewsRes;
910
import com.movelog.domain.record.domain.Keyword;
1011
import com.movelog.domain.record.domain.VerbType;
1112
import com.movelog.domain.record.exception.KeywordNotFoundException;
@@ -17,10 +18,13 @@
1718
import com.movelog.global.config.security.token.UserPrincipal;
1819
import com.movelog.global.util.S3Util;
1920
import lombok.RequiredArgsConstructor;
21+
import org.springframework.data.domain.PageRequest;
22+
import org.springframework.data.domain.Pageable;
2023
import org.springframework.stereotype.Service;
2124
import org.springframework.transaction.annotation.Transactional;
2225
import org.springframework.web.multipart.MultipartFile;
2326

27+
import java.time.LocalDateTime;
2428
import java.util.List;
2529
import java.util.Optional;
2630

@@ -81,6 +85,32 @@ public List<RecentKeywordsRes> getRecentKeywords(UserPrincipal userPrincipal) {
8185
.toList();
8286
}
8387

88+
public List<RecentNewsRes> getRecentNews(UserPrincipal userPrincipal, Integer page) {
89+
User user = validateUser(userPrincipal);
90+
// User user = userRepository.findById(5L).orElseThrow(UserNotFoundException::new);
91+
92+
//페이징 객체 생성
93+
Pageable pageable = PageRequest.of(page, 15);
94+
95+
// 최근 일주일간 생성한 뉴스 목록 조회
96+
LocalDateTime createdAt = LocalDateTime.now().minusDays(7);
97+
List<News> recentNews = newsRepository.findRecentNewsByUser(user, createdAt, pageable);
98+
99+
// 최신순 정렬
100+
recentNews.sort((n1, n2) -> n2.getCreatedAt().compareTo(n1.getCreatedAt()));
101+
102+
return recentNews.stream()
103+
.map(news -> RecentNewsRes.builder()
104+
.newsId(news.getNewsId())
105+
.newsImageUrl(news.getNewsUrl())
106+
.headLine(news.getHeadLine())
107+
.noun(news.getKeyword().getKeyword())
108+
.verb(VerbType.getStringVerbType(news.getKeyword().getVerbType()))
109+
.createdAt(news.getCreatedAt())
110+
.build())
111+
.toList();
112+
}
113+
84114

85115
// User 정보 검증
86116
private User validateUser(UserPrincipal userPrincipal) {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,27 @@
11
package com.movelog.domain.news.domain.repository;
22

33
import com.movelog.domain.news.domain.News;
4+
import com.movelog.domain.user.domain.User;
5+
import org.springframework.data.domain.Pageable;
46
import org.springframework.data.jpa.repository.JpaRepository;
7+
import org.springframework.data.jpa.repository.Query;
8+
import org.springframework.data.repository.query.Param;
59
import org.springframework.stereotype.Repository;
610

11+
import java.time.LocalDateTime;
12+
import java.util.List;
13+
714
@Repository
815
public interface NewsRepository extends JpaRepository<News, Long> {
16+
17+
@Query("SELECT n FROM News n " +
18+
"JOIN n.keyword k " +
19+
"WHERE k.user = :user " +
20+
"AND n.createdAt > :createdAt " +
21+
"ORDER BY n.createdAt DESC")
22+
List<News> findRecentNewsByUser(
23+
@Param("user") User user,
24+
@Param("createdAt") LocalDateTime createdAt,
25+
Pageable pageable
26+
);
927
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.movelog.domain.news.dto.response;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
import java.time.LocalDateTime;
10+
11+
@Builder
12+
@AllArgsConstructor
13+
@NoArgsConstructor
14+
@Getter
15+
public class RecentNewsRes {
16+
17+
@Schema( type = "int", example ="1", description="뉴스 ID")
18+
private Long newsId;
19+
20+
@Schema( type = "String", example ="https://movelog.s3.ap-northeast-2.amazonaws.com/record/2021-08-01/1.jpg", description="뉴스 이미지 url")
21+
private String newsImageUrl;
22+
23+
@Schema( type = "String", example ="5년 만의 첫 도전, 무엇이 그를 움직이게 했나?", description="뉴스 헤드라인 추천 내용입니다.")
24+
private String headLine;
25+
26+
@Schema( type = "String", example = "헬스", description="명사")
27+
private String noun;
28+
29+
@Schema( type = "String", example = "했어요", description="동사")
30+
private String verb;
31+
32+
@Schema( type = "LocalDateTime", example ="2021-08-01T00:00:00", description="뉴스 생성 시간")
33+
private LocalDateTime createdAt;
34+
35+
36+
}

src/main/java/com/movelog/domain/news/presentation/NewsController.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.movelog.domain.news.dto.request.NewsHeadLineReq;
66
import com.movelog.domain.news.dto.response.HeadLineRes;
77
import com.movelog.domain.news.dto.response.RecentKeywordsRes;
8+
import com.movelog.domain.news.dto.response.RecentNewsRes;
89
import com.movelog.global.config.security.token.CurrentUser;
910
import com.movelog.global.config.security.token.UserPrincipal;
1011
import com.movelog.global.payload.Message;
@@ -55,7 +56,7 @@ public ResponseEntity<?> createHeadLine(
5556
}
5657

5758

58-
@Operation(summary = "뉴스 생성 및 저장 API(기존 이미지 기록 기반)", description = "사용자의 기존 기록 이미지로 뉴스를 생성합니다.")
59+
@Operation(summary = "뉴스 생성 및 저장 API", description = "새로운 뉴스를 생성하고 저장합니다.")
5960
@ApiResponses(value = {
6061
@ApiResponse(responseCode = "200", description = "뉴스 생성 및 저장 성공",
6162
content = @Content(mediaType = "application/json", schema = @Schema(implementation = Message.class))),
@@ -90,6 +91,28 @@ public ResponseEntity<?> getRecentKeywords(
9091
return ResponseEntity.ok(ApiResponseUtil.success(response));
9192
}
9293

94+
@Operation(summary = "최근 뉴스 목록 조회 API", description = "최근 일주일간 생성한 뉴스 목록을 1페이지 당 15개씩 조회합니다.")
95+
@ApiResponses(value = {
96+
@ApiResponse(responseCode = "200", description = "최근 뉴스 목록 조회 성공",
97+
content = @Content(mediaType = "application/json",
98+
schema = @Schema(type = "array", implementation = RecentNewsRes.class))),
99+
@ApiResponse(responseCode = "400", description = "최근 뉴스 목록 조회 실패",
100+
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class)))
101+
})
102+
@GetMapping("/week")
103+
public ResponseEntity<?> getRecentNews(
104+
@Parameter(description = "Access Token을 입력해주세요.", required = true)
105+
@AuthenticationPrincipal UserPrincipal userPrincipal,
106+
@Parameter(description = "뉴스 목록의 페이지 번호를 입력해주세요. **Page는 1부터 시작됩니다!**", required = true)
107+
@RequestParam(value = "page", required = false, defaultValue = "0") Integer page
108+
) {
109+
List<RecentNewsRes> response = newsService.getRecentNews(userPrincipal, page);
110+
return ResponseEntity.ok(ApiResponseUtil.success(response));
111+
}
112+
113+
114+
115+
93116

94117

95118

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.movelog.domain.record.dto.request;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
@Builder
10+
@AllArgsConstructor
11+
@NoArgsConstructor
12+
@Getter
13+
public class SearchKeywordReq {
14+
@Schema( type = "String", example ="헬스", description="검색할 명사")
15+
private String searchKeyword;
16+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.movelog.domain.record.dto.response;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
@Builder
10+
@AllArgsConstructor
11+
@NoArgsConstructor
12+
@Getter
13+
public class SearchKeywordRes {
14+
15+
@Schema( type = "int", example = "1", description="키워드 ID")
16+
private Long keywordId;
17+
18+
@Schema( type = "String", example ="헬스", description="검색어가 포함된 명사")
19+
private String noun;
20+
21+
@Schema( type = "String", example ="했어요", description="명사에 해당하는 동사")
22+
private String verb;
23+
24+
}

src/main/java/com/movelog/domain/record/presentation/RecordController.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.movelog.domain.record.presentation;
22

33
import com.movelog.domain.record.dto.request.CreateRecordReq;
4+
import com.movelog.domain.record.dto.request.SearchKeywordReq;
45
import com.movelog.domain.record.dto.response.RecentRecordImagesRes;
6+
import com.movelog.domain.record.dto.response.SearchKeywordRes;
57
import com.movelog.domain.record.dto.response.TodayRecordStatus;
68
import com.movelog.domain.record.service.RecordService;
79
import com.movelog.global.config.security.token.UserPrincipal;
@@ -81,5 +83,23 @@ public ResponseEntity<?> retrieveRecentRecordImages(
8183
}
8284

8385

86+
@Operation(summary = "기록 내 명사 검색 API", description = "사용자가 생성한 기록 중 명사를 통해 동사-명사 쌍을 검색하는 API입니다.")
87+
@ApiResponses(value = {
88+
@ApiResponse(responseCode = "200", description = "기록 내 명사 검색 성공",
89+
content = @Content(mediaType = "application/json",
90+
schema = @Schema(type = "array", implementation = SearchKeywordRes.class))),
91+
@ApiResponse(responseCode = "400", description = "기록 내 명사 검색 실패",
92+
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class)))
93+
})
94+
@GetMapping("/search")
95+
public ResponseEntity<?> searchKeyword(
96+
@Parameter(description = "User의 토큰을 입력해주세요.", required = true) @AuthenticationPrincipal UserPrincipal userPrincipal,
97+
@Parameter(description = "검색할 명사를 입력해주세요.", required = true) @RequestBody SearchKeywordReq searchKeywordReq
98+
) {
99+
List<SearchKeywordRes> result = recordService.searchKeyword(userPrincipal, searchKeywordReq);
100+
return ResponseEntity.ok(ApiResponseUtil.success(result));
101+
}
102+
103+
84104

85105
}

src/main/java/com/movelog/domain/record/repository/KeywordRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ public interface KeywordRepository extends JpaRepository<Keyword,Long> {
1818
boolean existsByUserAndKeywordAndVerbType(User user, String noun, VerbType verbType);
1919

2020
Keyword findByUserAndKeywordAndVerbType(User user, String noun, VerbType verbType);
21+
22+
List<Keyword> findAllByUserAndKeywordContaining(User user, String keyword);
2123
}

src/main/java/com/movelog/domain/record/service/RecordService.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import com.movelog.domain.record.domain.Record;
55
import com.movelog.domain.record.domain.VerbType;
66
import com.movelog.domain.record.dto.request.CreateRecordReq;
7+
import com.movelog.domain.record.dto.request.SearchKeywordReq;
78
import com.movelog.domain.record.dto.response.RecentRecordImagesRes;
9+
import com.movelog.domain.record.dto.response.SearchKeywordRes;
810
import com.movelog.domain.record.dto.response.TodayRecordStatus;
911
import com.movelog.domain.record.repository.KeywordRepository;
1012
import com.movelog.domain.record.repository.RecordRepository;
@@ -19,6 +21,7 @@
1921
import org.springframework.transaction.annotation.Transactional;
2022
import org.springframework.web.multipart.MultipartFile;
2123

24+
import java.text.Collator;
2225
import java.time.LocalDate;
2326
import java.time.LocalDateTime;
2427
import java.time.LocalTime;
@@ -131,6 +134,33 @@ public List<RecentRecordImagesRes> retrieveRecentRecordImages(UserPrincipal user
131134

132135
}
133136

137+
138+
public List<SearchKeywordRes> searchKeyword(UserPrincipal userPrincipal, SearchKeywordReq searchKeywordReq) {
139+
User user = validUserById(userPrincipal.getId());
140+
// User user = validUserById(5L);
141+
String keyword = searchKeywordReq.getSearchKeyword();
142+
List<Keyword> keywords = keywordRepository.findAllByUserAndKeywordContaining(user, keyword);
143+
keywords.forEach(k -> log.info("Keyword in DB: {}", k.getKeyword()));
144+
145+
// Collator 생성
146+
Collator collator = Collator.getInstance(Locale.KOREA);
147+
148+
// Collator를 이용한 오름차순 정렬
149+
List<SearchKeywordRes> sortedResults = keywords.stream()
150+
.map(k -> SearchKeywordRes.builder()
151+
.keywordId(k.getKeywordId())
152+
.noun(k.getKeyword())
153+
.verb(VerbType.getStringVerbType(k.getVerbType()))
154+
.build())
155+
.sorted((o1, o2) -> collator.compare(o1.getNoun(), o2.getNoun())) // Collator로 비교
156+
.collect(Collectors.toList());
157+
158+
// 정렬된 명사 목록 출력
159+
sortedResults.forEach(r -> log.info("Sorted Noun: {}", r.getNoun()));
160+
161+
return sortedResults;
162+
}
163+
134164
private User validUserById(Long userId) {
135165
Optional<User> userOptional = userRepository.findById(userId);
136166
return userOptional.get();
@@ -153,5 +183,4 @@ private void validateCreateRecordReq(CreateRecordReq createRecordReq) {
153183
throw new IllegalArgumentException("noun is required.");
154184
}
155185
}
156-
157186
}

0 commit comments

Comments
 (0)