diff --git a/src/main/java/zim/tave/memory/config/OpenApiConfig.java b/src/main/java/zim/tave/memory/config/OpenApiConfig.java index cc2f267..9dc3d86 100644 --- a/src/main/java/zim/tave/memory/config/OpenApiConfig.java +++ b/src/main/java/zim/tave/memory/config/OpenApiConfig.java @@ -6,6 +6,9 @@ import io.swagger.v3.oas.annotations.info.Info; import io.swagger.v3.oas.annotations.security.SecurityScheme; import io.swagger.v3.oas.annotations.servers.Server; +import io.swagger.v3.oas.models.media.Schema; +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @@ -29,4 +32,46 @@ in = SecuritySchemeIn.HEADER ) public class OpenApiConfig { -} \ No newline at end of file + + @Bean + public OpenApiCustomizer apiResponseDtoVoidSchemaCustomizer() { + return openApi -> { + + // Components가 null일 경우 NPE 방지 + if (openApi.getComponents() == null) { + openApi.setComponents(new Components()); + } + + // ApiResponseDto 스키마를 명시적으로 등록 + Schema voidSchema = new Schema<>(); + voidSchema.setType("object"); + voidSchema.setDescription("공통 응답 형식 (에러 응답용 - data 필드는 null)"); + + Schema codeSchema = new Schema<>(); + codeSchema.setType("integer"); + codeSchema.setDescription("응답 코드"); + voidSchema.addProperty("code", codeSchema); + + Schema messageSchema = new Schema<>(); + messageSchema.setType("string"); + messageSchema.setDescription("응답 메시지"); + voidSchema.addProperty("message", messageSchema); + + Schema dataSchema = new Schema<>(); + dataSchema.setType("object"); + dataSchema.setNullable(true); + dataSchema.setDescription("데이터 (에러 응답에서는 항상 null)"); + voidSchema.addProperty("data", dataSchema); + + // addProperty 사용 제거 → Map + setProperties 방식으로 안전하게 설정 + Map> properties = new LinkedHashMap<>(); + properties.put("code", codeSchema); + properties.put("message", messageSchema); + properties.put("data", dataSchema); + voidSchema.setProperties(properties); + + // 스키마 등록 + openApi.getComponents().addSchemas("ApiResponseDtoVoid", voidSchema); + }; + } +} diff --git a/src/main/java/zim/tave/memory/config/swagger/SwaggerErrorCodeExampleCustomizer.java b/src/main/java/zim/tave/memory/config/swagger/SwaggerErrorCodeExampleCustomizer.java index c2a9f7d..3aa8928 100644 --- a/src/main/java/zim/tave/memory/config/swagger/SwaggerErrorCodeExampleCustomizer.java +++ b/src/main/java/zim/tave/memory/config/swagger/SwaggerErrorCodeExampleCustomizer.java @@ -65,10 +65,10 @@ public Operation customize(Operation operation, HandlerMethod handlerMethod) { apiResponse.getContent().addMediaType("application/json", mediaType); } - // Schema 설정 (ApiResponseDto 참조) + // Schema 설정 (ApiResponseDto 참조 - 에러 응답은 data가 null이므로 ApiResponseDtoVoid로 생성됨) if (mediaType.getSchema() == null) { io.swagger.v3.oas.models.media.Schema schema = new io.swagger.v3.oas.models.media.Schema<>(); - schema.set$ref("#/components/schemas/ApiResponseDto"); + schema.set$ref("#/components/schemas/ApiResponseDtoVoid"); mediaType.setSchema(schema); } @@ -142,6 +142,8 @@ private ResponseCode mapErrorCodeToResponseCode(ErrorCode errorCode) { case TRIP_NAME_REQUIRED -> ResponseCode.TRIP_NAME_REQUIRED; case TRIP_NAME_TOO_LONG -> ResponseCode.TRIP_NAME_TOO_LONG; case TRIP_DESCRIPTION_TOO_LONG -> ResponseCode.TRIP_DESCRIPTION_TOO_LONG; + case NOT_PAST_TRIP -> ResponseCode.NOT_PAST_TRIP; + case CANNOT_ADD_DIARY_TO_PAST_TRIP -> ResponseCode.CANNOT_ADD_DIARY_TO_PAST_TRIP; case KAKAO_LOGIN_REQUIRED -> ResponseCode.KAKAO_LOGIN_REQUIRED; case INCOMPLETE_USER_INFO -> ResponseCode.INCOMPLETE_USER_INFO; case ALREADY_JOINED -> ResponseCode.ALREADY_JOINED; @@ -151,6 +153,29 @@ private ResponseCode mapErrorCodeToResponseCode(ErrorCode errorCode) { case FILE_TOO_LARGE -> ResponseCode.FILE_TOO_LARGE; case FILE_TYPE_NOT_ALLOWED -> ResponseCode.FILE_TYPE_NOT_ALLOWED; case FILE_UPLOAD_FAILED -> ResponseCode.FILE_UPLOAD_FAILED; + // 보관 + case TRIP_NOT_STORED -> ResponseCode.TRIP_NOT_STORED; + case DIARY_NOT_STORED -> ResponseCode.DIARY_NOT_STORED; + // 카카오 로그인 + case KAKAO_TOKEN_MISSING -> ResponseCode.KAKAO_TOKEN_MISSING; + case KAKAO_INVALID_TOKEN -> ResponseCode.KAKAO_INVALID_TOKEN; + case KAKAO_UNAUTHORIZED -> ResponseCode.KAKAO_UNAUTHORIZED; + case KAKAO_SERVER_ERROR -> ResponseCode.KAKAO_SERVER_ERROR; + case KAKAO_RESPONSE_PARSING_ERROR -> ResponseCode.KAKAO_RESPONSE_PARSING_ERROR; + case KAKAO_API_UNKNOWN_ERROR -> ResponseCode.KAKAO_API_UNKNOWN_ERROR; + // 보드 + case BOARD_REQUIRED_FIELDS_MISSING -> ResponseCode.BOARD_REQUIRED_FIELDS_MISSING; + case BOARD_THEME_NOT_FOUND -> ResponseCode.BOARD_THEME_NOT_FOUND; + case BOARD_CREATE_FAILED -> ResponseCode.BOARD_CREATE_FAILED; + case BOARD_NOT_FOUND -> ResponseCode.BOARD_NOT_FOUND; + case BOARD_UPDATE_FORBIDDEN -> ResponseCode.BOARD_UPDATE_FORBIDDEN; + case BOARD_STICKER_NOT_FOUND -> ResponseCode.BOARD_STICKER_NOT_FOUND; + case BOARD_UPDATE_INTERNAL_ERROR -> ResponseCode.BOARD_UPDATE_INTERNAL_ERROR; + case BOARD_DELETE_FORBIDDEN -> ResponseCode.BOARD_DELETE_FORBIDDEN; + case BOARD_STICKER_MAP_NOT_FOUND -> ResponseCode.BOARD_STICKER_MAP_NOT_FOUND; + case BOARD_ACCESS_FORBIDDEN -> ResponseCode.BOARD_ACCESS_FORBIDDEN; + // 알림 + case ALARM_NOT_AGREED -> ResponseCode.ALARM_NOT_AGREED; default -> ResponseCode.SERVER_ERROR; }; } diff --git a/src/main/java/zim/tave/memory/controller/EmotionController.java b/src/main/java/zim/tave/memory/controller/EmotionController.java index 0a4c4b2..3ab901a 100644 --- a/src/main/java/zim/tave/memory/controller/EmotionController.java +++ b/src/main/java/zim/tave/memory/controller/EmotionController.java @@ -2,7 +2,6 @@ import io.swagger.v3.oas.annotations.Operation; 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 io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirements; @@ -14,6 +13,8 @@ import org.springframework.web.bind.annotation.RestController; import zim.tave.memory.config.swagger.ApiErrorCodeExamples; import zim.tave.memory.domain.Emotion; +import zim.tave.memory.global.common.ApiResponseDto; +import zim.tave.memory.global.common.ResponseCode; import zim.tave.memory.global.common.exception.ErrorCode; import zim.tave.memory.service.EmotionService; @@ -35,8 +36,8 @@ public class EmotionController { content = @Content) }) @GetMapping("") - public ResponseEntity> getAllEmotions() { + public ResponseEntity>> getAllEmotions() { List emotions = emotionService.getAllEmotions(); - return ResponseEntity.ok(emotions); + return ResponseEntity.ok(ApiResponseDto.success(ResponseCode.SUCCESS, emotions)); } } diff --git a/src/main/java/zim/tave/memory/controller/JoinController.java b/src/main/java/zim/tave/memory/controller/JoinController.java index 261f544..0df886a 100644 --- a/src/main/java/zim/tave/memory/controller/JoinController.java +++ b/src/main/java/zim/tave/memory/controller/JoinController.java @@ -2,7 +2,6 @@ import io.swagger.v3.oas.annotations.Operation; 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 io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -45,17 +44,17 @@ public class JoinController { @ApiResponse(responseCode = "400", description = """ 잘못된 요청입니다. 다음 에러 코드가 발생할 수 있습니다: - KAKAO_LOGIN_REQUIRED: 카카오 로그인이 필요합니다. - """), + """, content = @Content), @ApiResponse(responseCode = "401", description = """ 인증 실패입니다. 다음 에러 코드가 발생할 수 있습니다: - AUTHENTICATION_FAILED - INVALID_TOKEN - UNAUTHORIZED_USER - """), + """, content = @Content), @ApiResponse(responseCode = "409", description = """ 중복 가입 오류입니다: - ALREADY_JOINED: 이미 가입된 사용자입니다. - """) + """, content = @Content) }) @PostMapping("/join") public ResponseEntity> join( diff --git a/src/main/java/zim/tave/memory/controller/MyPageController.java b/src/main/java/zim/tave/memory/controller/MyPageController.java index a1e5596..154e470 100644 --- a/src/main/java/zim/tave/memory/controller/MyPageController.java +++ b/src/main/java/zim/tave/memory/controller/MyPageController.java @@ -2,7 +2,6 @@ import io.swagger.v3.oas.annotations.Operation; 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 io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -19,7 +18,6 @@ import zim.tave.memory.global.common.ApiResponseDto; import zim.tave.memory.global.common.ResponseCode; import zim.tave.memory.global.common.exception.ErrorCode; -import zim.tave.memory.security.CustomUserDetails; import zim.tave.memory.service.MyPageService; import zim.tave.memory.service.VisitedCountryService; @@ -93,7 +91,8 @@ public ResponseEntity> getMyPage( - USER_NOT_FOUND: 사용자를 찾을 수 없습니다. - VISITED_COUNTRY_NOT_FOUND: 방문한 국가 정보가 없습니다. """, content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류로 인해 방문 국가 조회에 실패하였습니다.") + @ApiResponse(responseCode = "500", description = "서버 오류로 인해 방문 국가 조회에 실패하였습니다.", + content = @Content) }) @GetMapping("/mypage-visited-countries") public ResponseEntity> getVisitedCountries( diff --git a/src/main/java/zim/tave/memory/controller/SettingController.java b/src/main/java/zim/tave/memory/controller/SettingController.java index 9eb9ec6..e2b0af2 100644 --- a/src/main/java/zim/tave/memory/controller/SettingController.java +++ b/src/main/java/zim/tave/memory/controller/SettingController.java @@ -84,7 +84,8 @@ public ResponseEntity> deleteAccount( 리소스를 찾을 수 없습니다: - USER_NOT_FOUND: 사용자를 찾을 수 없습니다. """, content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류로 인해 회원 정보 수정 실패") + @ApiResponse(responseCode = "500", description = "서버 오류로 인해 회원 정보 수정 실패", + content = @Content) }) @PatchMapping("/users/me") public ResponseEntity> updateUserInfo( diff --git a/src/main/java/zim/tave/memory/controller/StorageController.java b/src/main/java/zim/tave/memory/controller/StorageController.java index 987ea4b..3d5ac1d 100644 --- a/src/main/java/zim/tave/memory/controller/StorageController.java +++ b/src/main/java/zim/tave/memory/controller/StorageController.java @@ -18,7 +18,6 @@ import zim.tave.memory.global.common.ApiResponseDto; import zim.tave.memory.global.common.ResponseCode; import zim.tave.memory.global.common.exception.ErrorCode; -import zim.tave.memory.security.CustomUserDetails; import zim.tave.memory.service.StorageService; import java.util.List; diff --git a/src/main/java/zim/tave/memory/controller/TripThemeController.java b/src/main/java/zim/tave/memory/controller/TripThemeController.java index 48a9d7c..fb2419f 100644 --- a/src/main/java/zim/tave/memory/controller/TripThemeController.java +++ b/src/main/java/zim/tave/memory/controller/TripThemeController.java @@ -14,6 +14,8 @@ import zim.tave.memory.config.swagger.ApiErrorCodeExamples; import zim.tave.memory.domain.TripTheme; import zim.tave.memory.dto.response.TripThemeResponseDto; +import zim.tave.memory.global.common.ApiResponseDto; +import zim.tave.memory.global.common.ResponseCode; import zim.tave.memory.global.common.exception.ErrorCode; import zim.tave.memory.repository.TripThemeRepository; @@ -37,11 +39,11 @@ public class TripThemeController { @ApiResponse(responseCode = "500", description = "서버 내부 오류입니다.", content = @Content) }) - public ResponseEntity> getAllThemes() { + public ResponseEntity>> getAllThemes() { List themes = tripThemeRepository.findAllByOrderById(); List response = themes.stream() .map(TripThemeResponseDto::from) .collect(Collectors.toList()); - return ResponseEntity.ok(response); + return ResponseEntity.ok(ApiResponseDto.success(ResponseCode.SUCCESS, response)); } } diff --git a/src/main/java/zim/tave/memory/controller/WeatherController.java b/src/main/java/zim/tave/memory/controller/WeatherController.java index f9ecd56..a717646 100644 --- a/src/main/java/zim/tave/memory/controller/WeatherController.java +++ b/src/main/java/zim/tave/memory/controller/WeatherController.java @@ -12,6 +12,8 @@ import org.springframework.web.bind.annotation.*; import zim.tave.memory.config.swagger.ApiErrorCodeExamples; import zim.tave.memory.dto.response.WeatherResponseDto; +import zim.tave.memory.global.common.ApiResponseDto; +import zim.tave.memory.global.common.ResponseCode; import zim.tave.memory.global.common.exception.ErrorCode; import zim.tave.memory.service.WeatherService; @@ -34,9 +36,9 @@ public class WeatherController { @ApiResponse(responseCode = "500", description = "서버 오류입니다.", content = @Content) }) - public ResponseEntity> getAllWeathers() { + public ResponseEntity>> getAllWeathers() { List weathers = weatherService.getAllWeathers(); - return ResponseEntity.ok(weathers); + return ResponseEntity.ok(ApiResponseDto.success(ResponseCode.SUCCESS, weathers)); } @GetMapping("/{weatherId}") @@ -47,9 +49,10 @@ public ResponseEntity> getAllWeathers() { @ApiResponse(responseCode = "404", description = "날씨를 찾을 수 없습니다.\n- WEATHER_NOT_FOUND: 날씨를 찾을 수 없습니다.", content = @Content) }) - public WeatherResponseDto getWeatherById( + public ResponseEntity> getWeatherById( @Parameter(description = "날씨 ID", required = true, example = "1") @PathVariable Long weatherId) { - return weatherService.getWeatherById(weatherId); + WeatherResponseDto weather = weatherService.getWeatherById(weatherId); + return ResponseEntity.ok(ApiResponseDto.success(ResponseCode.SUCCESS, weather)); } } diff --git a/src/main/java/zim/tave/memory/global/common/ApiResponseDto.java b/src/main/java/zim/tave/memory/global/common/ApiResponseDto.java index 5cd4743..a9044ed 100644 --- a/src/main/java/zim/tave/memory/global/common/ApiResponseDto.java +++ b/src/main/java/zim/tave/memory/global/common/ApiResponseDto.java @@ -10,7 +10,7 @@ @NoArgsConstructor @AllArgsConstructor @Builder -@Schema(name = "ApiResponseDto", description = "공통 응답 형식") +@Schema(description = "공통 응답 형식") public class ApiResponseDto { @Schema(description = "응답 코드") @@ -18,6 +18,8 @@ public class ApiResponseDto { @Schema(description = "응답 메시지") private String message; + + @Schema(description = "데이터") private T data; // 성공 응답