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 @@ -75,11 +75,7 @@ public ResponseEntity<ApiResponse<PollDto>> updatePoll(@PathVariable Long pollId
@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);
pollService.deletePoll(pollId, currentMemberId);
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 삭제되었습니다.", null));
}

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

import java.util.List;
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);
List<PollVote> findByMember_MemberId(Long memberId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public interface PollService {
PollDto updatePoll(Long pollId, PollUpdateDto pollUpdateDto, Long memberId);
void patchUpdatePoll(Long pollId, PollUpdateDto pollUpdateDto);
void closePoll(Long pollId);
void deletePoll(Long pollId);
void deletePoll(Long pollId, Long memberId);

// ===== 검증 관련 =====
void validatePollCreate(PollCreateDto dto);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
import com.ai.lawyer.domain.poll.repository.*;
import com.ai.lawyer.domain.poll.dto.PollDto;
import com.ai.lawyer.domain.member.entity.Member;
import com.ai.lawyer.domain.member.repositories.MemberRepository;
import com.ai.lawyer.domain.post.dto.PostDto;
import com.ai.lawyer.domain.post.entity.Post;
import com.ai.lawyer.domain.post.repository.PostRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -31,6 +32,7 @@
import com.ai.lawyer.domain.poll.dto.PollGenderStaticsDto;
import com.ai.lawyer.domain.poll.dto.PollStaticsResponseDto;
import com.ai.lawyer.domain.poll.dto.PollAgeStaticsDto;
import com.ai.lawyer.global.util.AuthUtil;

@Service
@Transactional
Expand All @@ -42,7 +44,6 @@ public class PollServiceImpl implements PollService {
private final PollOptionsRepository pollOptionsRepository;
private final PollVoteRepository pollVoteRepository;
private final PollStaticsRepository pollStaticsRepository;
private final MemberRepository memberRepository;
private final PostRepository postRepository;

@Override
Expand All @@ -51,8 +52,7 @@ public PollDto createPoll(PollCreateDto request, Long memberId) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "게시글 ID는 필수입니다.");
}
validatePollCommon(request.getVoteTitle(), request.getPollOptions(), request.getReservedCloseAt());
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "회원 정보를 찾을 수 없습니다."));
Member member = AuthUtil.getMemberOrThrow(memberId);
Post post = postRepository.findById(request.getPostId())
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "게시글을 찾을 수 없습니다."));
if (post.getPoll() != null) {
Expand Down Expand Up @@ -117,8 +117,7 @@ public PollVoteDto vote(Long pollId, Long pollItemsId, Long memberId) {
}
PollOptions pollOptions = pollOptionsRepository.findById(pollItemsId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "투표 항목을 찾을 수 없습니다."));
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "회원 정보를 찾을 수 없습니다."));
Member member = AuthUtil.getMemberOrThrow(memberId);
// USER 또는 ADMIN만 투표 가능
if (!(member.getRole().name().equals("USER") || member.getRole().name().equals("ADMIN"))) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "투표 권한이 없습니다.");
Expand Down Expand Up @@ -190,7 +189,7 @@ public PollStaticsResponseDto getPollStatics(Long pollId) {
PollAgeStaticsDto.AgeGroupCountDto dto = PollAgeStaticsDto.AgeGroupCountDto.builder()
.option(option)
.ageGroup(arr[1] != null ? arr[1].toString() : null)
.voteCount(arr[2] != null ? ((Number)arr[2]).longValue() : 0L)
.voteCount(arr[2] != null ? ((Number) arr[2]).longValue() : 0L)
.build();
ageGroupMap.computeIfAbsent(pollItemsId, k -> new java.util.ArrayList<>()).add(dto);
}
Expand All @@ -214,7 +213,7 @@ public PollStaticsResponseDto getPollStatics(Long pollId) {
PollGenderStaticsDto.GenderCountDto dto = PollGenderStaticsDto.GenderCountDto.builder()
.option(option)
.gender(arr[1] != null ? arr[1].toString() : null)
.voteCount(arr[2] != null ? ((Number)arr[2]).longValue() : 0L)
.voteCount(arr[2] != null ? ((Number) arr[2]).longValue() : 0L)
.build();
genderGroupMap.computeIfAbsent(pollItemsId, k -> new java.util.ArrayList<>()).add(dto);
}
Expand Down Expand Up @@ -246,10 +245,12 @@ public void closePoll(Long pollId) {
}

