Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ private String fetchAndSaveDefinition(String word) {

private String fetchAndSaveDefinitionV2(String word) {
try {
String url = buildKoreanDictApiUrl(word);
String url = buildApiUrlV2(word);

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

String combinedDefinitions = extractTop3DefinitionsFromJson(json);
String combinedDefinitions = extractTop3DefinitionsFromJson(json, word);
saveDefinition(word, combinedDefinitions);

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

private String buildKoreanDictApiUrl(String word) {
private String buildApiUrlV2(String word) {
return UriComponentsBuilder.fromHttpUrl(KOREAN_DICT_API_BASE_URL)
.queryParam("key", API_KEY)
.queryParam("req_type", "json")
Expand All @@ -114,9 +114,9 @@ private String buildKoreanDictApiUrl(String word) {
.queryParam("sort", "dict")
.queryParam("start", "1")
.queryParam("num", "10")
.queryParam("advanced", "y")
.queryParam("type4", "all")
.queryParam("cat", "23")
// .queryParam("advanced", "y")
// .queryParam("type4", "all")
// .queryParam("cat", "23")
.build()
.toUriString();
}
Expand All @@ -136,39 +136,72 @@ private String extractDefinitionFromJson(String json) throws JsonProcessingExcep
}
}

private String extractTop3DefinitionsFromJson(String json) throws JsonProcessingException {
private String extractTop3DefinitionsFromJson(String json, String requestedWord) throws JsonProcessingException {
JsonNode rootNode = objectMapper.readTree(json);
JsonNode channelNode = rootNode.path("channel");

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

JsonNode itemsNode = channelNode.path("item");
if (!itemsNode.isArray() || itemsNode.size() == 0) {
throw new RuntimeException("검색 결과가 없습니다.");
return "찾을수 없는 단어입니다";
}

// 2. 클라이언트가 요청한 단어와 정확히 일치하는 item만 필터링
List<JsonNode> matchingItems = new ArrayList<>();
for (JsonNode item : itemsNode) {
String itemWord = item.path("word").asText();
if (requestedWord.equals(itemWord)) {
matchingItems.add(item);
}
}

if (matchingItems.isEmpty()) {
return "찾을수 없는 단어입니다";
}

// 3. 법률 카테고리 우선순위 적용
List<String> definitions = extractDefinitionsWithPriority(matchingItems);

if (definitions.isEmpty()) {
return "찾을수 없는 단어입니다";
}

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

// 최대 3개의 definition 추출
for (int i = 0; i < Math.min(itemsNode.size(), 3); i++) {
JsonNode item = itemsNode.get(i);
private List<String> extractDefinitionsWithPriority(List<JsonNode> matchingItems) {
List<String> legalDefinitions = new ArrayList<>(); // 법률 카테고리
List<String> allDefinitions = new ArrayList<>(); // 모든 카테고리

for (JsonNode item : matchingItems) {
JsonNode senseNode = item.path("sense");

if (senseNode.isArray() && senseNode.size() > 0) {
JsonNode firstSense = senseNode.get(0);
String definition = firstSense.path("definition").asText();
if (senseNode.isArray()) {
for (JsonNode sense : senseNode) {
String definition = sense.path("definition").asText();
String cat = sense.path("cat").asText("");

if (definition != null && !definition.trim().isEmpty()) {
String cleanDefinition = definition.trim();
allDefinitions.add(cleanDefinition);

if (definition != null && !definition.trim().isEmpty()) {
definitions.add(definition.trim());
// cat이 "법률"인 경우 별도로 수집
if ("법률".equals(cat)) {
legalDefinitions.add(cleanDefinition);
}
}
}
}
}

if (definitions.isEmpty()) {
throw new RuntimeException("검색 결과에서 정의를 찾을 수 없습니다.");
}

// 줄바꿈으로 연결하여 하나의 문자열로 만들기
return String.join("\n", definitions);
// 법률 카테고리가 있으면 법률만, 없으면 모든 카테고리 반환
return legalDefinitions.isEmpty() ? allDefinitions : legalDefinitions;
}

private void saveDefinition(String word, String definition) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package com.ai.lawyer.domain.poll.controller;

import com.ai.lawyer.domain.poll.dto.PollCreateDto;
import com.ai.lawyer.domain.poll.dto.PollDto;
import com.ai.lawyer.domain.poll.dto.PollStaticsResponseDto;
import com.ai.lawyer.domain.poll.dto.PollVoteDto;
import com.ai.lawyer.domain.poll.dto.*;
import com.ai.lawyer.domain.poll.entity.PollVote;
import com.ai.lawyer.domain.poll.entity.PollOptions;
import com.ai.lawyer.domain.poll.service.PollService;
import com.ai.lawyer.domain.post.dto.PostDetailDto;
import com.ai.lawyer.domain.post.service.PostService;
import com.ai.lawyer.global.response.ApiResponse;
import com.ai.lawyer.global.util.AuthUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
Expand All @@ -26,6 +25,7 @@
@RestController
@RequestMapping("/api/polls")
@RequiredArgsConstructor
@Slf4j
public class PollController {

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

@Operation(summary = "투표 옵션 목록 조회")
@GetMapping("/{pollId}/options")
public ResponseEntity<ApiResponse<List<PollOptions>>> getPollOptions(@PathVariable Long pollId) {
List<PollOptions> options = pollService.getPollOptions(pollId);
return ResponseEntity.ok(new ApiResponse<>(200, "투표 옵션 목록 조회 성공", options));
}

@Operation(summary = "투표하기")
@PostMapping("/{pollId}/vote")
public ResponseEntity<ApiResponse<PollVoteDto>> vote(@PathVariable Long pollId, @RequestParam Long pollItemsId) {
Expand All @@ -68,24 +63,39 @@ public ResponseEntity<ApiResponse<Void>> closePoll(@PathVariable Long pollId) {
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 종료되었습니다.", null));
}

@Operation(summary = "투표 수정")
@PutMapping("/{pollId}")
public ResponseEntity<ApiResponse<PollDto>> updatePoll(@PathVariable Long pollId, @RequestBody PollUpdateDto pollUpdateDto) {
Long currentMemberId = AuthUtil.getCurrentMemberId();
PollDto updated = pollService.updatePoll(pollId, pollUpdateDto, currentMemberId);
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 수정되었습니다.", updated));
}

@Operation(summary = "투표 삭제")
@DeleteMapping("/{pollId}")
public ResponseEntity<ApiResponse<Void>> deletePoll(@PathVariable Long pollId) {
Long currentMemberId = AuthUtil.getCurrentMemberId();
PollDto poll = pollService.getPoll(pollId, currentMemberId);
if (!poll.getPostId().equals(currentMemberId)) {
return ResponseEntity.status(403).body(new ApiResponse<>(403, "본인만 투표를 삭제할 수 있습니다.", null));
}
pollService.deletePoll(pollId);
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 삭제되었습니다.", null));
}

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

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

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

@Operation(summary = "투표 수정")
@PutMapping("/{pollId}")
public ResponseEntity<ApiResponse<PollDto>> updatePoll(@PathVariable Long pollId, @RequestBody com.ai.lawyer.domain.poll.dto.PollUpdateDto pollUpdateDto) {
PollDto updated = pollService.updatePoll(pollId, pollUpdateDto);
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 수정되었습니다.", updated));
}

@Operation(summary = "진행중인 투표 전체 목록 조회")
@GetMapping("/ongoing")
public ResponseEntity<ApiResponse<List<PollDto>>> getOngoingPolls() {
List<PollDto> polls = pollService.getPollsByStatus(PollDto.PollStatus.ONGOING);
Long memberId = AuthUtil.getCurrentMemberId();
List<PollDto> polls = pollService.getPollsByStatus(PollDto.PollStatus.ONGOING, memberId);
return ResponseEntity.ok(new ApiResponse<>(200, "진행중인 투표 전체 목록 조회 성공", polls));
}

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

@Operation(summary = "종료된 투표 Top N 조회")
@GetMapping("/top/closed-list") //검색조건 : pi/polls/top/closed-list?size=3
public ResponseEntity<ApiResponse<List<PollDto>>> getTopClosedPolls(@RequestParam(defaultValue = "3") int size) {
List<PollDto> polls = pollService.getTopNPollsByStatus(PollDto.PollStatus.CLOSED, size);
String message = String.format("종료된 투표 Top %d 조회 성공", size);
return ResponseEntity.ok(new ApiResponse<>(200, message, polls));
}

@Operation(summary = "진행중인 투표 Top N 조회")
@GetMapping("/top/ongoing-list") //검색조건 : api/polls/top/ongoing-list?size=3
public ResponseEntity<ApiResponse<List<PollDto>>> getTopOngoingPolls(@RequestParam(defaultValue = "3") int size) {
List<PollDto> polls = pollService.getTopNPollsByStatus(PollDto.PollStatus.ONGOING, size);
String message = String.format("진행중인 투표 Top %d 조회 성공", size);
return ResponseEntity.ok(new ApiResponse<>(200, message, polls));
}
// @Operation(summary = "종료된 투표 Top N 조회")
// @GetMapping("/top/closed-list") //검색조건 : pi/polls/top/closed-list?size=3
// public ResponseEntity<ApiResponse<List<PollDto>>> getTopClosedPolls(@RequestParam(defaultValue = "3") int size) {
// List<PollDto> polls = pollService.getTopNPollsByStatus(PollDto.PollStatus.CLOSED, size);
// String message = String.format("종료된 투표 Top %d 조회 성공", size);
// return ResponseEntity.ok(new ApiResponse<>(200, message, polls));
// }
//
// @Operation(summary = "진행중인 투표 Top N 조회")
// @GetMapping("/top/ongoing-list") //검색조건 : api/polls/top/ongoing-list?size=3
// public ResponseEntity<ApiResponse<List<PollDto>>> getTopOngoingPolls(@RequestParam(defaultValue = "3") int size) {
// List<PollDto> polls = pollService.getTopNPollsByStatus(PollDto.PollStatus.ONGOING, size);
// String message = String.format("진행중인 투표 Top %d 조회 성공", size);
// return ResponseEntity.ok(new ApiResponse<>(200, message, polls));
// }

@Operation(summary = "index(순번)로 투표하기")
@PostMapping("/{pollId}/voting")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ public class PollOptionDto {
private Long voteCount;
private java.util.List<PollStaticsDto> statics;
private int pollOptionIndex;
private boolean voted; // 해당 옵션에 투표했는지 여부
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ public class PollVoteDto {
private Long pollItemsId;
private Long memberId;
private Long voteCount;
private String message;
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import com.ai.lawyer.domain.poll.entity.PollVote;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface PollVoteRepository extends JpaRepository<PollVote, Long>, PollVoteRepositoryCustom {
Optional<PollVote> findByMember_MemberIdAndPoll_PollId(Long memberId, Long pollId);
void deleteByMember_MemberIdAndPoll_PollId(Long memberId, Long pollId);
Optional<PollVote> findByMember_MemberIdAndPollOptions_PollItemsId(Long memberId, Long pollItemsId);
}

Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,3 @@ public interface PollVoteRepositoryCustom {
List<Object[]> getOptionAgeStatics(Long pollId);
List<Object[]> getOptionGenderStatics(Long pollId);
}

Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@

public interface PollService {
// ===== 조회 관련 =====
PollDto getPoll(Long pollId);
PollDto getPollWithStatistics(Long pollId);
PollDto getPoll(Long pollId, Long memberId);
PollDto getPollWithStatistics(Long pollId, Long memberId);
List<PollOptions> getPollOptions(Long pollId);
List<PollDto> getPollsByStatus(PollDto.PollStatus status);
PollDto getTopPollByStatus(PollDto.PollStatus status);
List<PollDto> getTopNPollsByStatus(PollDto.PollStatus status, int n);
List<PollDto> getPollsByStatus(PollDto.PollStatus status, Long memberId);
PollDto getTopPollByStatus(PollDto.PollStatus status, Long memberId);
List<PollDto> getTopNPollsByStatus(PollDto.PollStatus status, int n, Long memberId);

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

// ===== 생성/수정/삭제 관련 =====
PollDto createPoll(PollCreateDto request, Long memberId);
PollDto updatePoll(Long pollId, PollUpdateDto pollUpdateDto);
PollDto updatePoll(Long pollId, PollUpdateDto pollUpdateDto, Long memberId);
void patchUpdatePoll(Long pollId, PollUpdateDto pollUpdateDto);
void closePoll(Long pollId);
void deletePoll(Long pollId);
Expand Down
Loading