Skip to content

Commit 7ad9c83

Browse files
committed
fix:swagger명세 추가, post /vote 수정
1 parent c56b525 commit 7ad9c83

8 files changed

Lines changed: 133 additions & 35 deletions

File tree

docs

Lines changed: 0 additions & 21 deletions
This file was deleted.

docs.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Notes: Spec ↔ Implementation 매핑
2+
#
3+
# page#0 관리자 페이지
4+
# 0.1 홈화면
5+
# GET /votes : 목록 (id, name, code, adminUrl, shareUrl)
6+
투표 블록 터치 → GET /votes/{id} 로 상세 조회
7+
# POST /votes : 새 투표 생성 (res.shareUrl 로 링크 복사)
8+
날짜 2개 선택 → backend에는 startDate,endDate로 저장 (두 날짜 포함 범위)
9+
프론트에서 3번째 날짜 선택 시 프론트 로직으로 2개만 유지 → 서버엔 항상 (start,end)만 전달
10+
# DELETE /votes/{id} : 투표 삭제
11+
# PATCH /votes/{id} : 이름/날짜 범위 수정 (둘 다 vote attr)
12+
13+
#
14+
# 0.2 투표 설정 화면
15+
# 참여자 칩 생성 섹션: POST /votes/{id}/participants {displayName}
16+
생성된 응답에 loginCode 포함(칩/매직링크 구현용)
17+
# 참여자 삭제: DELETE /participants/{participantId}
18+
#
19+
# 기타
20+
# Vote 삭제 시 @OneToMany(cascade=ALL, orphanRemoval=true)로 참가자 함께 삭제
21+
# 코드/링크: app.base-url + "/v/" + code (공유 링크), "/admin/votes/{id}" (관리 URL)
22+
# Validation: endDate >= startDate. name not blank.

src/main/java/com/workingdead/config/SecurityConfig.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
1717
.formLogin(form -> form.disable()) // 기본 로그인 폼 비활성화
1818
.httpBasic(basic -> basic.disable()) // 브라우저 팝업 로그인 비활성화
1919
.authorizeHttpRequests(auth -> auth
20+
.requestMatchers(
21+
"/v3/api-docs/**",
22+
"/swagger-ui/**",
23+
"/swagger-ui.html"
24+
).permitAll()
2025
.anyRequest().permitAll()
2126
);
2227

src/main/java/com/workingdead/meet/controller/ParticipantController.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44
import com.workingdead.meet.entity.*;
55
import com.workingdead.meet.repository.*;
66
import com.workingdead.meet.service.*;
7+
import io.swagger.v3.oas.annotations.*;
8+
import io.swagger.v3.oas.annotations.media.*;
9+
import io.swagger.v3.oas.annotations.responses.*;
10+
import io.swagger.v3.oas.annotations.tags.Tag;
711
import jakarta.validation.Valid;
812
import org.springframework.http.ResponseEntity;
913
import org.springframework.web.bind.annotation.*;
1014