@Override
public void deletePoll(Long pollId) {
public void deletePoll(Long pollId, Long memberId) {
Poll poll = pollRepository.findById(pollId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "투표를 찾을 수 없습니다."));

if (poll.getPost() == null || !poll.getPost().getMember().getMemberId().equals(memberId)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "본인만 투표를 삭제할 수 있습니다.");
}
// 1. 이 Poll을 참조하는 Post가 있으면 연결 해제
Post post = postRepository.findAll().stream()
.filter(p -> p.getPoll() != null && p.getPoll().getPollId().equals(pollId))
Expand All @@ -259,7 +260,6 @@ public void deletePoll(Long pollId) {
post.setPoll(null);
postRepository.save(post);
}

// 2. Poll 삭제
pollRepository.deleteById(pollId);
}
Expand Down Expand Up @@ -458,12 +458,12 @@ private PollDto convertToDto(Poll poll, Long memberId, boolean withStatistics) {
statics = staticsRaw.stream()
.map(arr -> {
String gender = arr[1] != null ? arr[1].toString() : null;
Integer age = arr[2] != null ? ((Number)arr[2]).intValue() : null;
Integer age = arr[2] != null ? ((Number) arr[2]).intValue() : null;
String ageGroup = getAgeGroup(age);
return PollStaticsDto.builder()
.gender(gender)
.ageGroup(ageGroup)
.voteCount((Long)arr[3])
.voteCount((Long) arr[3])
.build();
}).toList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,7 @@ public class PostController {
@Operation(summary = "게시글 등록")
@PostMapping
public ResponseEntity<ApiResponse<PostDto>> createPost(@RequestBody PostRequestDto postRequestDto) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
Long memberId;
if (principal instanceof org.springframework.security.core.userdetails.User user) {
memberId = Long.valueOf(user.getUsername());
} else if (principal instanceof Long) {
memberId = (Long) principal;
} else {
throw new IllegalArgumentException("올바른 회원 ID가 아닙니다");
}
Long memberId = AuthUtil.getAuthenticatedMemberId();
PostDto created = postService.createPost(postRequestDto, memberId);
return ResponseEntity.ok(new ApiResponse<>(201, "게시글이 등록되었습니다.", created));
}
Expand Down Expand Up @@ -91,43 +82,31 @@ public ResponseEntity<ApiResponse<List<PostDetailDto>>> getPostsByMember(@PathVa
@Operation(summary = "게시글 수정")
@PutMapping("/{postId}")
public ResponseEntity<ApiResponse<PostDetailDto>> updatePost(@PathVariable Long postId, @RequestBody PostUpdateDto postUpdateDto) {
Long currentMemberId = AuthUtil.getCurrentMemberId();
String currentRole = AuthUtil.getCurrentMemberRole();
PostDetailDto postDetail = postService.getPostDetailById(postId, currentMemberId);
PostDetailDto postDetail = postService.getPostDetailById(postId, AuthUtil.getAuthenticatedMemberId());
Long postOwnerId = postDetail.getPost().getMemberId();
if (!postOwnerId.equals(currentMemberId) && !"ADMIN".equals(currentRole)) {
return ResponseEntity.status(403).body(new ApiResponse<>(403, "본인 또는 관리자만 수정 가능합니다.", null));
}
AuthUtil.validateOwnerOrAdmin(postOwnerId);
postService.updatePost(postId, postUpdateDto);
PostDetailDto updated = postService.getPostDetailById(postId, currentMemberId);
PostDetailDto updated = postService.getPostDetailById(postId, AuthUtil.getAuthenticatedMemberId());
return ResponseEntity.ok(new ApiResponse<>(200, "게시글이 수정되었습니다.", updated));
}

@Operation(summary = "게시글 부분 수정(PATCH)")
@PatchMapping("/{postId}")
public ResponseEntity<ApiResponse<PostDetailDto>> patchUpdatePost(@PathVariable Long postId, @RequestBody PostUpdateDto postUpdateDto) {
Long currentMemberId = AuthUtil.getCurrentMemberId();
String currentRole = AuthUtil.getCurrentMemberRole();
PostDetailDto postDetail = postService.getPostDetailById(postId, currentMemberId);
PostDetailDto postDetail = postService.getPostDetailById(postId, AuthUtil.getAuthenticatedMemberId());
Long postOwnerId = postDetail.getPost().getMemberId();
if (!postOwnerId.equals(currentMemberId) && !"ADMIN".equals(currentRole)) {
return ResponseEntity.status(403).body(new ApiResponse<>(403, "본인 또는 관리자만 수정 가능합니다.", null));
}
AuthUtil.validateOwnerOrAdmin(postOwnerId);
postService.patchUpdatePost(postId, postUpdateDto);
PostDetailDto updated = postService.getPostDetailById(postId, currentMemberId);
PostDetailDto updated = postService.getPostDetailById(postId, AuthUtil.getAuthenticatedMemberId());
return ResponseEntity.ok(new ApiResponse<>(200, "게시글이 수정되었습니다.", updated));
}

@Operation(summary = "게시글 삭제")
@DeleteMapping("/{postId}")
public ResponseEntity<ApiResponse<Void>> deletePost(@PathVariable Long postId) {
Long currentMemberId = AuthUtil.getCurrentMemberId();
String currentRole = AuthUtil.getCurrentMemberRole();
PostDetailDto postDetail = postService.getPostDetailById(postId, currentMemberId);
PostDetailDto postDetail = postService.getPostDetailById(postId, AuthUtil.getAuthenticatedMemberId());
Long postOwnerId = postDetail.getPost().getMemberId();
if (!postOwnerId.equals(currentMemberId) && !"ADMIN".equals(currentRole)) {
return ResponseEntity.status(403).body(new ApiResponse<>(403, "본인 또는 관리자만 삭제 가능합니다.", null));
}
AuthUtil.validateOwnerOrAdmin(postOwnerId);
postService.deletePost(postId);
return ResponseEntity.ok(new ApiResponse<>(200, "게시글이 삭제되었습니다.", null));
}
Expand All @@ -142,50 +121,23 @@ public ResponseEntity<ApiResponse<Void>> handleResponseStatusException(ResponseS
@Operation(summary = "본인 게시글 단일 조회")
@GetMapping("/my/{postId}")
public ResponseEntity<ApiResponse<PostDto>> getMyPostById(@PathVariable Long postId) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
Long memberId;
if (principal instanceof org.springframework.security.core.userdetails.User user) {
memberId = Long.valueOf(user.getUsername());
} else if (principal instanceof Long) {
memberId = (Long) principal;
} else {
throw new IllegalArgumentException("올바른 회원 ID가 아닙니다");
}
Long memberId = AuthUtil.getAuthenticatedMemberId();
PostDto postDto = postService.getMyPostById(postId, memberId);
return ResponseEntity.ok(new ApiResponse<>(200, "본인 게시글 단일 조회 성공", postDto));
}

@Operation(summary = "본인 게시글 전체 조회")
@GetMapping("/my")
public ResponseEntity<ApiResponse<List<PostDto>>> getMyPosts() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
Long memberId;
if (principal instanceof org.springframework.security.core.userdetails.User user) {
memberId = Long.valueOf(user.getUsername());
} else if (principal instanceof Long) {
memberId = (Long) principal;
} else {
throw new IllegalArgumentException("올바른 회원 ID가 아닙니다");
}
Long memberId = AuthUtil.getAuthenticatedMemberId();
List<PostDto> posts = postService.getMyPosts(memberId);
return ResponseEntity.ok(new ApiResponse<>(200, "본인 게시글 전체 조회 성공", posts));
}

@Operation(summary = "게시글+투표 동시 등록")
@PostMapping("/createPost")
public ResponseEntity<ApiResponse<PostDetailDto>> createPostWithPoll(@RequestBody PostWithPollCreateDto dto) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
Long memberId;
if (principal instanceof org.springframework.security.core.userdetails.User user) {
memberId = Long.valueOf(user.getUsername());
} else if (principal instanceof Long) {
memberId = (Long) principal;
} else {
throw new ResponseStatusException(org.springframework.http.HttpStatus.UNAUTHORIZED, "인증 정보가 올바르지 않습니다.");
}
Long memberId = AuthUtil.getAuthenticatedMemberId();
PostDetailDto result = postService.createPostWithPoll(dto, memberId);
return ResponseEntity.ok(new ApiResponse<>(200, "게시글+투표 등록 완료", result));
}
Expand Down Expand Up @@ -271,4 +223,43 @@ public ResponseEntity<ApiResponse<PostDto>> getTopClosedPoll() {
PostDto post = postService.getTopPollByStatus(PollDto.PollStatus.CLOSED, memberId);
return ResponseEntity.ok(new ApiResponse<>(200, "마감된 투표 Top 1 조회 성공", post));
}
}

