diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 30ec15bc..0e7b17b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -114,14 +114,15 @@ jobs: APP_JWT_ACCESS_TOKEN_EXPIRATION_SECONDS: 3600 APP_JWT_REFRESH_TOKEN_EXPIRATION_SECONDS: 1209600 GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} - GOOGLE_OAUTH_CLIENT_SECRET: ${{secrets.GOOGLE_OAUTH_CLIENT_SECRET}} + GOOGLE_OAUTH_CLIENT_SECRET: ${{ secrets.GOOGLE_OAUTH_CLIENT_SECRET }} APP_OAUTH2_REDIRECT_SUCCESS_URL: ${{ secrets.OAUTH2_REDIRECT_SUCCESS_URL }} APP_OAUTH2_EXTRA_INFO_URL: ${{ secrets.EXTRA_INFO_URL }} + FIREBASE_SERVICE_ACCOUNT_PATH: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_PATH }} with: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USERNAME }} key: ${{ secrets.EC2_SSH_KEY }} - envs: DATABASE_HOST,DATABASE_USERNAME,DATABASE_PASSWORD,APP_JWT_SECRET,APP_JWT_ACCESS_TOKEN_EXPIRATION_SECONDS,APP_JWT_REFRESH_TOKEN_EXPIRATION_SECONDS, APP_OAUTH2_REDIRECT_SUCCESS_URL, APP_OAUTH2_EXTRA_INFO_URL, GOOGLE_CLIENT_ID, GOOGLE_OAUTH_CLIENT_SECRET + envs: DATABASE_HOST,DATABASE_USERNAME,DATABASE_PASSWORD,APP_JWT_SECRET,APP_JWT_ACCESS_TOKEN_EXPIRATION_SECONDS,APP_JWT_REFRESH_TOKEN_EXPIRATION_SECONDS,APP_OAUTH2_REDIRECT_SUCCESS_URL,APP_OAUTH2_EXTRA_INFO_URL,GOOGLE_CLIENT_ID,GOOGLE_OAUTH_CLIENT_SECRET,FIREBASE_SERVICE_ACCOUNT_PATH script: | curl -fsSL https://raw.githubusercontent.com/JECT-Study/JECT2-4th-Server/main/scripts/setup-docker-caddy.sh \ | bash diff --git a/src/main/java/com/ject/vs/auth/adapter/web/AuthController.java b/src/main/java/com/ject/vs/auth/adapter/web/AuthController.java index 91613d09..90fd6d26 100644 --- a/src/main/java/com/ject/vs/auth/adapter/web/AuthController.java +++ b/src/main/java/com/ject/vs/auth/adapter/web/AuthController.java @@ -5,6 +5,8 @@ import com.ject.vs.config.CookieProperties; import com.ject.vs.config.JwtProperties; import com.ject.vs.util.CookieUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -14,6 +16,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "인증", description = "인증 관련 API") @RestController @RequiredArgsConstructor public class AuthController { @@ -22,6 +25,7 @@ public class AuthController { private final JwtProperties jwtProperties; private final CookieProperties cookieProperties; + @Operation(summary = "토큰 재발급", description = "Refresh Token으로 Access Token을 재발급합니다.") @PostMapping("/auth/reissue") public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse response) { String refreshToken = cookieUtil.getCookieValue(request, CookieUtil.CookieType.REFRESH_TOKEN); diff --git a/src/main/java/com/ject/vs/notification/adapter/web/DeviceController.java b/src/main/java/com/ject/vs/notification/adapter/web/DeviceController.java index 0f416bbe..ac4836a4 100644 --- a/src/main/java/com/ject/vs/notification/adapter/web/DeviceController.java +++ b/src/main/java/com/ject/vs/notification/adapter/web/DeviceController.java @@ -3,12 +3,15 @@ import com.ject.vs.notification.adapter.web.dto.RegisterPushTokenRequest; import com.ject.vs.notification.port.in.PushTokenUseCase; import com.ject.vs.vote.exception.UnauthorizedException; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +@Tag(name = "푸시 알림", description = "푸시 토큰 등록/해제 API (회원 전용)") @RestController @RequestMapping("/api/devices") @RequiredArgsConstructor @@ -16,6 +19,7 @@ public class DeviceController { private final PushTokenUseCase useCase; + @Operation(summary = "푸시 토큰 등록", description = "FCM/APNs 디바이스 토큰을 등록합니다. 투표 종료 시 푸시 알림 발송에 사용됩니다.") @PostMapping("/push-token") @ResponseStatus(HttpStatus.NO_CONTENT) public void register( @@ -25,6 +29,7 @@ public void register( useCase.register(userId, request.token(), request.platform()); } + @Operation(summary = "푸시 토큰 해제", description = "등록된 모든 푸시 토큰을 해제합니다.") @DeleteMapping("/push-token") @ResponseStatus(HttpStatus.NO_CONTENT) public void unregisterAll(@AuthenticationPrincipal Long userId) { diff --git a/src/main/java/com/ject/vs/notification/adapter/web/NotificationController.java b/src/main/java/com/ject/vs/notification/adapter/web/NotificationController.java index ca5e837b..15c01045 100644 --- a/src/main/java/com/ject/vs/notification/adapter/web/NotificationController.java +++ b/src/main/java/com/ject/vs/notification/adapter/web/NotificationController.java @@ -6,11 +6,14 @@ import com.ject.vs.notification.port.in.NotificationCommandUseCase; import com.ject.vs.notification.port.in.NotificationQueryUseCase; import com.ject.vs.vote.exception.UnauthorizedException; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +@Tag(name = "알림", description = "알림 관련 API (회원 전용)") @RestController @RequestMapping("/api/notifications") @RequiredArgsConstructor @@ -19,6 +22,7 @@ public class NotificationController { private final NotificationQueryUseCase queryUseCase; private final NotificationCommandUseCase commandUseCase; + @Operation(summary = "알림 목록 조회", description = "알림 목록을 조회합니다. 커서 기반 페이지네이션을 지원합니다.") @GetMapping public NotificationListResponse getList( @AuthenticationPrincipal Long userId, @@ -28,12 +32,14 @@ public NotificationListResponse getList( return NotificationListResponse.from(queryUseCase.getList(userId, cursor, size)); } + @Operation(summary = "읽지 않은 알림 수 조회", description = "읽지 않은 알림의 개수를 조회합니다.") @GetMapping("/unread-count") public UnreadCountResponse getUnreadCount(@AuthenticationPrincipal Long userId) { if (userId == null) throw new UnauthorizedException(); return new UnreadCountResponse(queryUseCase.getUnreadCount(userId)); } + @Operation(summary = "알림 읽음 처리", description = "특정 알림을 읽음 처리합니다.") @PostMapping("/{notificationId}/read") @ResponseStatus(HttpStatus.NO_CONTENT) public void markAsRead( @@ -43,6 +49,7 @@ public void markAsRead( commandUseCase.markAsRead(notificationId, userId); } + @Operation(summary = "모든 알림 읽음 처리", description = "모든 알림을 읽음 처리합니다.") @PostMapping("/read-all") public ReadAllResponse markAllAsRead(@AuthenticationPrincipal Long userId) { if (userId == null) throw new UnauthorizedException(); diff --git a/src/main/java/com/ject/vs/notification/adapter/web/NotificationPromptController.java b/src/main/java/com/ject/vs/notification/adapter/web/NotificationPromptController.java index bc1eedd1..d9e18bcd 100644 --- a/src/main/java/com/ject/vs/notification/adapter/web/NotificationPromptController.java +++ b/src/main/java/com/ject/vs/notification/adapter/web/NotificationPromptController.java @@ -4,11 +4,14 @@ import com.ject.vs.notification.port.in.NotificationPromptUseCase; import com.ject.vs.notification.port.in.NotificationPromptUseCase.PromptStatusResult; import com.ject.vs.vote.exception.UnauthorizedException; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +@Tag(name = "알림 권한 프롬프트", description = "알림 권한 요청 프롬프트 관련 API (회원 전용)") @RestController @RequestMapping("/api/me/notification-prompt") @RequiredArgsConstructor @@ -16,6 +19,7 @@ public class NotificationPromptController { private final NotificationPromptUseCase useCase; + @Operation(summary = "프롬프트 표시 여부 조회", description = "알림 권한 요청 프롬프트를 표시해야 하는지 여부를 조회합니다.") @GetMapping("/status") public PromptStatusResponse getStatus(@AuthenticationPrincipal Long userId) { if (userId == null) throw new UnauthorizedException(); @@ -23,6 +27,7 @@ public PromptStatusResponse getStatus(@AuthenticationPrincipal Long userId) { return new PromptStatusResponse(result.shouldShow(), result.totalParticipationCount()); } + @Operation(summary = "프롬프트 거절 기록", description = "사용자가 알림 권한 프롬프트를 거절했음을 기록합니다.") @PostMapping("/dismissed") @ResponseStatus(HttpStatus.NO_CONTENT) public void recordDismissed(@AuthenticationPrincipal Long userId) { diff --git a/src/main/java/com/ject/vs/notification/adapter/web/NotificationSettingController.java b/src/main/java/com/ject/vs/notification/adapter/web/NotificationSettingController.java index 8fb6609f..03eded06 100644 --- a/src/main/java/com/ject/vs/notification/adapter/web/NotificationSettingController.java +++ b/src/main/java/com/ject/vs/notification/adapter/web/NotificationSettingController.java @@ -4,11 +4,14 @@ import com.ject.vs.notification.adapter.web.dto.UpdateNotificationSettingRequest; import com.ject.vs.notification.port.in.NotificationSettingUseCase; import com.ject.vs.vote.exception.UnauthorizedException; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +@Tag(name = "알림 설정", description = "알림 설정 관련 API (회원 전용)") @RestController @RequestMapping("/api/me/notification-setting") @RequiredArgsConstructor @@ -16,12 +19,14 @@ public class NotificationSettingController { private final NotificationSettingUseCase useCase; + @Operation(summary = "알림 설정 조회", description = "현재 알림 설정 상태를 조회합니다.") @GetMapping public NotificationSettingResponse get(@AuthenticationPrincipal Long userId) { if (userId == null) throw new UnauthorizedException(); return NotificationSettingResponse.from(useCase.get(userId)); } + @Operation(summary = "알림 설정 변경", description = "푸시 알림 ON/OFF 설정을 변경합니다.") @PutMapping public NotificationSettingResponse update( @AuthenticationPrincipal Long userId, diff --git a/src/main/java/com/ject/vs/user/adapter/web/UserController.java b/src/main/java/com/ject/vs/user/adapter/web/UserController.java index 4fd6f3a9..2e715034 100644 --- a/src/main/java/com/ject/vs/user/adapter/web/UserController.java +++ b/src/main/java/com/ject/vs/user/adapter/web/UserController.java @@ -7,6 +7,8 @@ import com.ject.vs.user.adapter.web.dto.UserProfileRequest; import com.ject.vs.user.adapter.web.dto.UserProfileResponse; import com.ject.vs.user.port.UserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -17,12 +19,14 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "사용자", description = "사용자 프로필 관련 API") @RestController @RequestMapping("/api/users") @RequiredArgsConstructor public class UserController { private final UserService userService; + @Operation(summary = "추가 정보 설정", description = "사용자 추가 정보(닉네임, 성별, 생년월일)를 설정합니다.") @PostMapping("/me/profile") public ResponseEntity setupInfo(@AuthenticationPrincipal Long userId, @RequestBody UserExtraInfo userExtraInfo) { UserProfileResponse response = userService.setupAdditionalInfo(userExtraInfo, userId); @@ -30,6 +34,7 @@ public ResponseEntity setupInfo(@AuthenticationPrincipal Lo return ResponseEntity.status(HttpStatus.CREATED).body(response); } + @Operation(summary = "닉네임 중복 확인", description = "닉네임 사용 가능 여부를 확인합니다.") @PostMapping("/nickname/check") public ResponseEntity isUniqueNickname(@AuthenticationPrincipal Long userId, @RequestBody UserNicknameRec nickname) { NicknameCheckResponse response = userService.checkNickname(nickname.nickname(), userId); @@ -37,6 +42,7 @@ public ResponseEntity isUniqueNickname(@AuthenticationPri return ResponseEntity.ok(response); } + @Operation(summary = "닉네임 추천", description = "사용 가능한 랜덤 닉네임을 추천합니다.") @GetMapping("/nickname/suggest") public ResponseEntity suggestNickname(@AuthenticationPrincipal Long userId) { UserNicknameRec response = userService.suggestNickname(userId); @@ -44,6 +50,7 @@ public ResponseEntity suggestNickname(@AuthenticationPrincipal return ResponseEntity.ok(response); } + @Operation(summary = "내 프로필 조회", description = "로그인한 사용자의 프로필 정보를 조회합니다.") @GetMapping("/me") public ResponseEntity getMyProfile(@AuthenticationPrincipal Long userId) { UserProfileResponse response = userService.getUserProfile(userId); @@ -51,6 +58,7 @@ public ResponseEntity getMyProfile(@AuthenticationPrincipal return ResponseEntity.ok(response); } + @Operation(summary = "기본 프로필 초기화", description = "사용자 기본 프로필 정보를 초기화합니다.") @PostMapping("/info") public ResponseEntity initializeDefaultProfile(@AuthenticationPrincipal Long userId, @RequestBody UserProfileRequest userInfo) { return ResponseEntity.ok(userService.initializeDefaultProfile(userId, userInfo)); diff --git a/src/main/java/com/ject/vs/vote/adapter/web/GuestFreeVoteController.java b/src/main/java/com/ject/vs/vote/adapter/web/GuestFreeVoteController.java index 26e9f4b8..2787c888 100644 --- a/src/main/java/com/ject/vs/vote/adapter/web/GuestFreeVoteController.java +++ b/src/main/java/com/ject/vs/vote/adapter/web/GuestFreeVoteController.java @@ -4,11 +4,15 @@ import com.ject.vs.vote.adapter.web.dto.FreeVotesResponse; import com.ject.vs.vote.domain.GuestFreeVote; import com.ject.vs.vote.port.GuestFreeVoteService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "비회원 무료 투표", description = "비회원 무료 투표권 관련 API") @RestController @RequestMapping("/api/me") @RequiredArgsConstructor @@ -16,8 +20,9 @@ public class GuestFreeVoteController { private final GuestFreeVoteService guestFreeVoteService; + @Operation(summary = "잔여 무료 투표권 조회", description = "비회원의 잔여 무료 투표권 수를 조회합니다. 회원은 remainingFreeVotes가 null로 응답됩니다.") @GetMapping("/free-votes") - public FreeVotesResponse getFreeVotes(@AnonymousId String anonymousId) { + public FreeVotesResponse getFreeVotes(@Parameter(hidden = true) @AnonymousId String anonymousId) { return new FreeVotesResponse( guestFreeVoteService.remaining(anonymousId), GuestFreeVote.totalFreeVotes() diff --git a/src/main/java/com/ject/vs/vote/adapter/web/ImmersiveVoteController.java b/src/main/java/com/ject/vs/vote/adapter/web/ImmersiveVoteController.java index 8c8b5324..083716bd 100644 --- a/src/main/java/com/ject/vs/vote/adapter/web/ImmersiveVoteController.java +++ b/src/main/java/com/ject/vs/vote/adapter/web/ImmersiveVoteController.java @@ -9,11 +9,15 @@ import com.ject.vs.vote.port.in.ImmersiveVoteCommandUseCase; import com.ject.vs.vote.port.in.ImmersiveVoteQueryUseCase; import com.ject.vs.vote.port.in.VoteResultQueryUseCase; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +@Tag(name = "몰입형 투표", description = "몰입형 투표 피드 관련 API") @RestController @RequestMapping("/api/immersive-votes") @RequiredArgsConstructor @@ -23,31 +27,35 @@ public class ImmersiveVoteController { private final ImmersiveVoteQueryUseCase immersiveVoteQueryUseCase; private final VoteResultQueryUseCase voteResultQueryUseCase; + @Operation(summary = "몰입형 투표 피드 조회", description = "스와이프 형식의 몰입형 투표 피드를 조회합니다. 커서 기반 페이지네이션을 지원합니다.") @GetMapping public ImmersiveFeedResponse getFeed( @RequestParam(required = false) Long cursor, @RequestParam(defaultValue = "10") int size, @AuthenticationPrincipal Long userId, - @AnonymousId String anonymousId) { + @Parameter(hidden = true) @AnonymousId String anonymousId) { return ImmersiveFeedResponse.from(immersiveVoteQueryUseCase.getFeed(cursor, size, userId, anonymousId)); } + @Operation(summary = "투표 참여/취소", description = "투표에 참여하거나 같은 옵션 재클릭 시 취소합니다. 비회원은 5회까지 무료 투표 가능합니다.") @PostMapping("/{voteId}/participate") public ImmersiveParticipateResponse participateOrCancel( @PathVariable Long voteId, @AuthenticationPrincipal Long userId, - @AnonymousId String anonymousId, + @Parameter(hidden = true) @AnonymousId String anonymousId, @RequestBody @Valid ParticipateRequest request) { ImmersiveVoteCommandUseCase.ImmersiveParticipateResult result = immersiveVoteCommandUseCase.participateOrCancel(voteId, userId, anonymousId, request.optionId()); return ImmersiveParticipateResponse.from(result); } + @Operation(summary = "실시간 투표 현황 조회", description = "투표 후 실시간 비율 갱신을 위한 폴링 API입니다.") @GetMapping("/{voteId}/live") public ImmersiveLiveResponse getLive(@PathVariable Long voteId) { return ImmersiveLiveResponse.from(immersiveVoteQueryUseCase.getLive(voteId)); } + @Operation(summary = "공유 링크 생성", description = "투표 공유를 위한 링크를 생성합니다.") @GetMapping("/{voteId}/share") public ShareLinkResponse getShareLink(@PathVariable Long voteId) { return ShareLinkResponse.from(voteResultQueryUseCase.getShareLink(voteId)); diff --git a/src/main/java/com/ject/vs/vote/adapter/web/VoteController.java b/src/main/java/com/ject/vs/vote/adapter/web/VoteController.java index 221af035..ae66814c 100644 --- a/src/main/java/com/ject/vs/vote/adapter/web/VoteController.java +++ b/src/main/java/com/ject/vs/vote/adapter/web/VoteController.java @@ -5,12 +5,16 @@ import com.ject.vs.vote.exception.UnauthorizedException; import com.ject.vs.vote.port.VoteDetailQueryService; import com.ject.vs.vote.port.in.VoteCommandUseCase; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +@Tag(name = "일반형 투표", description = "일반형 투표 관련 API") @RestController @RequestMapping("/api/votes") @RequiredArgsConstructor @@ -19,6 +23,7 @@ public class VoteController { private final VoteCommandUseCase voteCommandUseCase; private final VoteDetailQueryService voteDetailQueryService; + @Operation(summary = "투표 생성", description = "새로운 투표를 생성합니다. 회원만 가능합니다.") @PostMapping @ResponseStatus(HttpStatus.CREATED) public VoteCreateResponse create( @@ -28,19 +33,21 @@ public VoteCreateResponse create( return VoteCreateResponse.from(voteCommandUseCase.create(request.toCommand())); } + @Operation(summary = "투표 상세 조회", description = "투표 상세 정보를 조회합니다. 투표 전에는 결과(voteCount/ratio)가 null로 응답됩니다.") @GetMapping("/{voteId}") public VoteDetailResponse getDetail( @PathVariable Long voteId, @AuthenticationPrincipal Long userId, - @AnonymousId String anonymousId) { + @Parameter(hidden = true) @AnonymousId String anonymousId) { return VoteDetailResponse.from(voteDetailQueryService.getDetail(voteId, userId, anonymousId)); } + @Operation(summary = "투표 참여", description = "투표에 참여합니다. 비회원은 5회까지 무료 투표 가능합니다.") @PostMapping("/{voteId}/participate") public ParticipateResponse participate( @PathVariable Long voteId, @AuthenticationPrincipal Long userId, - @AnonymousId String anonymousId, + @Parameter(hidden = true) @AnonymousId String anonymousId, @RequestBody @Valid ParticipateRequest request) { VoteCommandUseCase.ParticipateResult result = userId != null ? voteCommandUseCase.participateAsMember(voteId, userId, request.optionId()) @@ -48,6 +55,7 @@ public ParticipateResponse participate( return ParticipateResponse.from(result); } + @Operation(summary = "다시 투표하기", description = "투표를 취소합니다. 회원만 가능합니다.") @DeleteMapping("/{voteId}/participate") @ResponseStatus(HttpStatus.NO_CONTENT) public void cancel( diff --git a/src/main/java/com/ject/vs/vote/adapter/web/VoteEmojiController.java b/src/main/java/com/ject/vs/vote/adapter/web/VoteEmojiController.java index 3da6b306..801668d9 100644 --- a/src/main/java/com/ject/vs/vote/adapter/web/VoteEmojiController.java +++ b/src/main/java/com/ject/vs/vote/adapter/web/VoteEmojiController.java @@ -4,21 +4,26 @@ import com.ject.vs.vote.adapter.web.dto.EmojiRequest; import com.ject.vs.vote.adapter.web.dto.EmojiResponse; import com.ject.vs.vote.port.in.VoteEmojiCommandUseCase; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +@Tag(name = "이모지 반응", description = "투표 이모지 반응 API") @RestController @RequiredArgsConstructor public class VoteEmojiController { private final VoteEmojiCommandUseCase voteEmojiCommandUseCase; + @Operation(summary = "일반형 투표 이모지 반응", description = "이모지 반응을 추가/변경/취소합니��. 같은 이모지 재선택 또는 null 전송 시 취소됩니다.") @PutMapping("/api/votes/{voteId}/emoji") public EmojiResponse reactOnVote( @PathVariable Long voteId, @AuthenticationPrincipal Long userId, - @AnonymousId String anonymousId, + @Parameter(hidden = true) @AnonymousId String anonymousId, @RequestBody EmojiRequest request) { VoteEmojiCommandUseCase.EmojiResult result = userId != null ? voteEmojiCommandUseCase.reactAsMember(voteId, userId, request.emoji()) @@ -26,11 +31,12 @@ public EmojiResponse reactOnVote( return EmojiResponse.from(result); } + @Operation(summary = "몰입형 투표 이모지 반응", description = "이모지 반응을 추가/변경/취소합니다. 같은 이모지 재선택 또는 null 전송 시 취소됩니다.") @PutMapping("/api/immersive-votes/{voteId}/emoji") public EmojiResponse reactOnImmersiveVote( @PathVariable Long voteId, @AuthenticationPrincipal Long userId, - @AnonymousId String anonymousId, + @Parameter(hidden = true) @AnonymousId String anonymousId, @RequestBody EmojiRequest request) { VoteEmojiCommandUseCase.EmojiResult result = userId != null ? voteEmojiCommandUseCase.reactAsMember(voteId, userId, request.emoji()) diff --git a/src/main/java/com/ject/vs/vote/adapter/web/VoteResultController.java b/src/main/java/com/ject/vs/vote/adapter/web/VoteResultController.java index cb1b9fa7..3eeb3ead 100644 --- a/src/main/java/com/ject/vs/vote/adapter/web/VoteResultController.java +++ b/src/main/java/com/ject/vs/vote/adapter/web/VoteResultController.java @@ -3,10 +3,13 @@ import com.ject.vs.vote.adapter.web.dto.ShareLinkResponse; import com.ject.vs.vote.adapter.web.dto.VoteResultResponse; import com.ject.vs.vote.port.in.VoteResultQueryUseCase; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +@Tag(name = "투표 결과", description = "마감된 투표 결과 조회 API") @RestController @RequestMapping("/api/votes/{voteId}") @RequiredArgsConstructor @@ -14,6 +17,7 @@ public class VoteResultController { private final VoteResultQueryUseCase voteResultQueryUseCase; + @Operation(summary = "투표 결과 조회", description = "마감된 투표의 결과를 조회합니다. 진행 중 투표는 403 응답합니다. 비회원은 insight가 잠금 상태로 응답됩니다.") @GetMapping("/result") public VoteResultResponse getResult( @PathVariable Long voteId, @@ -21,6 +25,7 @@ public VoteResultResponse getResult( return VoteResultResponse.from(voteResultQueryUseCase.getResult(voteId, userId)); } + @Operation(summary = "공유 링크 생성", description = "투표 공유를 위한 링크를 생성합니다.") @GetMapping("/share") public ShareLinkResponse getShareLink(@PathVariable Long voteId) { return ShareLinkResponse.from(voteResultQueryUseCase.getShareLink(voteId));