Skip to content

Commit 62c134a

Browse files
authored
Merge pull request #75 from Move-Log/feat/search-word
[FEAT] 나의 단어 통계 조회를 위한 단어 검색 기능 구현
2 parents 9bd3ef8 + b578c1f commit 62c134a

File tree

12 files changed

+193
-14
lines changed

12 files changed

+193
-14
lines changed

.github/workflows/deploy.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ jobs:
6363
6464
mkdir -p ./src/main/resources/webclient
6565
echo "${{ secrets.APPLICATION_WEBCLIENT_YML }}" | base64 --decode > ./src/main/resources/webclient/application-webclient.yml
66+
67+
mkdir -p ./src/main/resources/redis
68+
echo "${{ secrets.APPLICATION_REDIS_YML }}" | base64 --decode > ./src/main/resources/redis/application-redis.yml
6669
6770
# Docker 이미지 빌드
6871
- name: Build Docker image

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ dependencies {
7777
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
7878

7979
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
80+
81+
// Redis
82+
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
83+
8084
}
8185

8286
tasks.named('test') {

src/main/java/com/movelog/MoveLogApplication.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
@PropertySource(value = { "classpath:s3/application-s3.yml" }, factory = YamlPropertySourceFactory.class)
1717
@PropertySource(value = { "classpath:chatgpt/application-chatgpt.yml" }, factory = YamlPropertySourceFactory.class)
1818
@PropertySource(value = { "classpath:webclient/application-webclient.yml" }, factory = YamlPropertySourceFactory.class)
19+
@PropertySource(value = { "classpath:redis/application-redis.yml" }, factory = YamlPropertySourceFactory.class)
1920
public class MoveLogApplication {
2021
public static void main(String[] args) {
2122
SpringApplication.run(MoveLogApplication.class, args);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import com.movelog.domain.record.domain.Keyword;
99
import com.movelog.domain.record.domain.VerbType;
1010
import com.movelog.domain.record.exception.KeywordNotFoundException;
11-
import com.movelog.domain.record.repository.KeywordRepository;
11+
import com.movelog.domain.record.domain.repository.KeywordRepository;
1212
import com.movelog.domain.user.application.UserService;
1313
import com.movelog.domain.user.domain.User;
1414
import com.movelog.domain.user.domain.repository.UserRepository;
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.movelog.domain.record.application;
2+
3+
import com.movelog.domain.record.domain.Keyword;
4+
import com.movelog.domain.record.dto.response.SearchKeywordInStatsRes;
5+
import com.movelog.domain.record.exception.KeywordNotFoundException;
6+
import com.movelog.domain.record.domain.repository.KeywordRepository;
7+
import com.movelog.domain.user.application.UserService;
8+
import com.movelog.domain.user.domain.User;
9+
import com.movelog.domain.user.exception.UserNotFoundException;
10+
import com.movelog.global.config.security.token.UserPrincipal;
11+
import lombok.RequiredArgsConstructor;
12+
import lombok.extern.slf4j.Slf4j;
13+
import org.springframework.stereotype.Service;
14+
import org.springframework.transaction.annotation.Transactional;
15+
16+
import java.util.List;
17+
import java.util.Optional;
18+
19+
@Service
20+
@RequiredArgsConstructor
21+
@Transactional(readOnly = true)
22+
@Slf4j
23+
public class KeywordService {
24+
25+
private final UserService userService;
26+
private final KeywordRepository keywordRepository;
27+
28+
public List<SearchKeywordInStatsRes> searchKeywordInStats(UserPrincipal userPrincipal, String keyword) {
29+
User user = validUserById(userPrincipal);
30+
// 검색어를 포함한 키워드 리스트 조회
31+
List<Keyword> keywords = keywordRepository.findAllByUserAndKeywordContaining(user, keyword);
32+
33+
// 기록이 많은 순서대로 정렬
34+
keywords = sortKeywordByRecordCount(keywords);
35+
36+
return keywords.stream()
37+
.map(keyword1 -> SearchKeywordInStatsRes.builder()
38+
.keywordId(keyword1.getKeywordId())
39+
.noun(keyword1.getKeyword())
40+
.build())
41+
.toList();
42+
43+
}
44+
45+
46+
// 키워드 내 기록 개수를 반환
47+
private int keywordRecordCount(Long keywordId){
48+
Keyword keyword = validKeywordById(keywordId);
49+
return keyword.getRecords().size();
50+
}
51+
52+
// 키워드 내 기록이 많은 순서대로 정렬
53+
private List<Keyword> sortKeywordByRecordCount(List<Keyword> keywords) {
54+
return keywords.stream()
55+
.sorted((k1, k2) -> keywordRecordCount(k2.getKeywordId()) - keywordRecordCount(k1.getKeywordId()))
56+
.toList();
57+
}
58+
59+
private User validUserById(UserPrincipal userPrincipal) {
60+
Optional<User> userOptional = userService.findById(userPrincipal.getId());
61+
if (userOptional.isEmpty()) { throw new UserNotFoundException(); }
62+
return userOptional.get();
63+
}
64+
65+
private Keyword validKeywordById(Long keywordId) {
66+
Optional<Keyword> keywordOptional = keywordRepository.findById(keywordId);
67+
if(keywordOptional.isEmpty()) { throw new KeywordNotFoundException(); }
68+
return keywordOptional.get();
69+
}
70+
71+
}

src/main/java/com/movelog/domain/record/service/RecordService.java renamed to src/main/java/com/movelog/domain/record/application/RecordService.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
1-
package com.movelog.domain.record.service;
1+
package com.movelog.domain.record.application;
22

3-
import com.movelog.domain.news.domain.News;
4-
import com.movelog.domain.news.dto.response.NewsCalendarRes;
53
import com.movelog.domain.record.domain.Keyword;
64
import com.movelog.domain.record.domain.Record;
75
import com.movelog.domain.record.domain.VerbType;
86
import com.movelog.domain.record.dto.request.CreateRecordReq;
9-
import com.movelog.domain.record.dto.request.SearchKeywordReq;
107
import com.movelog.domain.record.dto.response.*;
11-
import com.movelog.domain.record.repository.KeywordRepository;
12-
import com.movelog.domain.record.repository.RecordRepository;
8+
import com.movelog.domain.record.domain.repository.KeywordRepository;
9+
import com.movelog.domain.record.domain.repository.RecordRepository;
1310
import com.movelog.domain.user.application.UserService;
1411
import com.movelog.domain.user.domain.User;
1512
import com.movelog.domain.user.domain.repository.UserRepository;
1613
import com.movelog.domain.user.exception.UserNotFoundException;
1714
import com.movelog.global.config.security.token.UserPrincipal;
1815
import com.movelog.global.util.S3Util;
19-
import jakarta.validation.ConstraintViolation;
2016
import lombok.RequiredArgsConstructor;
2117
import lombok.extern.slf4j.Slf4j;
2218
import org.springframework.data.domain.Page;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.movelog.domain.record.repository;
1+
package com.movelog.domain.record.domain.repository;
22

33
import com.movelog.domain.record.domain.Keyword;
44
import com.movelog.domain.record.domain.Record;

src/main/java/com/movelog/domain/record/repository/RecordRepository.java renamed to src/main/java/com/movelog/domain/record/domain/repository/RecordRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.movelog.domain.record.repository;
1+
package com.movelog.domain.record.domain.repository;
22

33
import com.movelog.domain.record.domain.Keyword;
44
import com.movelog.domain.record.domain.Record;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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 SearchKeywordInStatsRes {
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+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.movelog.domain.record.presentation;
2+
3+
import com.movelog.domain.record.application.KeywordService;
4+
import com.movelog.domain.record.dto.response.SearchKeywordInStatsRes;
5+
import com.movelog.global.config.security.token.UserPrincipal;
6+
import com.movelog.global.payload.ErrorResponse;
7+
import io.swagger.v3.oas.annotations.Operation;
8+
import io.swagger.v3.oas.annotations.Parameter;
9+
import io.swagger.v3.oas.annotations.media.Content;
10+
import io.swagger.v3.oas.annotations.media.Schema;
11+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
12+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
13+
import io.swagger.v3.oas.annotations.tags.Tag;
14+
import lombok.RequiredArgsConstructor;
15+
import lombok.extern.slf4j.Slf4j;
16+
import org.springframework.http.ResponseEntity;
17+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
18+
import org.springframework.web.bind.annotation.GetMapping;
19+
import org.springframework.web.bind.annotation.RequestMapping;
20+
import org.springframework.web.bind.annotation.RequestParam;
21+
import org.springframework.web.bind.annotation.RestController;
22+
23+
import java.util.List;
24+
25+
@Slf4j
26+
@RestController
27+
@RequiredArgsConstructor
28+
@RequestMapping("/api/v1/keyword")
29+
@Tag(name = "Keyword", description = "단어 관련 API입니다.")
30+
public class KeywordController {
31+
32+
private final KeywordService keywordService;
33+
34+
@Operation(summary = "단어 통계 조회 API", description = "서비스 내에서 생성된 단어를 검색하는 API입니다.")
35+
@ApiResponses(value = {
36+
@ApiResponse(responseCode = "200", description = "단어 통계 조회 성공",
37+
content = @Content(mediaType = "application/json",
38+
schema = @Schema(type = "array", implementation = SearchKeywordInStatsRes.class))),
39+
@ApiResponse(responseCode = "400", description = "단어 통계 조회 실패",
40+
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class)))
41+
})
42+
@GetMapping("/search")
43+
public ResponseEntity<?> searchKeywordInStats(
44+
@Parameter(description = "Access Token을 입력해주세요.", required = true) @AuthenticationPrincipal UserPrincipal userPrincipal,
45+
@Parameter(description = "검색할 명사를 입력해주세요.", required = true) @RequestParam String keyword
46+
) {
47+
List<SearchKeywordInStatsRes> response = keywordService.searchKeywordInStats(userPrincipal, keyword);
48+
return ResponseEntity.ok(response);
49+
}
50+
}

0 commit comments

Comments
 (0)