Skip to content

Commit c3b854a

Browse files
authored
Merge pull request #261 from prgrms-web-devcourse-final-project/develop
배포
2 parents 7616a4f + 4c3c5fb commit c3b854a

File tree

18 files changed

+436
-263
lines changed

18 files changed

+436
-263
lines changed

backend/src/main/java/com/ai/lawyer/domain/lawWord/service/LawWordService.java

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ private String fetchAndSaveDefinition(String word) {
7575

7676
private String fetchAndSaveDefinitionV2(String word) {
7777
try {
78-
String url = buildKoreanDictApiUrl(word);
78+
String url = buildApiUrlV2(word);
7979

8080
// WebClient 호출 (동기 방식)
8181
String json = webClient.get()
@@ -84,7 +84,7 @@ private String fetchAndSaveDefinitionV2(String word) {
8484
.bodyToMono(String.class)
8585
.block();
8686

87-
String combinedDefinitions = extractTop3DefinitionsFromJson(json);
87+
String combinedDefinitions = extractTop3DefinitionsFromJson(json, word);
8888
saveDefinition(word, combinedDefinitions);
8989

9090
return combinedDefinitions;
@@ -105,7 +105,7 @@ private String buildApiUrl(String word) {
105105
return API_BASE_URL + "?OC=" + API_OC + "&target=lstrm&type=JSON&query=" + word;
106106
}
107107

108-
private String buildKoreanDictApiUrl(String word) {
108+
private String buildApiUrlV2(String word) {
109109
return UriComponentsBuilder.fromHttpUrl(KOREAN_DICT_API_BASE_URL)
110110
.queryParam("key", API_KEY)
111111
.queryParam("req_type", "json")
@@ -114,9 +114,9 @@ private String buildKoreanDictApiUrl(String word) {
114114
.queryParam("sort", "dict")
115115
.queryParam("start", "1")
116116
.queryParam("num", "10")
117-
.queryParam("advanced", "y")
118-
.queryParam("type4", "all")
119-
.queryParam("cat", "23")
117+
// .queryParam("advanced", "y")
118+
// .queryParam("type4", "all")
119+
// .queryParam("cat", "23")
120120
.build()
121121
.toUriString();
122122
}
@@ -136,39 +136,72 @@ private String extractDefinitionFromJson(String json) throws JsonProcessingExcep
136136
}
137137
}
138138

139-
private String extractTop3DefinitionsFromJson(String json) throws JsonProcessingException {
139+
private String extractTop3DefinitionsFromJson(String json, String requestedWord) throws JsonProcessingException {
140140
JsonNode rootNode = objectMapper.readTree(json);
141+
JsonNode channelNode = rootNode.path("channel");
141142

142-
// channel > item 배열에서 아이템들 추출
143-
JsonNode itemsNode = rootNode.path("channel").path("item");
143+
// 1. total이 0이면 '찾을수 없는 단어입니다' 리턴
144+
int total = channelNode.path("total").asInt(0);
145+
if (total == 0) {
146+
return "찾을수 없는 단어입니다";
147+
}
144148

149+
JsonNode itemsNode = channelNode.path("item");
145150
if (!itemsNode.isArray() || itemsNode.size() == 0) {
146-
throw new RuntimeException("검색 결과가 없습니다.");
151+
return "찾을수 없는 단어입니다";
152+
}
153+
154+
// 2. 클라이언트가 요청한 단어와 정확히 일치하는 item만 필터링
155+
List<JsonNode> matchingItems = new ArrayList<>();
156+
for (JsonNode item : itemsNode) {
157+
String itemWord = item.path("word").asText();
158+
if (requestedWord.equals(itemWord)) {
159+
matchingItems.add(item);
160+
}
161+
}
162+
163+
if (matchingItems.isEmpty()) {
164+
return "찾을수 없는 단어입니다";
165+
}
166+
167+
// 3. 법률 카테고리 우선순위 적용
168+
List<String> definitions = extractDefinitionsWithPriority(matchingItems);
169+
170+
if (definitions.isEmpty()) {
171+
return "찾을수 없는 단어입니다";
147172
}
148173

149-
List<String> definitions = new ArrayList<>();
174+
// 같은 word면 개수 제한 없이 모든 definition 반환
175+
return String.join("\n", definitions);
176+
}
150177

151-
// 최대 3개의 definition 추출
152-
for (int i = 0; i < Math.min(itemsNode.size(), 3); i++) {
153-
JsonNode item = itemsNode.get(i);
178+
private List<String> extractDefinitionsWithPriority(List<JsonNode> matchingItems) {
179+
List<String> legalDefinitions = new ArrayList<>(); // 법률 카테고리
180+
List<String> allDefinitions = new ArrayList<>(); // 모든 카테고리
181+
182+
for (JsonNode item : matchingItems) {
154183
JsonNode senseNode = item.path("sense");
155184

156-
if (senseNode.isArray() && senseNode.size() > 0) {
157-
JsonNode firstSense = senseNode.get(0);
158-
String definition = firstSense.path("definition").asText();
185+
if (senseNode.isArray()) {
186+
for (JsonNode sense : senseNode) {
187+
String definition = sense.path("definition").asText();
188+
String cat = sense.path("cat").asText("");
189+
190+
if (definition != null && !definition.trim().isEmpty()) {
191+
String cleanDefinition = definition.trim();
192+
allDefinitions.add(cleanDefinition);
159193

160-
if (definition != null && !definition.trim().isEmpty()) {
161-
definitions.add(definition.trim());
194+
// cat이 "법률"인 경우 별도로 수집
195+
if ("법률".equals(cat)) {
196+
legalDefinitions.add(cleanDefinition);
197+
}
198+
}
162199
}
163200
}
164201
}
165202

166-
if (definitions.isEmpty()) {
167-
throw new RuntimeException("검색 결과에서 정의를 찾을 수 없습니다.");
168-
}
169-
170-
// 줄바꿈으로 연결하여 하나의 문자열로 만들기
171-
return String.join("\n", definitions);
203+
// 법률 카테고리가 있으면 법률만, 없으면 모든 카테고리 반환
204+
return legalDefinitions.isEmpty() ? allDefinitions : legalDefinitions;
172205
}
173206

174207
private void saveDefinition(String word, String definition) {

backend/src/main/java/com/ai/lawyer/domain/poll/controller/PollController.java

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
package com.ai.lawyer.domain.poll.controller;
22

3-
import com.ai.lawyer.domain.poll.dto.PollCreateDto;
4-
import com.ai.lawyer.domain.poll.dto.PollDto;
5-
import com.ai.lawyer.domain.poll.dto.PollStaticsResponseDto;
6-
import com.ai.lawyer.domain.poll.dto.PollVoteDto;
3+
import com.ai.lawyer.domain.poll.dto.*;
74
import com.ai.lawyer.domain.poll.entity.PollVote;
85
import com.ai.lawyer.domain.poll.entity.PollOptions;
96
import com.ai.lawyer.domain.poll.service.PollService;
107
import com.ai.lawyer.domain.post.dto.PostDetailDto;
118
import com.ai.lawyer.domain.post.service.PostService;
129
import com.ai.lawyer.global.response.ApiResponse;
10+
import com.ai.lawyer.global.util.AuthUtil;
1311
import io.swagger.v3.oas.annotations.Operation;
1412
import io.swagger.v3.oas.annotations.tags.Tag;
1513
import lombok.RequiredArgsConstructor;
14+
import lombok.extern.slf4j.Slf4j;
1615
import org.springframework.http.ResponseEntity;
1716
import org.springframework.security.core.Authentication;
1817
import org.springframework.security.core.context.SecurityContextHolder;
@@ -26,6 +25,7 @@
2625
@RestController
2726
@RequestMapping("/api/polls")
2827
@RequiredArgsConstructor
28+
@Slf4j
2929
public class PollController {
3030

3131
private final PollService pollService;
@@ -34,17 +34,12 @@ public class PollController {
3434
@Operation(summary = "투표 단일 조회")
3535
@GetMapping("/{pollId}")
3636
public ResponseEntity<ApiResponse<PollDto>> getPoll(@PathVariable Long pollId) {
37-
PollDto poll = pollService.getPoll(pollId);
37+
Long memberId = AuthUtil.getCurrentMemberId();
38+
log.info("PollController getPoll: memberId={}", memberId);
39+
PollDto poll = pollService.getPoll(pollId, memberId);
3840
return ResponseEntity.ok(new ApiResponse<>(200, "투표 단일 조회 성공", poll));
3941
}
4042

41-
@Operation(summary = "투표 옵션 목록 조회")
42-
@GetMapping("/{pollId}/options")
43-
public ResponseEntity<ApiResponse<List<PollOptions>>> getPollOptions(@PathVariable Long pollId) {
44-
List<PollOptions> options = pollService.getPollOptions(pollId);
45-
return ResponseEntity.ok(new ApiResponse<>(200, "투표 옵션 목록 조회 성공", options));
46-
}
47-
4843
@Operation(summary = "투표하기")
4944
@PostMapping("/{pollId}/vote")
5045
public ResponseEntity<ApiResponse<PollVoteDto>> vote(@PathVariable Long pollId, @RequestParam Long pollItemsId) {
@@ -68,24 +63,39 @@ public ResponseEntity<ApiResponse<Void>> closePoll(@PathVariable Long pollId) {
6863
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 종료되었습니다.", null));
6964
}
7065

66+
@Operation(summary = "투표 수정")
67+
@PutMapping("/{pollId}")
68+
public ResponseEntity<ApiResponse<PollDto>> updatePoll(@PathVariable Long pollId, @RequestBody PollUpdateDto pollUpdateDto) {
69+
Long currentMemberId = AuthUtil.getCurrentMemberId();
70+
PollDto updated = pollService.updatePoll(pollId, pollUpdateDto, currentMemberId);
71+
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 수정되었습니다.", updated));
72+
}
73+
7174
@Operation(summary = "투표 삭제")
7275
@DeleteMapping("/{pollId}")
7376
public ResponseEntity<ApiResponse<Void>> deletePoll(@PathVariable Long pollId) {
77+
Long currentMemberId = AuthUtil.getCurrentMemberId();
78+
PollDto poll = pollService.getPoll(pollId, currentMemberId);
79+
if (!poll.getPostId().equals(currentMemberId)) {
80+
return ResponseEntity.status(403).body(new ApiResponse<>(403, "본인만 투표를 삭제할 수 있습니다.", null));
81+
}
7482
pollService.deletePoll(pollId);
7583
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 삭제되었습니다.", null));
7684
}
7785

7886
@Operation(summary = "진행중인 투표 Top 1 조회")
7987
@GetMapping("/top/ongoing")
8088
public ResponseEntity<ApiResponse<PollDto>> getTopOngoingPoll() {
81-
PollDto poll = pollService.getTopPollByStatus(PollDto.PollStatus.ONGOING);
89+
Long memberId = AuthUtil.getCurrentMemberId();
90+
PollDto poll = pollService.getTopPollByStatus(PollDto.PollStatus.ONGOING, memberId);
8291
return ResponseEntity.ok(new ApiResponse<>(200, "진행중인 투표 Top 1 조회 성공", poll));
8392
}
8493

8594
@Operation(summary = "종료된 투표 Top 1 조회")
8695
@GetMapping("/top/closed")
8796
public ResponseEntity<ApiResponse<PollDto>> getTopClosedPoll() {
88-
PollDto poll = pollService.getTopPollByStatus(PollDto.PollStatus.CLOSED);
97+
Long memberId = AuthUtil.getCurrentMemberId();
98+
PollDto poll = pollService.getTopPollByStatus(PollDto.PollStatus.CLOSED, memberId);
8999
return ResponseEntity.ok(new ApiResponse<>(200, "종료된 투표 Top 1 조회 성공", poll));
90100
}
91101

@@ -112,42 +122,37 @@ public ResponseEntity<ApiResponse<PollDto>> createPoll(@RequestBody PollCreateDt
112122
return ResponseEntity.ok(new ApiResponse<>(201, "투표가 생성되었습니다.", created));
113123
}
114124

115-
@Operation(summary = "투표 수정")
116-
@PutMapping("/{pollId}")
117-
public ResponseEntity<ApiResponse<PollDto>> updatePoll(@PathVariable Long pollId, @RequestBody com.ai.lawyer.domain.poll.dto.PollUpdateDto pollUpdateDto) {
118-
PollDto updated = pollService.updatePoll(pollId, pollUpdateDto);
119-
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 수정되었습니다.", updated));
120-
}
121-
122125
@Operation(summary = "진행중인 투표 전체 목록 조회")
123126
@GetMapping("/ongoing")
124127
public ResponseEntity<ApiResponse<List<PollDto>>> getOngoingPolls() {
125-
List<PollDto> polls = pollService.getPollsByStatus(PollDto.PollStatus.ONGOING);
128+
Long memberId = AuthUtil.getCurrentMemberId();
129+
List<PollDto> polls = pollService.getPollsByStatus(PollDto.PollStatus.ONGOING, memberId);
126130
return ResponseEntity.ok(new ApiResponse<>(200, "진행중인 투표 전체 목록 조회 성공", polls));
127131
}
128132

129133
@Operation(summary = "종료된 투표 전체 목록 조회")
130134
@GetMapping("/closed")
131135
public ResponseEntity<ApiResponse<List<PollDto>>> getClosedPolls() {
132-
List<PollDto> polls = pollService.getPollsByStatus(PollDto.PollStatus.CLOSED);
136+
Long memberId = AuthUtil.getCurrentMemberId();
137+
List<PollDto> polls = pollService.getPollsByStatus(PollDto.PollStatus.CLOSED, memberId);
133138
return ResponseEntity.ok(new ApiResponse<>(200, "종료된 투표 전체 목록 조회 성공", polls));
134139
}
135140

136-
@Operation(summary = "종료된 투표 Top N 조회")
137-
@GetMapping("/top/closed-list") //검색조건 : pi/polls/top/closed-list?size=3
138-
public ResponseEntity<ApiResponse<List<PollDto>>> getTopClosedPolls(@RequestParam(defaultValue = "3") int size) {
139-
List<PollDto> polls = pollService.getTopNPollsByStatus(PollDto.PollStatus.CLOSED, size);
140-
String message = String.format("종료된 투표 Top %d 조회 성공", size);
141-
return ResponseEntity.ok(new ApiResponse<>(200, message, polls));
142-
}
143-
144-
@Operation(summary = "진행중인 투표 Top N 조회")
145-
@GetMapping("/top/ongoing-list") //검색조건 : api/polls/top/ongoing-list?size=3
146-
public ResponseEntity<ApiResponse<List<PollDto>>> getTopOngoingPolls(@RequestParam(defaultValue = "3") int size) {
147-
List<PollDto> polls = pollService.getTopNPollsByStatus(PollDto.PollStatus.ONGOING, size);
148-
String message = String.format("진행중인 투표 Top %d 조회 성공", size);
149-
return ResponseEntity.ok(new ApiResponse<>(200, message, polls));
150-
}
141+
// @Operation(summary = "종료된 투표 Top N 조회")
142+
// @GetMapping("/top/closed-list") //검색조건 : pi/polls/top/closed-list?size=3
143+
// public ResponseEntity<ApiResponse<List<PollDto>>> getTopClosedPolls(@RequestParam(defaultValue = "3") int size) {
144+
// List<PollDto> polls = pollService.getTopNPollsByStatus(PollDto.PollStatus.CLOSED, size);
145+
// String message = String.format("종료된 투표 Top %d 조회 성공", size);
146+
// return ResponseEntity.ok(new ApiResponse<>(200, message, polls));
147+
// }
148+
//
149+
// @Operation(summary = "진행중인 투표 Top N 조회")
150+
// @GetMapping("/top/ongoing-list") //검색조건 : api/polls/top/ongoing-list?size=3
151+
// public ResponseEntity<ApiResponse<List<PollDto>>> getTopOngoingPolls(@RequestParam(defaultValue = "3") int size) {
152+
// List<PollDto> polls = pollService.getTopNPollsByStatus(PollDto.PollStatus.ONGOING, size);
153+
// String message = String.format("진행중인 투표 Top %d 조회 성공", size);
154+
// return ResponseEntity.ok(new ApiResponse<>(200, message, polls));
155+
// }
151156

152157
@Operation(summary = "index(순번)로 투표하기")
153158
@PostMapping("/{pollId}/voting")

backend/src/main/java/com/ai/lawyer/domain/poll/dto/PollOptionDto.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ public class PollOptionDto {
1212
private Long voteCount;
1313
private java.util.List<PollStaticsDto> statics;
1414
private int pollOptionIndex;
15+
private boolean voted; // 해당 옵션에 투표했는지 여부
1516
}

backend/src/main/java/com/ai/lawyer/domain/poll/dto/PollVoteDto.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ public class PollVoteDto {
1515
private Long pollItemsId;
1616
private Long memberId;
1717
private Long voteCount;
18+
private String message;
1819
}
19-

backend/src/main/java/com/ai/lawyer/domain/poll/repository/PollVoteRepository.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import com.ai.lawyer.domain.poll.entity.PollVote;
44
import org.springframework.data.jpa.repository.JpaRepository;
55

6+
import java.util.Optional;
7+
68
public interface PollVoteRepository extends JpaRepository<PollVote, Long>, PollVoteRepositoryCustom {
9+
Optional<PollVote> findByMember_MemberIdAndPoll_PollId(Long memberId, Long pollId);
10+
void deleteByMember_MemberIdAndPoll_PollId(Long memberId, Long pollId);
11+
Optional<PollVote> findByMember_MemberIdAndPollOptions_PollItemsId(Long memberId, Long pollItemsId);
712
}
8-

backend/src/main/java/com/ai/lawyer/domain/poll/repository/PollVoteRepositoryCustom.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,3 @@ public interface PollVoteRepositoryCustom {
1414
List<Object[]> getOptionAgeStatics(Long pollId);
1515
List<Object[]> getOptionGenderStatics(Long pollId);
1616
}
17-

backend/src/main/java/com/ai/lawyer/domain/poll/service/PollService.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414

1515
public interface PollService {
1616
// ===== 조회 관련 =====
17-
PollDto getPoll(Long pollId);
18-
PollDto getPollWithStatistics(Long pollId);
17+
PollDto getPoll(Long pollId, Long memberId);
18+
PollDto getPollWithStatistics(Long pollId, Long memberId);
1919
List<PollOptions> getPollOptions(Long pollId);
20-
List<PollDto> getPollsByStatus(PollDto.PollStatus status);
21-
PollDto getTopPollByStatus(PollDto.PollStatus status);
22-
List<PollDto> getTopNPollsByStatus(PollDto.PollStatus status, int n);
20+
List<PollDto> getPollsByStatus(PollDto.PollStatus status, Long memberId);
21+
PollDto getTopPollByStatus(PollDto.PollStatus status, Long memberId);
22+
List<PollDto> getTopNPollsByStatus(PollDto.PollStatus status, int n, Long memberId);
2323

2424
// ===== 통계 관련 =====
2525
PollStaticsResponseDto getPollStatics(Long pollId);
@@ -31,7 +31,7 @@ public interface PollService {
3131

3232
// ===== 생성/수정/삭제 관련 =====
3333
PollDto createPoll(PollCreateDto request, Long memberId);
34-
PollDto updatePoll(Long pollId, PollUpdateDto pollUpdateDto);
34+
PollDto updatePoll(Long pollId, PollUpdateDto pollUpdateDto, Long memberId);
3535
void patchUpdatePoll(Long pollId, PollUpdateDto pollUpdateDto);
3636
void closePoll(Long pollId);
3737
void deletePoll(Long pollId);

0 commit comments

Comments
 (0)