@Operation(summary = "내가 참여한 진행중 투표 게시글 페이징 조회")
@GetMapping("/my/ongoingPaged")
public ResponseEntity<ApiResponse<PostPageDto>> getMyOngoingPostsPaged(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
Long memberId = AuthUtil.getAuthenticatedMemberId();
Page<PostDto> posts = postService.getMyOngoingPostsPaged(pageable, memberId);
PostPageDto response = new PostPageDto(posts);
return ResponseEntity.ok(new ApiResponse<>(200, "내가 참여한 진행중 투표 게시글 페이징 조회 성공", response));
}

@Operation(summary = "내가 참여한 마감 투표 게시글 페이징 조회")
@GetMapping("/my/closedPaged")
public ResponseEntity<ApiResponse<PostPageDto>> getMyClosedPostsPaged(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
Long memberId = AuthUtil.getAuthenticatedMemberId();
Page<PostDto> posts = postService.getMyClosedPostsPaged(pageable, memberId);
PostPageDto response = new PostPageDto(posts);
return ResponseEntity.ok(new ApiResponse<>(200, "내가 참여한 마감 투표 게시글 페이징 조회 성공", response));
}

@Operation(summary = "내가 참여한 모든 투표 게시글 페이징 조회")
@GetMapping("/my/votedPaged")
public ResponseEntity<ApiResponse<PostPageDto>> getMyVotedPostsPaged(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
Long memberId = AuthUtil.getAuthenticatedMemberId();
Page<PostDto> posts = postService.getMyVotedPostsPaged(pageable, memberId);
PostPageDto response = new PostPageDto(posts);
return ResponseEntity.ok(new ApiResponse<>(200, "내가 참여한 모든 투표 게시글 페이징 조회 성공", response));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ public interface PostService {
Page<PostDto> getPostsPaged(Pageable pageable, Long memberId);
Page<PostDto> getOngoingPostsPaged(Pageable pageable, Long memberId);
Page<PostDto> getClosedPostsPaged(Pageable pageable, Long memberId);
Page<PostDto> getMyOngoingPostsPaged(Pageable pageable, Long memberId);
Page<PostDto> getMyClosedPostsPaged(Pageable pageable, Long memberId);
Page<PostDto> getMyVotedPostsPaged(Pageable pageable, Long memberId);

// ===== 투표 Top 관련 =====
List<PostDto> getTopNPollsByStatus(PollDto.PollStatus status, int n, Long memberId);
Expand Down
Loading