diff --git a/src/main/kotlin/nexters/weski/batch/ResortBatchController.kt b/src/main/kotlin/nexters/weski/batch/ResortBatchController.kt new file mode 100644 index 0000000..b55bb44 --- /dev/null +++ b/src/main/kotlin/nexters/weski/batch/ResortBatchController.kt @@ -0,0 +1,71 @@ +package nexters.weski.batch + +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.tags.Tag +import nexters.weski.ski_resort.SkiResortService +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + + +@Tag(name = "스키장 개장일/폐장일 업데이트 API", description = "스키장 개장일/폐장일을 업데이트") +@RestController +class ResortBatchController( + private val resortService: SkiResortService +) { + @Operation( + summary = "스키장 개장일/폐장일 업데이트 API", + description = """ + 스키장 개장일을 업데이트하면 해당 스키장의 개장일이 변경됩니다. + date : OPENING_DATE, CLOSING_DATE 중 하나를 선택합니다. + resortId는 다음과 같습니다. + 1, 지산 리조트 + 2, 곤지암 스키장 + 3, 비발디파크 + 4, 엘리시안 강촌 + 5, 웰리힐리파크 + 6, 휘닉스파크 + 7, 하이원 스키장 + 8, 용평스키장 모나 + 9, 무주덕유산 + 10, 에덴벨리(양산) + 11, 오투리조트 + """ + ) + @PostMapping("/batch/resort-date") + fun updateResortDate( + @RequestBody + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "스키장 개장일/폐장일 업데이트 요청", + required = true, + content = [Content( + mediaType = "application/json", + schema = Schema(implementation = ResortDateUpdateRequest::class) + )] + ) + request: ResortDateUpdateRequest + ) { + resortService.updateResortDate( + resortId = request.resortId, + dateType = request.dateType, + date = request.date + ) + } + + @Operation( + summary = "스키장 운영상태 업데이트", + description = """ + 스키장 운영상태를 업데이트하면 해당 스키장의 개장일과 폐장일을 기준으로 운영상태가 변경됩니다. + 스키장 운영상태는 다음과 같습니다. + - 예정 + - 운영중 + - 운영종료 + """ + ) + @PostMapping("/batch/resort-status") + fun updateResortStatus() { + resortService.updateSkiResortStatus() + } +} diff --git a/src/main/kotlin/nexters/weski/batch/ResortDateUpdateRequest.kt b/src/main/kotlin/nexters/weski/batch/ResortDateUpdateRequest.kt new file mode 100644 index 0000000..0873c82 --- /dev/null +++ b/src/main/kotlin/nexters/weski/batch/ResortDateUpdateRequest.kt @@ -0,0 +1,32 @@ +package nexters.weski.batch + +import io.swagger.v3.oas.annotations.media.Schema +import java.time.LocalDate + +class ResortDateUpdateRequest ( + @Schema( + description = "스키장 ID", + example = "1" + ) + val resortId: Long, + + @Schema( + description = "날짜 타입 (OPENING_DATE: 개장일, CLOSING_DATE: 폐장일)", + example = "OPENING_DATE" + ) + val dateType: DateType, + + @Schema( + description = "날짜 (yyyy-MM-dd 형식)", + example = "2024-11-30" + ) + val date: LocalDate +) + +enum class DateType { + @Schema(description = "개장일") + OPENING_DATE, + + @Schema(description = "폐장일") + CLOSING_DATE +} \ No newline at end of file diff --git a/src/main/kotlin/nexters/weski/batch/ResortStatusUpdateScheduler.kt b/src/main/kotlin/nexters/weski/batch/ResortStatusUpdateScheduler.kt new file mode 100644 index 0000000..9c606f9 --- /dev/null +++ b/src/main/kotlin/nexters/weski/batch/ResortStatusUpdateScheduler.kt @@ -0,0 +1,15 @@ +package nexters.weski.batch + +import nexters.weski.ski_resort.SkiResortService +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Component + +@Component +class ResortStatusUpdateScheduler( + private val skiResortService: SkiResortService +) { + @Scheduled(cron = "15 0 0 * * ?") + fun scheduleResortStatusUpdate() { + skiResortService.updateSkiResortStatus() + } +} \ No newline at end of file diff --git a/src/main/kotlin/nexters/weski/common/config/SwaggerConfig.kt b/src/main/kotlin/nexters/weski/common/config/SwaggerConfig.kt index a9c0d37..ab82bc9 100644 --- a/src/main/kotlin/nexters/weski/common/config/SwaggerConfig.kt +++ b/src/main/kotlin/nexters/weski/common/config/SwaggerConfig.kt @@ -2,6 +2,7 @@ package nexters.weski.common.config import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.info.Info +import org.springdoc.core.models.GroupedOpenApi import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.web.filter.ForwardedHeaderFilter @@ -24,6 +25,22 @@ class SwaggerConfig { ) } + @Bean + fun userApi(): GroupedOpenApi { + return GroupedOpenApi.builder() + .group("API for WE-SKI Client") + .pathsToMatch("/api/**") + .build() + } + + @Bean + fun productApi(): GroupedOpenApi { + return GroupedOpenApi.builder() + .group("BATCH for ADMIN") + .pathsToMatch("/batch/**") + .build() + } + @Bean fun forwardedHeaderFilter(): ForwardedHeaderFilter { return ForwardedHeaderFilter() diff --git a/src/main/kotlin/nexters/weski/ski_resort/SkiResortResponseDto.kt b/src/main/kotlin/nexters/weski/ski_resort/SkiResortResponseDto.kt index addb6e9..faa56f9 100644 --- a/src/main/kotlin/nexters/weski/ski_resort/SkiResortResponseDto.kt +++ b/src/main/kotlin/nexters/weski/ski_resort/SkiResortResponseDto.kt @@ -11,6 +11,8 @@ data class SkiResortResponseDto( val resortId: Long, val name: String, val status: String, + val openingDate: String, + val closingDate: String, val openSlopes: Int, val currentWeather: SimpleCurrentWeatherDto, val weeklyWeather: List @@ -25,6 +27,8 @@ data class SkiResortResponseDto( resortId = skiResort.resortId, name = skiResort.name, status = skiResort.status.name, + openingDate = skiResort.openingDate?.toString() ?: "미정", + closingDate = skiResort.closingDate?.toString() ?: "미정", openSlopes = skiResort.openSlopes, currentWeather = currentWeather?.let { SimpleCurrentWeatherDto( diff --git a/src/main/kotlin/nexters/weski/ski_resort/SkiResortService.kt b/src/main/kotlin/nexters/weski/ski_resort/SkiResortService.kt index 37fad5e..c8eeda1 100644 --- a/src/main/kotlin/nexters/weski/ski_resort/SkiResortService.kt +++ b/src/main/kotlin/nexters/weski/ski_resort/SkiResortService.kt @@ -1,8 +1,10 @@ package nexters.weski.ski_resort +import nexters.weski.batch.DateType import nexters.weski.weather.CurrentWeatherRepository import nexters.weski.weather.DailyWeatherRepository import org.springframework.stereotype.Service +import java.time.LocalDate @Service class SkiResortService( @@ -19,4 +21,36 @@ class SkiResortService( SkiResortResponseDto.fromEntity(skiResort, currentWeather, weeklyWeather) } } + + fun updateResortDate(resortId: Long, dateType: DateType, date: LocalDate) { + val skiResort = skiResortRepository.findById(resortId) + .orElseThrow { IllegalArgumentException("해당 ID의 스키장이 존재하지 않습니다.") } + + val updatedSkiResort = when (dateType) { + DateType.OPENING_DATE -> skiResort.copy(openingDate = date) + DateType.CLOSING_DATE -> skiResort.copy(closingDate = date) + } + + skiResortRepository.save(updatedSkiResort) + } + + fun updateSkiResortStatus() { + val skiResorts = skiResortRepository.findAll() + val today = LocalDate.now() + + skiResorts.forEach { skiResort -> + val openingDate = skiResort.openingDate + val closingDate = skiResort.closingDate + + val newStatus = when { + today.isBefore(openingDate) -> ResortStatus.예정 + closingDate != null && today.isAfter(closingDate) -> ResortStatus.운영종료 + else -> ResortStatus.운영중 + } + + val updatedSkiResort = skiResort.copy(status = newStatus) + skiResortRepository.save(updatedSkiResort) + } + } + } diff --git a/src/test/kotlin/nexters/weski/ski_resort/SkiResortControllerTest.kt b/src/test/kotlin/nexters/weski/ski_resort/SkiResortControllerTest.kt index b97ab32..cd9dda1 100644 --- a/src/test/kotlin/nexters/weski/ski_resort/SkiResortControllerTest.kt +++ b/src/test/kotlin/nexters/weski/ski_resort/SkiResortControllerTest.kt @@ -43,8 +43,8 @@ class SkiResortControllerTest @Autowired constructor( ) // Given val skiResorts = listOf( - SkiResortResponseDto(1, "스키장 A", ResortStatus.운영중.name, 3, currentWeather, weeklyWeather), - SkiResortResponseDto(2, "스키장 B", ResortStatus.운영중.name, 4, currentWeather, weeklyWeather), + SkiResortResponseDto(1, "스키장 A", ResortStatus.운영중.name, "미정", "미정", 3, currentWeather, weeklyWeather), + SkiResortResponseDto(2, "스키장 B", ResortStatus.운영중.name, "미정", "미정", 4, currentWeather, weeklyWeather), ) every { skiResortService.getAllSkiResortsAndWeather() } returns skiResorts diff --git a/src/test/kotlin/nexters/weski/ski_resort/SkiResortServiceTest.kt b/src/test/kotlin/nexters/weski/ski_resort/SkiResortServiceTest.kt index f27f6f4..531e4a2 100644 --- a/src/test/kotlin/nexters/weski/ski_resort/SkiResortServiceTest.kt +++ b/src/test/kotlin/nexters/weski/ski_resort/SkiResortServiceTest.kt @@ -22,10 +22,34 @@ class SkiResortServiceTest { fun `getAllSkiResorts should return list of SkiResortDto`() { // Given val skiResorts = listOf( - SkiResort(1, "스키장 A", ResortStatus.운영중, null, null, 5, 10), - SkiResort(2, "스키장 B", ResortStatus.예정, null, null, 0, 8) + SkiResort( + resortId = 1, + name = "스키장 A", + status = ResortStatus.운영중, + openingDate = null, + closingDate = null, + openSlopes = 3, + totalSlopes = 8, + xCoordinate = "12.0", + yCoordinate = "34.0", + detailedAreaCode = "11D20201", + broadAreaCode = "11D20000" + ), + SkiResort( + resortId = 2, + name = "스키장 B", + status = ResortStatus.운영중, + openingDate = null, + closingDate = null, + openSlopes = 3, + totalSlopes = 8, + xCoordinate = "12.0", + yCoordinate = "34.0", + detailedAreaCode = "11D20201", + broadAreaCode = "11D20000" + ) ) - every { skiResortRepository.findAll() } returns skiResorts + every { skiResortRepository.findAllByOrderByOpeningDateAsc() } returns skiResorts every { currentWeatherRepository.findBySkiResortResortId(any()) } returns null every { dailyWeatherRepository.findAllBySkiResortResortId(any()) } returns emptyList() diff --git a/src/test/kotlin/nexters/weski/snow_maker/SnowMakerServiceTest.kt b/src/test/kotlin/nexters/weski/snow_maker/SnowMakerServiceTest.kt index a036e57..71aa655 100644 --- a/src/test/kotlin/nexters/weski/snow_maker/SnowMakerServiceTest.kt +++ b/src/test/kotlin/nexters/weski/snow_maker/SnowMakerServiceTest.kt @@ -36,7 +36,19 @@ class SnowMakerServiceTest { // Given val resortId = 1L val isPositive = true - val skiResort = SkiResort(resortId, "스키장 A", ResortStatus.운영중, null, null, 5, 10) + val skiResort = SkiResort( + resortId = 1, + name = "스키장 A", + status = ResortStatus.운영중, + openingDate = null, + closingDate = null, + openSlopes = 3, + totalSlopes = 8, + xCoordinate = "12.0", + yCoordinate = "34.0", + detailedAreaCode = "11D20201", + broadAreaCode = "11D20000" + ) val snowMakerVote = SnowMakerVote( isPositive = isPositive, skiResort = skiResort diff --git a/src/test/kotlin/nexters/weski/weather/WeatherServiceTest.kt b/src/test/kotlin/nexters/weski/weather/WeatherServiceTest.kt index f4f895d..daa4fff 100644 --- a/src/test/kotlin/nexters/weski/weather/WeatherServiceTest.kt +++ b/src/test/kotlin/nexters/weski/weather/WeatherServiceTest.kt @@ -29,7 +29,7 @@ class WeatherServiceTest { resortId, -5, -2, -8, -10, "눈이 내리고 있습니다.", "눈", skiResort ) every { currentWeatherRepository.findBySkiResortResortId(resortId) } returns currentWeather - every { hourlyWeatherRepository.findAll() } returns listOf() + every { hourlyWeatherRepository.findBySkiResortResortId(resortId) } returns listOf() every { dailyWeatherRepository.findAllBySkiResortResortId(resortId) } returns listOf() // When