Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0314355
chore[security]: /api/chat/** 추가
yongho9064 Sep 29, 2025
c7dcca1
chore[security]: /api/chat/** permitAll 제거
yongho9064 Sep 29, 2025
5b7ae3c
Merge pull request #114 from yongho9064/security/ai
yongho9064 Sep 29, 2025
7c2e200
feat[poll]:poll 기능 추가
GarakChoi Sep 24, 2025
30bbfd9
feat[poll]:poll 기능 추가
GarakChoi Sep 24, 2025
b3a5f2d
feat[poll]:투표 기능 추가
GarakChoi Sep 24, 2025
dac04c1
feat[poll]:투표 ㄱê¸포스트 기능 연동
GarakChoi Sep 25, 2025
51e7808
work
GarakChoi Sep 25, 2025
08369ef
work
GarakChoi Sep 25, 2025
d94ae10
feat[poll] 투표기ë Š¥ 추가
GarakChoi Sep 26, 2025
f31d442
feat[poll]:상위투표조회추가
GarakChoi Sep 26, 2025
01c487b
feat[poll]:테스트기능추가
GarakChoi Sep 26, 2025
4b582e8
feat[poll]:ìì투표예약마감추가
GarakChoi Sep 26, 2025
a686e5f
work
GarakChoi Sep 27, 2025
62ca28f
feat[post]:게시글 투표 기능추가
GarakChoi Sep 27, 2025
31a6ba7
feat[post]:투표하기 swagger 추가
GarakChoi Sep 27, 2025
63dfcba
work
GarakChoi Sep 29, 2025
a384764
work
GarakChoi Sep 29, 2025
6651e2c
work
GarakChoi Sep 29, 2025
522d3c8
work
GarakChoi Sep 29, 2025
2e47b83
work
GarakChoi Sep 29, 2025
24e21d0
work
GarakChoi Sep 29, 2025
6c4e22f
work
GarakChoi Sep 29, 2025
de57a25
Revert "chore[security]: /api/chat/** 추가"
yongho9064 Sep 29, 2025
d9d6dfa
work
GarakChoi Sep 29, 2025
772b9f8
Merge pull request #115 from prgrms-web-devcourse-final-project/rever…
yongho9064 Sep 29, 2025
0be29b5
work
GarakChoi Sep 29, 2025
302200c
fix[qdrent]: 버그 수정
yongho9064 Sep 29, 2025
c5b6678
Merge pull request #117 from yongho9064/bug/fix
yongho9064 Sep 29, 2025
d1a4e9e
Merge pull request #111 from prgrms-web-devcourse-final-project/feat/…
GarakChoi Sep 29, 2025
7aacd6c
fix[post]:DB에러수정
GarakChoi Sep 29, 2025
aa9db4a
fix[post]:DB에러수정
GarakChoi Sep 29, 2025
cadf580
Merge pull request #118 from prgrms-web-devcourse-final-project/feat/…
DooHyoJeong Sep 29, 2025
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
@@ -1,4 +1,172 @@
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.PollVoteDto;
import com.ai.lawyer.domain.poll.entity.PollVote;
import com.ai.lawyer.domain.poll.entity.PollOptions;
import com.ai.lawyer.domain.poll.entity.PollStatics;
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 io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

import org.springframework.web.server.ResponseStatusException;

import java.util.List;

@Tag(name = "Poll API", description = "투표 관련 API")
@RestController
@RequestMapping("/api/polls")
@RequiredArgsConstructor
public class PollController {

private final PollService pollService;
private final PostService postService;

@Operation(summary = "투표 단일 조회")
@GetMapping("/{pollId}")
public ResponseEntity<ApiResponse<PollDto>> getPoll(@PathVariable Long pollId) {
PollDto poll = pollService.getPoll(pollId);
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) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Long memberId = Long.parseLong(authentication.getName());
PollVoteDto result = pollService.vote(pollId, pollItemsId, memberId);
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 성공적으로 완료되었습니다.", result));
}

@Operation(summary = "투표 통계 조회")
@GetMapping("/{pollId}/statics")
public ResponseEntity<ApiResponse<List<PollStatics>>> getPollStatics(@PathVariable Long pollId) {
List<PollStatics> statics = pollService.getPollStatics(pollId);
return ResponseEntity.ok(new ApiResponse<>(200, "투표 통계 조회 성공", statics));
}

@Operation(summary = "투표 종료")
@PutMapping("/{pollId}/close")
public ResponseEntity<ApiResponse<Void>> closePoll(@PathVariable Long pollId) {
pollService.closePoll(pollId);
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 종료되었습니다.", null));
}

@Operation(summary = "투표 삭제")
@DeleteMapping("/{pollId}")
public ResponseEntity<ApiResponse<Void>> deletePoll(@PathVariable Long pollId) {
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);
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);
return ResponseEntity.ok(new ApiResponse<>(200, "종료된 투표 Top 1 조회 성공", poll));
}

// @Operation(summary = "진행중인 투표 상세 조회")
// @GetMapping("/top/ongoing-detail")
// public PostDetailDto getTopOngoingPollDetail() {
// PollDto pollDto = pollService.getTopPollByStatus(PollDto.PollStatus.ONGOING);
// return postService.getPostDetailById(pollDto.getPostId());
// }
//
// @Operation(summary = "종료된 투표 상세 조회")
// @GetMapping("/top/closed-detail")
// public PostDetailDto getTopClosedPollDetail() {
// PollDto pollDto = pollService.getTopPollByStatus(PollDto.PollStatus.CLOSED);
// return postService.getPostDetailById(pollDto.getPostId());
// }

@Operation(summary = "투표 생성")
@PostMapping("")
public ResponseEntity<ApiResponse<PollDto>> createPoll(@RequestBody PollCreateDto pollCreateDto) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Long memberId = Long.parseLong(authentication.getName());
PollDto created = pollService.createPoll(pollCreateDto, memberId);
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);
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);
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 = "index(순번)로 투표하기 - Swagger 편의용")
@PostMapping("/{pollId}/vote-by-index")
public ResponseEntity<ApiResponse<PollVoteDto>> voteByIndex(@PathVariable Long pollId, @RequestParam int index) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Long memberId = Long.parseLong(authentication.getName());
List<PollOptions> options = pollService.getPollOptions(pollId);
if (index < 1 || index > options.size()) {
throw new ResponseStatusException(org.springframework.http.HttpStatus.BAD_REQUEST, "index가 옵션 범위를 벗어났습니다.");
}
Long pollItemsId = options.get(index - 1).getPollItemsId();
PollVoteDto result = pollService.vote(pollId, pollItemsId, memberId);
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 성공적으로 완료되었습니다.", result));
}

@ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<ApiResponse<Void>> handleResponseStatusException(ResponseStatusException ex) {
int code = ex.getStatusCode().value();
String message = ex.getReason();
return ResponseEntity.status(code).body(new ApiResponse<>(code, message, null));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.ai.lawyer.domain.poll.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PollCreateDto {
@Schema(description = "게시글 ID", example = "123")
private Long postId;
@Schema(description = "투표 제목", example = "당신의 선택은?")
private String voteTitle;
@Schema(description = "투표 항목(2개 필수)", example = "[{\"content\": \"항목1 내용\"}, {\"content\": \"항목2 내용\"}]")
private List<PollOptionCreateDto> pollOptions;
private LocalDateTime reservedCloseAt;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.*;
import java.time.LocalDateTime;
import java.util.List;

@Data
@NoArgsConstructor
Expand All @@ -14,9 +15,11 @@ public class PollDto {
private PollStatus status;
private LocalDateTime createdAt;
private LocalDateTime closedAt;
private LocalDateTime expectedCloseAt;
private List<PollOptionDto> pollOptions;
private Long totalVoteCount;

public enum PollStatus {
ONGOING, CLOSED
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.ai.lawyer.domain.poll.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PollOptionCreateDto {
@Schema(description = "투표 항목 내용", example = "항목1 내용")
private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.ai.lawyer.domain.poll.dto;

import lombok.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PollOptionDto {
private Long pollItemsId;
private String content;
private Long voteCount;
private java.util.List<PollStaticsDto> statics;
private int pollOptionIndex;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ai.lawyer.domain.poll.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PollOptionUpdateDto {
@Schema(description = "투표 항목 id", example = "1")
private Long pollItemsId;

@Schema(description = "투표 항목 내용", example = "항목1 내용")
private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.ai.lawyer.domain.poll.dto;

import lombok.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PollStaticsDto {
private String gender;
private String ageGroup;
private Long voteCount;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.ai.lawyer.domain.poll.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PollUpdateDto {
@Schema(description = "투표 제목", example = "당신의 선택은?")
private String voteTitle;
@Schema(description = "투표 항목(2개 필수)", example = "[{\"content\": \"항목1 내용\"}, {\"content\": \"항목2 내용\"}]")
private List<PollOptionUpdateDto> pollOptions;
private LocalDateTime reservedCloseAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ai.lawyer.domain.poll.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PollVoteDto {
private Long pollVoteId;
private Long pollId;
private Long pollItemsId;
private Long memberId;
private Long voteCount;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.ai.lawyer.domain.poll.dto;

import com.ai.lawyer.domain.post.dto.PostDto;
import lombok.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PollWithPostDto {
private PollDto poll;
private PostDto post;
}

Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ public class Poll {
@Column(name = "poll_id")
private Long pollId;

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id", nullable = true)
@OneToOne(mappedBy = "poll", fetch = FetchType.LAZY)
private Post post;

@Column(length = 100, nullable = false, name = "vote_title")
Expand All @@ -36,6 +35,12 @@ public class Poll {
@Column(name = "closed_at")
private LocalDateTime closedAt;

@Column(name = "reserved_close_at")
private LocalDateTime reservedCloseAt;

@OneToMany(mappedBy = "poll", cascade = CascadeType.REMOVE, orphanRemoval = true)
private java.util.List<PollOptions> pollOptions;

// 투표 상태 Enum 타입
public enum PollStatus {
ONGOING, CLOSED
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,31 @@
package com.ai.lawyer.domain.poll.entity;

import jakarta.persistence.*;
import lombok.*;

@Entity
@Table(name = "poll_options")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PollOptions {
}

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "poll_items_id")
private Long pollItemsId;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "poll_id", nullable = false, foreignKey = @ForeignKey(name = "FK_POLLOPTIONS_POLL"))
private Poll poll;

@Column(length = 100, nullable = false, name = "option_text")
private String option;

@Column(name = "count")
private Long count;

@OneToMany(mappedBy = "pollOptions", cascade = CascadeType.REMOVE, orphanRemoval = true)
private java.util.List<PollVote> pollVotes;
}
Loading