diff --git a/src/main/java/org/example/studylog/config/SwaggerConfig.java b/src/main/java/org/example/studylog/config/SwaggerConfig.java index 53509a0..f519571 100644 --- a/src/main/java/org/example/studylog/config/SwaggerConfig.java +++ b/src/main/java/org/example/studylog/config/SwaggerConfig.java @@ -1,5 +1,7 @@ package org.example.studylog.config; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; @@ -10,8 +12,15 @@ public class SwaggerConfig { @Bean public OpenAPI openAPI(){ + SecurityScheme securityScheme = new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT"); + SecurityRequirement securityRequirement = new SecurityRequirement().addList("bearerAuth"); + return new OpenAPI() - .components(new Components()) + .components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) + .addSecurityItem(securityRequirement) .info(apiInfo()); } diff --git a/src/main/java/org/example/studylog/controller/FriendController.java b/src/main/java/org/example/studylog/controller/FriendController.java index 272131e..77aad7f 100644 --- a/src/main/java/org/example/studylog/controller/FriendController.java +++ b/src/main/java/org/example/studylog/controller/FriendController.java @@ -1,9 +1,14 @@ package org.example.studylog.controller; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.example.studylog.dto.ProfileResponseDTO; import org.example.studylog.dto.friend.FriendNameDTO; import org.example.studylog.dto.friend.FriendRequestDTO; import org.example.studylog.dto.friend.FriendResponseDTO; @@ -24,8 +29,12 @@ public class FriendController { private final FriendService friendService; - @Operation(summary = "code로 친구 조회", description = "친구 추가 시, code로 친구 조회 API") - @GetMapping(params = "code") + @Operation(summary = "code로 친구 조회", description = "친구 추가 시, code로 친구 조회하는 API") + @ApiResponse(responseCode = "200", description = "사용자 이름 조회 완료", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = FriendNameDTO.class))) + @GetMapping("by-code") public ResponseEntity findUserByCode(@RequestParam String code) { // 로그인한 사용자 oauthId 가져오기 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); @@ -36,6 +45,10 @@ public ResponseEntity findUserByCode(@RequestParam String code) { } @Operation(summary = "친구 목록 조회", description = "로그인한 사용자의 친구 목록 조회 API") + @ApiResponse(responseCode = "200", description = "친구 목록 조회 완료", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = FriendResponseDTO.class)))) @GetMapping public ResponseEntity getFriendList(){ // 로그인한 사용자 oauthId 가져오기 @@ -47,6 +60,10 @@ public ResponseEntity getFriendList(){ } @Operation(summary = "친구 검색", description = "친구 목록에서 이름으로 친구 조회 API") + @ApiResponse(responseCode = "200", description = "{query}에 대한 친구 검색 완료", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = FriendResponseDTO.class)))) @GetMapping("/search") public ResponseEntity getFriendByQuery(@RequestParam String query){ // 로그인한 사용자 oauthId 가져오기 @@ -57,7 +74,10 @@ public ResponseEntity getFriendByQuery(@RequestParam String query){ return ResponseUtil.buildResponse(200, String.format("\'%s\'에 대한 친구 검색 완료", query), friends); } - @Operation(summary = "code로 친구 추가", description = "코드로 친구 추가 API") + @Operation(summary = "code로 친구 추가", description = "code로 친구 추가 API") + @ApiResponse(responseCode = "201", description = "친구 추가 완료", + content = @Content( + mediaType = "application/json")) @PostMapping public ResponseEntity addFriend(@RequestBody @Valid FriendRequestDTO request) { // 로그인한 사용자 oauthId 가져오기 @@ -69,6 +89,10 @@ public ResponseEntity addFriend(@RequestBody @Valid FriendRequestDTO request) } @Operation(summary = "친구 삭제", description = "friendId로 친구 삭제 API") + @ApiResponse(responseCode = "200", description = "친구 삭제 완료", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = FriendResponseDTO.class))) @DeleteMapping("/{friendId}") public ResponseEntity deleteFriend(@PathVariable Long friendId){ // 로그인한 사용자 oauthId 가져오기 diff --git a/src/main/java/org/example/studylog/controller/NotificationController.java b/src/main/java/org/example/studylog/controller/NotificationController.java index fad50e2..b853628 100644 --- a/src/main/java/org/example/studylog/controller/NotificationController.java +++ b/src/main/java/org/example/studylog/controller/NotificationController.java @@ -1,6 +1,12 @@ package org.example.studylog.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; +import org.example.studylog.dto.ProfileResponseDTO; import org.example.studylog.dto.notification.NotificationListResponseDTO; import org.example.studylog.service.NotificationService; import org.example.studylog.util.ResponseUtil; @@ -20,6 +26,8 @@ public class NotificationController { private final NotificationService notificationService; + @Operation(summary = "SSE 구독") + @ApiResponse(content = @Content(schema = @Schema(implementation = SseEmitter.class))) @GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter subscribe() { // 로그인한 사용자 oauthId 가져오기 @@ -29,6 +37,11 @@ public SseEmitter subscribe() { return notificationService.createEmitter(oauthId); } + @Operation(summary = "알림 목록 조회") + @ApiResponse(responseCode = "200", description = "알림 목록 조회 완료", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = NotificationListResponseDTO.class)))) @GetMapping("/notifications") public ResponseEntity getNotificationList() { // 로그인한 사용자 oauthId 가져오기 diff --git a/src/main/java/org/example/studylog/controller/QuizController.java b/src/main/java/org/example/studylog/controller/QuizController.java index 6231487..119316f 100644 --- a/src/main/java/org/example/studylog/controller/QuizController.java +++ b/src/main/java/org/example/studylog/controller/QuizController.java @@ -1,9 +1,14 @@ package org.example.studylog.controller; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.example.studylog.dto.BackgroundDTO; import org.example.studylog.dto.oauth.CustomOAuth2User; import org.example.studylog.dto.quiz.CreateQuizRequestDTO; import org.example.studylog.dto.quiz.QuizListResponseDTO; @@ -30,6 +35,10 @@ public class QuizController { private final QuizService quizService; @Operation(summary = "퀴즈 생성", description = "recordId로 친구 생성 API") + @ApiResponse(responseCode = "200", description = "퀴즈 생성 완료", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = QuizResponseDTO.class)))) @PostMapping("/{recordId}") public ResponseEntity createQuiz( @AuthenticationPrincipal CustomOAuth2User currentUser, @@ -55,6 +64,10 @@ public ResponseEntity createQuiz( } @Operation(summary = "퀴즈 상세 조회", description = "quizId로 퀴즈 상세 조회 API") + @ApiResponse(responseCode = "200", description = "퀴즈 상세 조회 완료", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = QuizResponseDTO.class))) @GetMapping("/{quizId}") public ResponseEntity getQuiz(@AuthenticationPrincipal CustomOAuth2User currentUser, @PathVariable Long quizId){ @@ -73,7 +86,11 @@ public ResponseEntity getQuiz(@AuthenticationPrincipal CustomOAuth2User curre } } - @Operation(summary = "퀴즈 상세 조회", description = "quizId로 퀴즈 상세 조회 API") + @Operation(summary = "퀴즈 목록 조회", description = "query, date, categoryId로 퀴즈 상세 조회 API") + @ApiResponse(responseCode = "200", description = "퀴즈 목록 조회 완료", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = QuizListResponseDTO.class))) @GetMapping public ResponseEntity getQuizList( @AuthenticationPrincipal CustomOAuth2User currentUser, diff --git a/src/main/java/org/example/studylog/controller/UserController.java b/src/main/java/org/example/studylog/controller/UserController.java index 0a33c33..7943d29 100644 --- a/src/main/java/org/example/studylog/controller/UserController.java +++ b/src/main/java/org/example/studylog/controller/UserController.java @@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j; import org.example.studylog.dto.*; import org.example.studylog.dto.oauth.CustomOAuth2User; +import org.example.studylog.dto.oauth.TokenDTO; import org.example.studylog.service.UserService; import org.example.studylog.util.ResponseUtil; import org.springframework.http.MediaType; @@ -28,18 +29,33 @@ public class UserController { private final UserService userService; - @Operation(summary = "프로필 업데이트 api", description = "프로필 생성을 위한 api") - @PostMapping("/profile") + @Operation(summary = "프로필 생성", description = "프로필 생성을 위한 api") + @ApiResponse( + responseCode = "200", + description = "사용자 프로필 생성 완료", + content = @Content( + mediaType = "application/json", + schema = @Schema( + implementation = ProfileResponseDTO.class + ))) + @PostMapping(path = "/profile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity createProfile(@Valid @ModelAttribute ProfileCreateRequestDTO request) { // 로그인한 사용자 oauthId 가져오기 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); String oauthId = auth.getName(); + log.info("사용자 프로필 생성 시작: oauthId = {}", oauthId); ProfileResponseDTO dto = userService.createUserProfile(request, oauthId); + log.info("사용자 프로필 생성 완료: profileImage = {}, nickname = {}, intro = {}", + dto.getProfileImage(), dto.getNickname(), dto.getIntro()); return ResponseUtil.buildResponse(200, "사용자 프로필 생성 완료", dto); } - @Operation(summary = "프로필 수정 api", description = "프로필 수정을 위한 api") + @Operation(summary = "프로필 수정", description = "프로필 수정을 위한 api") + @ApiResponse(responseCode = "200", description = "사용자 프로필 수정 완료", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ProfileResponseDTO.class))) @PatchMapping(path = "/profile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity updateProfile(@ModelAttribute ProfileUpdateRequestDTO request) { // 로그인한 사용자 oauthId 가져오기 @@ -50,10 +66,12 @@ public ResponseEntity updateProfile(@ModelAttribute ProfileUpdateRequestDTO r return ResponseUtil.buildResponse(200, "사용자 프로필 수정 완료", dto); } - @Operation(summary = "프로필 조회 api") + @Operation(summary = "프로필 조회") @GetMapping("/profile") - @ApiResponse(responseCode = "200", description = "성공 시 data 필드는 다음과 같습니다", - content = @Content(schema = @Schema(implementation = ProfileResponseDTO.class))) + @ApiResponse(responseCode = "200", description = "사용자 프로필 조회 성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ProfileResponseDTO.class))) public ResponseEntity getProfile() { // 로그인한 사용자 oauthId 가져오기 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); @@ -63,7 +81,11 @@ public ResponseEntity getProfile() { return ResponseUtil.buildResponse(200, "사용자 프로필 조회 성공", dto); } - @Operation(summary = "로그인 유저의 마이페이지 조회 api") + @Operation(summary = "로그인 유저의 마이페이지 조회") + @ApiResponse(responseCode = "200", description = "사용자 정보 조회 성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = UserInfoResponseDTO.class))) @GetMapping public ResponseEntity getUserInfo() { // 로그인한 사용자 oauthId 가져오기 @@ -74,10 +96,12 @@ public ResponseEntity getUserInfo() { return ResponseUtil.buildResponse(200, "사용자 정보 조회 성공", dto); } - @Operation(summary = "배경화면 수정 api") - @PatchMapping("/background") - @ApiResponse(responseCode = "200", description = "성공 시 data 필드는 다음과 같습니다", - content = @Content(schema = @Schema(implementation = BackgroundDTO.ResponseDTO.class))) + @Operation(summary = "배경화면 수정") + @PatchMapping(path = "/background", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @ApiResponse(responseCode = "200", description = "사용자 배경화면 수정 완료", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = BackgroundDTO.ResponseDTO.class))) public ResponseEntity updateBackground( @AuthenticationPrincipal CustomOAuth2User currentUser, @Valid @ModelAttribute BackgroundDTO.RequestDTO dto) { diff --git a/src/main/java/org/example/studylog/controller/jwt/AuthController.java b/src/main/java/org/example/studylog/controller/jwt/AuthController.java index 4747988..91d105f 100644 --- a/src/main/java/org/example/studylog/controller/jwt/AuthController.java +++ b/src/main/java/org/example/studylog/controller/jwt/AuthController.java @@ -1,15 +1,20 @@ package org.example.studylog.controller.jwt; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.example.studylog.dto.ResponseDTO; import org.example.studylog.dto.oauth.TokenDTO; -import org.example.studylog.entity.user.User; import org.example.studylog.jwt.JWTUtil; import org.example.studylog.service.TokenService; import org.example.studylog.util.CookieUtil; import org.example.studylog.util.ResponseUtil; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -27,6 +32,25 @@ public AuthController(JWTUtil jwtUtil, TokenService tokenService) { this.tokenService = tokenService; } + @Operation(summary = "AccessToken 재발급 API", + parameters = { + @Parameter( + in = ParameterIn.COOKIE, + name = "refresh", + required = true, + description = "리프레시 토큰" + ) + }) + @ApiResponse( + responseCode = "200", + description = "토큰 재발급 완료", + content = @Content( + mediaType = "application/json", + schema = @Schema( + name = "TokenResponseDTO", + implementation = TokenDTO.ResponseDTO.class + )) + ) @PostMapping("/token-reissue") public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse response){ diff --git a/src/main/java/org/example/studylog/dto/oauth/TokenDTO.java b/src/main/java/org/example/studylog/dto/oauth/TokenDTO.java index 9583aae..49ba2c9 100644 --- a/src/main/java/org/example/studylog/dto/oauth/TokenDTO.java +++ b/src/main/java/org/example/studylog/dto/oauth/TokenDTO.java @@ -1,11 +1,14 @@ package org.example.studylog.dto.oauth; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; + @Getter @Builder +@Schema(name = "TokenDTO") public class TokenDTO { private String refreshToken; @@ -15,6 +18,7 @@ public class TokenDTO { @Getter @Builder + @Schema(name = "TokenResponseDTO") public static class ResponseDTO { private String accessToken; private String code; diff --git a/src/main/java/org/example/studylog/oauth2/ProfileCheckFilter.java b/src/main/java/org/example/studylog/oauth2/ProfileCheckFilter.java index 983d1c0..429b180 100644 --- a/src/main/java/org/example/studylog/oauth2/ProfileCheckFilter.java +++ b/src/main/java/org/example/studylog/oauth2/ProfileCheckFilter.java @@ -4,6 +4,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; import org.example.studylog.dto.oauth.CustomOAuth2User; import org.example.studylog.entity.user.User; import org.example.studylog.repository.UserRepository; @@ -14,6 +15,7 @@ import java.io.IOException; +@Slf4j public class ProfileCheckFilter extends OncePerRequestFilter { private final UserRepository userRepository; @@ -38,7 +40,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse // /signup, /users/profile의 PUT 요청은 허용, 그 외는 막음 if(!isProfileCompleted && !requestURI.startsWith("/signup")&& - !(requestURI.equals("/users/profile") && method.equalsIgnoreCase("PUT"))) { + !(requestURI.equals("/users/profile") && method.equalsIgnoreCase("POST"))) { + log.info("ProfileCheckFilter로 인해 /signup으로 리다이렉션"); response.sendRedirect("/signup"); return; }