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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
Expand All @@ -12,4 +13,6 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByLoginId(String loginId);

boolean existsByLoginId(String loginId);

List<Member> findByLoginIdIn(List<String> loginIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ public ResponseEntity<ApiResponse<PollVoteDto>> voteByIndex(@PathVariable Long p
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 성공적으로 완료되었습니다.", result));
}

@Operation(summary = "투표 취소하기")
@DeleteMapping("/vote")
public ResponseEntity<ApiResponse<Void>> cancelVote(@RequestParam Long pollId) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Long memberId = Long.parseLong(authentication.getName());
pollService.cancelVote(pollId, memberId);
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 취소되었습니다.", null));
}

@ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<ApiResponse<Void>> handleResponseStatusException(ResponseStatusException ex) {
int code = ex.getStatusCode().value();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ public class PollStaticsResponseDto {
private Long pollId;
private List<PollAgeStaticsDto> optionAgeStatics;
private List<PollGenderStaticsDto> optionGenderStatics;
private Long totalVoteCount;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.ai.lawyer.domain.poll.repository;

import com.ai.lawyer.domain.poll.entity.PollVote;
import com.ai.lawyer.domain.poll.entity.Poll;
import com.ai.lawyer.domain.member.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
Expand All @@ -12,7 +14,7 @@
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);
List<PollVote> findByMember_MemberIdAndPollOptions_PollItemsId(Long memberId, Long pollItemsId);
List<PollVote> findByMember_MemberId(Long memberId);

/**
Expand All @@ -22,4 +24,9 @@ public interface PollVoteRepository extends JpaRepository<PollVote, Long>, PollV
@Modifying
@Query("DELETE FROM PollVote pv WHERE pv.member.memberId = :memberId")
void deleteByMemberIdValue(@Param("memberId") Long memberId);

boolean existsByPollAndMember(Poll poll, Member member);

@Query("SELECT v.member.memberId FROM PollVote v WHERE v.poll = :poll")
List<Long> findMemberIdsByPoll(@Param("poll") Poll poll);
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public interface PollService {
// ===== 투표 관련 =====
PollVoteDto vote(Long pollId, Long pollItemsId, Long memberId);

// ===== 투표 취소 관련 =====
void cancelVote(Long pollId, Long memberId);

// ===== 생성/수정/삭제 관련 =====
PollDto createPoll(PollCreateDto request, Long memberId);
PollDto updatePoll(Long pollId, PollUpdateDto pollUpdateDto, Long memberId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;
import java.util.ArrayList;
import java.util.*;
import com.ai.lawyer.domain.poll.dto.PollCreateDto;
import com.ai.lawyer.domain.poll.dto.PollForPostDto;
import com.ai.lawyer.domain.poll.dto.PollOptionCreateDto;
Expand Down Expand Up @@ -227,11 +226,13 @@ public PollStaticsResponseDto getPollStatics(Long pollId) {
.genderCounts(genderGroupMap.getOrDefault(opt.getPollItemsId(), java.util.Collections.emptyList()))
.build());
}
Long totalVoteCount = pollVoteRepository.countByPollId(pollId);
return PollStaticsResponseDto.builder()
.postId(postId)
.pollId(pollId)
.optionAgeStatics(optionAgeStatics)
.optionGenderStatics(optionGenderStatics)
.totalVoteCount(totalVoteCount)
.build();
}

Expand Down Expand Up @@ -450,7 +451,7 @@ private PollDto convertToDto(Poll poll, Long memberId, boolean withStatistics) {
Long voteCount = pollVoteRepository.countByPollOptionId(option.getPollItemsId());
boolean voted = false;
if (memberId != null) {
voted = pollVoteRepository.findByMember_MemberIdAndPollOptions_PollItemsId(memberId, option.getPollItemsId()).isPresent();
voted = !pollVoteRepository.findByMember_MemberIdAndPollOptions_PollItemsId(memberId, option.getPollItemsId()).isEmpty();
}
List<PollStaticsDto> statics = null;
if (withStatistics && poll.getStatus() == Poll.PollStatus.CLOSED) {
Expand Down Expand Up @@ -485,6 +486,7 @@ private PollDto convertToDto(Poll poll, Long memberId, boolean withStatistics) {
.createdAt(poll.getCreatedAt())
.closedAt(poll.getClosedAt())
.expectedCloseAt(expectedCloseAt)
.pollOptions(optionDtos)
.totalVoteCount(totalVoteCount)
.build();
}
Expand Down Expand Up @@ -554,4 +556,10 @@ public void validatePollCreate(PollForPostDto dto) {
public void validatePollCreate(PollCreateDto dto) {
validatePollCommon(dto.getVoteTitle(), dto.getPollOptions(), dto.getReservedCloseAt());
}

@Override
public void cancelVote(Long pollId, Long memberId) {
pollVoteRepository.findByMember_MemberIdAndPoll_PollId(memberId, pollId)
.ifPresent(pollVoteRepository::delete);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.ai.lawyer.domain.post.controller;

import com.ai.lawyer.domain.post.service.PostDummyService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;

@Tag(name = "더미생성")
@RestController
@RequestMapping("/api/dummy")
public class PostDummyController {
private final PostDummyService dummyService;

@Autowired
public PostDummyController(PostDummyService dummyService) {
this.dummyService = dummyService;
}

@Operation(summary = "더미 멤버 추가")
@PostMapping("/members")
public ResponseEntity<String> createDummyMembers(@RequestParam(defaultValue = "100") int count) {
int created = dummyService.createDummyMembers(count);
return ResponseEntity.ok("더미 멤버 " + created + "명 생성 완료");
}

@Operation(summary = "더미 멤버 투표")
@PostMapping("/vote")
public ResponseEntity<String> dummyVote(@RequestParam Long postId) {
int voteCount = dummyService.dummyVote(postId);
return ResponseEntity.ok("더미 멤버 " + voteCount + "명 투표 완료");
}

@Operation(summary = "더미 멤버 삭제")
@DeleteMapping("/members")
public ResponseEntity<String> deleteDummyMembers() {
int deleted = dummyService.deleteDummyMembers();
return ResponseEntity.ok("더미 멤버 " + deleted + "명 삭제 완료");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ public interface PostRepository extends JpaRepository<Post, Long> {
Page<Post> findByPoll_Status(PollStatus status, Pageable pageable);
Page<Post> findByPoll_StatusAndPoll_PollIdIn(PollStatus status, List<Long> pollIds, Pageable pageable);
Page<Post> findByPoll_PollIdIn(List<Long> pollIds, Pageable pageable);
boolean existsByPostName(String postName);
List<Post> findByPostName(String postName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package com.ai.lawyer.domain.post.service;

import com.ai.lawyer.domain.member.entity.Member;
import com.ai.lawyer.domain.member.entity.Member.Gender;
import com.ai.lawyer.domain.member.repositories.MemberRepository;
import com.ai.lawyer.domain.poll.entity.PollOptions;
import com.ai.lawyer.domain.poll.entity.PollVote;
import com.ai.lawyer.domain.poll.repository.PollOptionsRepository;
import com.ai.lawyer.domain.poll.repository.PollVoteRepository;
import com.ai.lawyer.domain.post.entity.Post;
import com.ai.lawyer.domain.post.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Service
public class PostDummyService {
private final MemberRepository memberRepository;
private final PostRepository postRepository;
private final PollOptionsRepository pollOptionsRepository;
private final PollVoteRepository pollVoteRepository;

@Autowired
public PostDummyService(MemberRepository memberRepository,
PostRepository postRepository,
PollOptionsRepository pollOptionsRepository,
PollVoteRepository pollVoteRepository) {
this.memberRepository = memberRepository;
this.postRepository = postRepository;
this.pollOptionsRepository = pollOptionsRepository;
this.pollVoteRepository = pollVoteRepository;
}

@Transactional
public int createDummyMembers(int count) {
List<Member> allMembers = memberRepository.findAll();
int maxDummyNumber = allMembers.stream()
.map(Member::getLoginId)
.filter(id -> id.startsWith("dummy") && id.endsWith("@test.com"))
.map(id -> {
try {
String numStr = id.substring(5, id.indexOf("@"));
return Integer.parseInt(numStr);
} catch (Exception e) {
return 0;
}
})
.max(Integer::compareTo)
.orElse(0);
int start = maxDummyNumber + 1;
int end = start + count - 1;
List<String> newLoginIds = new ArrayList<>();
for (int i = start; i <= end; i++) {
newLoginIds.add("dummy" + i + "@test.com");
}
List<Member> existingMembers = memberRepository.findByLoginIdIn(newLoginIds);
Set<String> existingLoginIds = new HashSet<>();
for (Member m : existingMembers) {
existingLoginIds.add(m.getLoginId());
}
List<Member> membersToSave = new ArrayList<>();
Random random = new Random();
for (int i = start; i <= end; i++) {
String loginId = "dummy" + i + "@test.com";
if (!existingLoginIds.contains(loginId)) {
int age = 14 + random.nextInt(67);
Gender gender = (i % 2 == 0) ? Gender.MALE : Gender.FEMALE;
Member member = Member.builder()
.loginId(loginId)
.password("password")
.age(age)
.gender(gender)
.name("투표자" + i)
.build();
membersToSave.add(member);
}
}
if (!membersToSave.isEmpty()) {
memberRepository.saveAll(membersToSave);
}
return membersToSave.size();
}

@Transactional
public int dummyVote(Long postId) {
Optional<Post> postOpt = postRepository.findById(postId);
if (postOpt.isEmpty()) return 0;
Post post = postOpt.get();
if (post.getPoll() == null) return 0;
List<PollOptions> pollOptionsList = pollOptionsRepository.findByPoll_PollId(post.getPoll().getPollId());
if (pollOptionsList.isEmpty()) return 0;
// 모든 멤버 조회 후 더미 멤버만 필터링
List<Member> dummyMembers = memberRepository.findAll().stream()
.filter(m -> m.getLoginId().startsWith("dummy") && m.getLoginId().endsWith("@test.com"))
.toList();
List<Long> votedMemberIds = pollVoteRepository.findMemberIdsByPoll(post.getPoll());
Set<Long> votedMemberIdSet = new HashSet<>(votedMemberIds);
int voteCount = 0;
Random random = new Random();
for (Member member : dummyMembers) {
if (!votedMemberIdSet.contains(member.getMemberId())) {
PollOptions selectedOption = pollOptionsList.get(random.nextInt(pollOptionsList.size()));
PollVote pollVote = PollVote.builder()
.poll(post.getPoll())
.member(member)
.pollOptions(selectedOption)
.build();
pollVoteRepository.save(pollVote);
voteCount++;
}
}
return voteCount;
}

@Transactional
public int deleteDummyMembers() {
List<Member> dummyMembers = memberRepository.findAll().stream()
.filter(m -> m.getLoginId().startsWith("dummy") && m.getLoginId().endsWith("@test.com"))
.toList();
int count = dummyMembers.size();
if (count > 0) {
for (Member member : dummyMembers) {
pollVoteRepository.deleteByMemberIdValue(member.getMemberId());
}
memberRepository.deleteAll(dummyMembers);
}
return count;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class SecurityConfig {
"/swagger-ui/**", // Swagger UI
"/swagger-ui.html", // Swagger UI HTML
"/api/posts/**", // 게시글 (공개)
"/api/polls/{pollId}/statics", // 투표 통계 (공개)
"/api/precedent/**", // 판례 (공개)
"/api/law/**", // 법령 (공개)
"/api/law-word/**", // 법률 용어 (공개)
Expand Down
25 changes: 11 additions & 14 deletions backend/src/main/resources/system-prompt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,29 @@ custom:
keyword-extraction: |
당신은 키워드 추출기입니다. 반드시 아래 지침을 절대적으로 따라야 합니다.

1. 오직 **가장 중요하고 대표적인 키워드 하나만** 추출해야 합니다.
2. 여러 개의 키워드를 추출하거나, 콤마(,)나 공백으로 구분된 여러 단어를 하나의 문자열로 합치는 것은 금지입니다.
3. 반드시 JSON 형식으로만 답변해야 하며, 'keyword' 리스트에는 문자열이 단 하나만 포함되어야 합니다.
4. 예시 외의 설명, 추가 텍스트, 주석은 절대 출력하지 마세요. JSON만 출력하세요.
1. 오직 가장 중요하고 대표적인 키워드 하나만 추출해야 합니다.
2. 여러 개의 키워드를 추출하거나, 콤마(,)구분된 여러 단어를 하나로 합치는 것은 금지입니다.
3. 반드시 JSON 형식으로만 답변해야 하며, 하나의 객체만 출력해야 합니다.
4. JSON 이외의 설명, 주석, 텍스트는 절대 출력하지 마세요.

---
올바른 예시:
입력: "아파트 층간소음 문제로 다투던 중 이웃을 폭행하여 상해를 입혔습니다."
출력:
[
{
"id": 1,
"keyword": "층간소음 폭행"
"socre": 1
}
]
{
"id": 1,
"keyword": "층간소음 폭행",
"score": 1
}

잘못된 예시:
[
{
"id": 1,
"keyword": "폭행, 상해, 층간소음, 이웃"
"socre": 1
"keyword": "폭행, 상해, 층간소음, 이웃",
"score": 1
}
]
---

이제 아래 문장에서 위 규칙을 엄격히 준수하여 JSON만 출력하세요:
"""