11-
15+
@Tag(name = "Participant", description = "참여자 관리 API")
1216
@RestController
1317
@RequestMapping("")
1418
public class ParticipantController {
@@ -17,13 +21,30 @@ public class ParticipantController {
1721

1822

1923
// 0.2 참여자 추가/삭제
24+
@Operation(
25+
summary = "참여자 추가",
26+
description = "특정 투표에 새로운 참여자를 추가합니다. displayName을 기반으로 참여자 칩이 생성됩니다."
27+
)
28+
@ApiResponses({
29+
@ApiResponse(responseCode = "200", description = "참여자 추가 성공",
30+
content = @Content(schema = @Schema(implementation = ParticipantDtos.ParticipantRes.class))),
31+
@ApiResponse(responseCode = "400", description = "잘못된 요청 (displayName이 비어있는 경우)", content = @Content),
32+
@ApiResponse(responseCode = "404", description = "투표를 찾을 수 없음", content = @Content)
33+
})
2034
@PostMapping("/votes/{voteId}/participants")
2135
public ResponseEntity<ParticipantDtos.ParticipantRes> add(@PathVariable Long voteId, @RequestBody @Valid ParticipantDtos.CreateParticipantReq req) {
2236
var res = participantService.add(voteId, req.displayName());
2337
return ResponseEntity.ok(res);
2438
}
2539

26-
40+
@Operation(
41+
summary = "참여자 삭제",
42+
description = "특정 참여자를 삭제합니다."
43+
)
44+
@ApiResponses({
45+
@ApiResponse(responseCode = "204", description = "참여자 삭제 성공", content = @Content),
46+
@ApiResponse(responseCode = "404", description = "참여자를 찾을 수 없음", content = @Content)
47+
})
2748
@DeleteMapping("/participants/{participantId}")
2849
public ResponseEntity<Void> remove(@PathVariable Long participantId) {
2950
participantService.remove(participantId);

src/main/java/com/workingdead/meet/controller/VoteController.java

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,81 @@
44
import com.workingdead.meet.entity.*;
55
import com.workingdead.meet.repository.*;
66
import com.workingdead.meet.service.*;
7+
import io.swagger.v3.oas.annotations.*;
8+
import io.swagger.v3.oas.annotations.media.*;
9+
import io.swagger.v3.oas.annotations.responses.*;
10+
import io.swagger.v3.oas.annotations.tags.Tag;
711
import jakarta.validation.Valid;
812
import org.springframework.http.ResponseEntity;
913
import org.springframework.web.bind.annotation.*;
1014
import java.util.List;
1115

12-
16+
@Tag(name = "Vote", description = "투표 관리 API")
1317
@RestController
1418
@RequestMapping("/votes")
1519
public class VoteController {
1620
private final VoteService voteService;
1721
public VoteController(VoteService voteService) { this.voteService = voteService; }
1822

19-
23+
@Operation(
24+
summary = "투표 목록 조회",
25+
description = "모든 투표 목록을 조회합니다. 각 투표의 기본 정보(id, name, code, adminUrl, shareUrl, startDate, endDate)를 반환합니다." +
26+
"code+baseUrl=shareUrl"
27+
)
28+
@ApiResponses({
29+
@ApiResponse(responseCode = "200", description = "조회 성공",
30+
content = @Content(schema = @Schema(implementation = VoteDtos.VoteSummary.class)))
31+
})
2032
// 0.1 홈화면 리스트 & 생성
2133
@GetMapping
2234
public List<VoteDtos.VoteSummary> list() { return voteService.listAll(); }
2335

36+
// 0.2 투표 설정 화면 읽기/수정/삭제
37+
@GetMapping("/{id}")
38+
public VoteDtos.VoteDetail get(@PathVariable Long id) { return voteService.get(id); }
39+
2440

41+
@Operation(
42+
summary = "새 투표 생성",
43+
description = "새로운 투표를 생성합니다. 고유한 code가 자동 생성되며, shareUrl을 통해 참여자에게 공유할 수 있습니다."
44+
)
45+
@ApiResponses({
46+
@ApiResponse(responseCode = "200", description = "생성 성공",
47+
content = @Content(schema = @Schema(implementation = VoteDtos.VoteSummary.class))),
48+
@ApiResponse(responseCode = "400", description = "잘못된 요청 (name이 비어있는 경우)", content = @Content)
49+
})
2550
@PostMapping
2651
public ResponseEntity<VoteDtos.VoteSummary> create(@RequestBody @Valid VoteDtos.CreateVoteReq req) {
27-
var res = voteService.create(req.name());
52+
var res = voteService.create(req);
2853
// 0.2.3 링크 복사: res.shareUrl 포함
2954
return ResponseEntity.ok(res);
3055
}
3156

3257

33-
// 0.2 투표 설정 화면 읽기/수정/삭제
34-
@GetMapping("/{id}")
35-
public VoteDtos.VoteDetail get(@PathVariable Long id) { return voteService.get(id); }
36-
37-
58+
@Operation(
59+
summary = "투표 정보 수정",
60+
description = "투표의 이름 또는 날짜 범위를 수정합니다. endDate는 startDate보다 크거나 같아야 합니다."
61+
)
62+
@ApiResponses({
63+
@ApiResponse(responseCode = "200", description = "수정 성공",
64+
content = @Content(schema = @Schema(implementation = VoteDtos.VoteDetail.class))),
65+
@ApiResponse(responseCode = "400", description = "잘못된 요청 (endDate < startDate)", content = @Content),
66+
@ApiResponse(responseCode = "404", description = "투표를 찾을 수 없음", content = @Content)
67+
})
3868
@PatchMapping("/{id}")
3969
public VoteDtos.VoteDetail update(@PathVariable Long id, @RequestBody VoteDtos.UpdateVoteReq req) {
4070
// 날짜 범위 검증은 서비스에서 처리 (end >= start)
4171
return voteService.update(id, req);
4272
}
4373

44-
74+
@Operation(
75+
summary = "투표 삭제",
76+
description = "투표를 삭제합니다. 연관된 모든 참여자도 함께 삭제됩니다 (cascade)."
77+
)
78+
@ApiResponses({
79+
@ApiResponse(responseCode = "204", description = "삭제 성공", content = @Content),
80+
@ApiResponse(responseCode = "404", description = "투표를 찾을 수 없음", content = @Content)
81+
})
4582
@DeleteMapping("/{id}")
4683
public ResponseEntity<Void> delete(@PathVariable Long id) {
4784
voteService.delete(id);

src/main/java/com/workingdead/meet/dto/VoteDtos.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66

77

88
public class VoteDtos {
9-
public record CreateVoteReq(@NotBlank String name) {}
9+
public record CreateVoteReq(
10+
@NotBlank String name,
11+
LocalDate startDate,
12+
LocalDate endDate,
13+
List<String> participantNames // 참여자 이름 리스트 (optional)
14+
) {}
1015
public record UpdateVoteReq(String name, LocalDate startDate, LocalDate endDate) {}
1116

1217
public record VoteSummary(Long id, String name, String code, String adminUrl, String shareUrl, LocalDate startDate, LocalDate endDate) {}

src/main/java/com/workingdead/meet/service/VoteService.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.workingdead.meet.dto.ParticipantDtos;
44
import com.workingdead.meet.dto.VoteDtos;
5+
import com.workingdead.meet.entity.Participant;
56
import com.workingdead.meet.entity.Vote;
67
import com.workingdead.meet.repository.VoteRepository;
78
import jakarta.transaction.Transactional;
@@ -26,9 +27,29 @@ public VoteService(VoteRepository voteRepo, @Value("${app.base-url:http://localh
2627
}
2728

2829

29-
public VoteDtos.VoteSummary create(String name) {
30+
public VoteDtos.VoteSummary create(VoteDtos.CreateVoteReq req) {
31+
//1. Vote c
3032
String code = genCode(8);
31-
Vote v = new Vote(name, code);
33+
Vote v = new Vote(req.name(), code);
34+
35+
// 2. 날짜 범위 설정 (있으면)
36+
if (req.startDate() != null && req.endDate() != null) {
37+
if (req.endDate().isBefore(req.startDate())) {
38+
throw new IllegalArgumentException("endDate must be >= startDate");
39+
}
40+
v.setDateRange(req.startDate(), req.endDate());
41+
42+
// 3. 참여자 추가 (있으면)
43+
if (req.participantNames() != null && !req.participantNames().isEmpty()) {
44+
for (String name : req.participantNames()) {
45+
if (name != null && !name.isBlank()) {
46+
Participant p = new Participant(v, name.trim());
47+
v.getParticipants().add(p);
48+
}
49+
}
50+
}
51+
}
52+
3253
voteRepo.save(v);
3354
return toSummary(v);
3455
}

src/main/resources/application.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,11 @@ spring:
5656
init:
5757
mode: always # 외부 DB에서도 init 실행
5858
platform: postgresql # schema-@@platform@@.sql 매핑용
59+
60+
springdoc:
61+
api-docs:
62+
path: /v3/api-docs
63+
swagger-ui:
64+
path: /swagger-ui.html
65+
enabled: true
66+
packages-to-scan: com.workingdead

0 commit comments

Comments
 (0)