refactor: ThemeController 코틀린 마이그레이션 및 Swagger 로직 분리

This commit is contained in:
이상진 2025-07-17 18:18:26 +09:00
parent d6be6ba240
commit 0896e3bf30
3 changed files with 92 additions and 86 deletions

View File

@ -0,0 +1,51 @@
package roomescape.theme.docs
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.validation.Valid
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestParam
import roomescape.auth.web.support.Admin
import roomescape.auth.web.support.LoginRequired
import roomescape.common.dto.response.CommonApiResponse
import roomescape.theme.web.ThemeRequest
import roomescape.theme.web.ThemeResponse
import roomescape.theme.web.ThemesResponse
@Tag(name = "5. 테마 API", description = "테마를 조회 / 추가 / 삭제할 때 사용합니다.")
interface ThemeAPI {
@LoginRequired
@Operation(summary = "모든 테마 조회", description = "모든 테마를 조회합니다.", tags = ["로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
fun getAllThemes(): ResponseEntity<CommonApiResponse<ThemesResponse>>
@Operation(summary = "가장 많이 예약된 테마 조회")
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
fun getMostReservedThemes(
@RequestParam(defaultValue = "10") @Parameter(description = "최대로 조회할 테마 갯수") count: Int
): ResponseEntity<CommonApiResponse<ThemesResponse>>
@Admin
@Operation(summary = "테마 추가", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(
ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true),
)
fun saveTheme(
@Valid @RequestBody request: ThemeRequest,
): ResponseEntity<CommonApiResponse<ThemeResponse>>
@Admin
@Operation(summary = "테마 삭제", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(
ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true),
)
fun removeTheme(
@PathVariable id: Long
): ResponseEntity<CommonApiResponse<Unit>>
}

View File

@ -1,98 +1,51 @@
package roomescape.theme.web; package roomescape.theme.web
import org.springframework.http.HttpHeaders; import io.swagger.v3.oas.annotations.Parameter
import org.springframework.http.HttpStatus; import jakarta.validation.Valid
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.*
import org.springframework.web.bind.annotation.PathVariable; import roomescape.common.dto.response.CommonApiResponse
import org.springframework.web.bind.annotation.PostMapping; import roomescape.theme.business.ThemeService
import org.springframework.web.bind.annotation.RequestBody; import roomescape.theme.docs.ThemeAPI
import org.springframework.web.bind.annotation.RequestParam; import java.net.URI
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import roomescape.auth.web.support.Admin;
import roomescape.auth.web.support.LoginRequired;
import roomescape.common.dto.response.RoomescapeApiResponse;
import roomescape.common.dto.response.RoomescapeErrorResponse;
import roomescape.theme.business.ThemeService;
@RestController @RestController
@Tag(name = "5. 테마 API", description = "테마를 조회 / 추가 / 삭제할 때 사용합니다.") class ThemeController(
public class ThemeController { private val themeService: ThemeService
) : ThemeAPI {
private final ThemeService themeService; @GetMapping("/themes")
override fun getAllThemes(): ResponseEntity<CommonApiResponse<ThemesResponse>> {
val response: ThemesResponse = themeService.findAllThemes()
public ThemeController(ThemeService themeService) { return ResponseEntity.ok(CommonApiResponse(response))
this.themeService = themeService; }
}
@LoginRequired @GetMapping("/themes/most-reserved-last-week")
@GetMapping("/themes") override fun getMostReservedThemes(
@ResponseStatus(HttpStatus.OK) @RequestParam(defaultValue = "10") @Parameter(description = "최대로 조회할 테마 갯수") count: Int
@Operation(summary = "모든 테마 조회", description = "모든 테마를 조회합니다.", tags = "로그인이 필요한 API") ): ResponseEntity<CommonApiResponse<ThemesResponse>> {
@ApiResponses({ val response: ThemesResponse = themeService.getMostReservedThemesByCount(count)
@ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)
})
public RoomescapeApiResponse<ThemesResponse> getAllThemes() {
return RoomescapeApiResponse.success(themeService.findAllThemes());
}
@GetMapping("/themes/most-reserved-last-week") return ResponseEntity.ok(CommonApiResponse(response))
@ResponseStatus(HttpStatus.OK) }
@Operation(summary = "가장 많이 예약된 테마 조회")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)
})
public RoomescapeApiResponse<ThemesResponse> getMostReservedThemes(
@RequestParam(defaultValue = "10") @Parameter(description = "최대로 조회할 테마 갯수") int count
) {
return RoomescapeApiResponse.success(themeService.getMostReservedThemesByCount(count));
}
@Admin @PostMapping("/themes")
@PostMapping("/themes") override fun saveTheme(
@ResponseStatus(HttpStatus.CREATED) @RequestBody @Valid request: ThemeRequest
@Operation(summary = "테마 추가", tags = "관리자 로그인이 필요한 API") ): ResponseEntity<CommonApiResponse<ThemeResponse>> {
@ApiResponses({ val themeResponse: ThemeResponse = themeService.addTheme(request)
@ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true),
@ApiResponse(responseCode = "409", description = "같은 이름의 테마를 추가할 수 없습니다.",
content = @Content(schema = @Schema(implementation = RoomescapeErrorResponse.class)))
})
public RoomescapeApiResponse<ThemeResponse> saveTheme(
@Valid @RequestBody ThemeRequest request,
HttpServletResponse response
) {
ThemeResponse themeResponse = themeService.addTheme(request);
response.setHeader(HttpHeaders.LOCATION, "/themes/" + themeResponse.id());
return RoomescapeApiResponse.success(themeResponse); return ResponseEntity.created(URI.create("/themes/${themeResponse.id}"))
} .body(CommonApiResponse(themeResponse))
}
@Admin @DeleteMapping("/themes/{id}")
@DeleteMapping("/themes/{id}") override fun removeTheme(
@ResponseStatus(HttpStatus.NO_CONTENT) @PathVariable id: Long
@Operation(summary = "테마 삭제", tags = "관리자 로그인이 필요한 API") ): ResponseEntity<CommonApiResponse<Unit>> {
@ApiResponses({ themeService.removeThemeById(id)
@ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true),
@ApiResponse(responseCode = "409", description = "예약된 테마는 삭제할 수 없습니다.",
content = @Content(schema = @Schema(implementation = RoomescapeErrorResponse.class)))
})
public RoomescapeApiResponse<Void> removeTheme(
@NotNull(message = "themeId는 null일 수 없습니다.") @PathVariable Long id
) {
themeService.removeThemeById(id);
return RoomescapeApiResponse.success(); return ResponseEntity.noContent().build()
} }
} }

View File

@ -3,6 +3,7 @@ package roomescape.theme.web
import io.swagger.v3.oas.annotations.media.Schema import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size import jakarta.validation.constraints.Size
import org.hibernate.validator.constraints.URL
import roomescape.theme.infrastructure.persistence.Theme import roomescape.theme.infrastructure.persistence.Theme
@Schema(name = "테마 저장 요청", description = "테마 정보를 저장할 때 사용합니다.") @Schema(name = "테마 저장 요청", description = "테마 정보를 저장할 때 사용합니다.")
@ -20,6 +21,7 @@ data class ThemeRequest(
@field:Schema(description = "필수 값이며, 썸네일 이미지 URL 을 입력해주세요.") @field:Schema(description = "필수 값이며, 썸네일 이미지 URL 을 입력해주세요.")
@NotBlank @NotBlank
@URL
val thumbnail: String val thumbnail: String
) )