diff --git a/src/main/kotlin/roomescape/theme/business/ThemeService.kt b/src/main/kotlin/roomescape/theme/business/ThemeService.kt index 7ee4fb80..a290fa78 100644 --- a/src/main/kotlin/roomescape/theme/business/ThemeService.kt +++ b/src/main/kotlin/roomescape/theme/business/ThemeService.kt @@ -9,11 +9,13 @@ import org.springframework.transaction.annotation.Transactional import roomescape.admin.business.AdminService import roomescape.common.config.next import roomescape.common.dto.AuditInfo +import roomescape.common.util.DateUtils import roomescape.theme.exception.ThemeErrorCode import roomescape.theme.exception.ThemeException import roomescape.theme.infrastructure.persistence.ThemeEntity import roomescape.theme.infrastructure.persistence.ThemeRepository import roomescape.theme.web.* +import java.time.LocalDate private val log: KLogger = KotlinLogging.logger {} @@ -43,13 +45,18 @@ class ThemeService( } @Transactional(readOnly = true) - fun findAllInfosByIds(request: ThemeIdListRequest): ThemeInfoListResponse { - log.info { "[ThemeService.findThemesByIds] 예약 페이지에서의 테마 목록 조회 시작: themeIds=${request.themeIds}" } - val result: List = themeRepository.findAllByIdIn(request.themeIds) + fun findMostReservedThemeLastWeek(count: Int): ThemeInfoListResponse { + log.info { "[ThemeService.findMostReservedThemeLastWeek] 인기 테마 조회 시작: count=$count" } + + val previousWeekSunday = DateUtils.getSundayOfPreviousWeek(LocalDate.now()) + val previousWeekSaturday = previousWeekSunday.plusDays(6) + + return themeRepository.findMostReservedThemeByDateAndCount(previousWeekSunday, previousWeekSaturday, count) + .toListResponse() + .also { + log.info { "[ThemeService.findMostReservedThemeLastWeek] ${it.themes.size} / $count 개의 인기 테마 조회 완료" } + } - return result.toInfoListResponse().also { - log.info { "[ThemeService.findThemesByIds] ${it.themes.size} / ${request.themeIds.size} 개 테마 조회 완료" } - } } // ======================================== diff --git a/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt b/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt index 58c7e76a..82163cc6 100644 --- a/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt +++ b/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt @@ -5,8 +5,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponses import jakarta.validation.Valid import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestParam import roomescape.admin.infrastructure.persistence.AdminType import roomescape.admin.infrastructure.persistence.Privilege import roomescape.auth.web.support.AdminOnly @@ -50,13 +52,13 @@ interface AdminThemeAPI { } interface PublicThemeAPI { - @Public - @Operation(summary = "입력된 모든 ID에 대한 테마 정보 조회") - @ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true)) - fun findThemeInfosByIds(request: ThemeIdListRequest): ResponseEntity> - @Public @Operation(summary = "입력된 테마 ID에 대한 정보 조회") @ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true)) fun findThemeInfoById(@PathVariable id: Long): ResponseEntity> + + @Public + @Operation(summary = "지난 주에 가장 많이 예약된 count 개의 테마 조회") + @GetMapping("/most-reserved") + fun findMostReservedThemeLastWeek(@RequestParam count: Int): ResponseEntity> } diff --git a/src/main/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepository.kt b/src/main/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepository.kt index 25934626..29db0b47 100644 --- a/src/main/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepository.kt +++ b/src/main/kotlin/roomescape/theme/infrastructure/persistence/ThemeRepository.kt @@ -2,6 +2,8 @@ package roomescape.theme.infrastructure.persistence import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query +import roomescape.theme.business.domain.ThemeInfo +import java.time.LocalDate interface ThemeRepository : JpaRepository { @@ -10,5 +12,32 @@ interface ThemeRepository : JpaRepository { fun existsByName(name: String): Boolean - fun findAllByIdIn(themeIds: List): List + @Query( + value = """ + SELECT + t.id, t.name, t.description, t.difficulty, t.thumbnail_url, t.price, + t.min_participants, t.max_participants, + t.available_minutes, t.expected_minutes_from, t.expected_minutes_to + FROM + theme t + JOIN ( + SELECT + s.theme_id, count(*) as reservation_count + FROM + schedule s + JOIN + reservation r ON s.id = r.schedule_id AND r.status = 'CONFIRMED' + WHERE + s.status = 'RESERVED' + AND (s.date BETWEEN :startFrom AND :endAt) + GROUP BY + s.theme_id + ORDER BY + reservation_count desc + LIMIT :count + ) ranked_themes ON t.id = ranked_themes.theme_id + """, + nativeQuery = true + ) + fun findMostReservedThemeByDateAndCount(startFrom: LocalDate, endAt: LocalDate, count: Int): List } diff --git a/src/main/kotlin/roomescape/theme/web/ThemeController.kt b/src/main/kotlin/roomescape/theme/web/ThemeController.kt index f79146f0..8d67ea28 100644 --- a/src/main/kotlin/roomescape/theme/web/ThemeController.kt +++ b/src/main/kotlin/roomescape/theme/web/ThemeController.kt @@ -1,6 +1,5 @@ package roomescape.theme.web -import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import roomescape.common.dto.response.CommonApiResponse @@ -12,15 +11,6 @@ import roomescape.theme.docs.PublicThemeAPI class ThemeController( private val themeService: ThemeService, ) : PublicThemeAPI { - @PostMapping("/batch") - override fun findThemeInfosByIds( - @Valid @RequestBody request: ThemeIdListRequest - ): ResponseEntity> { - val response = themeService.findAllInfosByIds(request) - - return ResponseEntity.ok(CommonApiResponse(response)) - } - @GetMapping("/{id}") override fun findThemeInfoById( @PathVariable id: Long @@ -29,4 +19,13 @@ class ThemeController( return ResponseEntity.ok(CommonApiResponse(response)) } + + @GetMapping("/most-reserved") + override fun findMostReservedThemeLastWeek( + @RequestParam count: Int + ): ResponseEntity> { + val response = themeService.findMostReservedThemeLastWeek(count) + + return ResponseEntity.ok(CommonApiResponse(response)) + } } diff --git a/src/main/kotlin/roomescape/theme/web/ThemeDto.kt b/src/main/kotlin/roomescape/theme/web/ThemeDto.kt index e79601a2..9befc770 100644 --- a/src/main/kotlin/roomescape/theme/web/ThemeDto.kt +++ b/src/main/kotlin/roomescape/theme/web/ThemeDto.kt @@ -1,6 +1,6 @@ package roomescape.theme.web -import roomescape.theme.infrastructure.persistence.Difficulty +import roomescape.theme.business.domain.ThemeInfo import roomescape.theme.infrastructure.persistence.ThemeEntity data class ThemeIdListRequest( @@ -12,7 +12,7 @@ data class ThemeInfoResponse( val name: String, val thumbnailUrl: String, val description: String, - val difficulty: Difficulty, + val difficulty: String, val price: Int, val minParticipants: Short, val maxParticipants: Short, @@ -21,7 +21,7 @@ data class ThemeInfoResponse( val expectedMinutesTo: Short ) -fun ThemeEntity.toInfoResponse() = ThemeInfoResponse( +fun ThemeInfo.toInfoResponse() = ThemeInfoResponse( id = this.id, name = this.name, thumbnailUrl = this.thumbnailUrl, @@ -35,10 +35,24 @@ fun ThemeEntity.toInfoResponse() = ThemeInfoResponse( expectedMinutesTo = this.expectedMinutesTo ) +fun ThemeEntity.toInfoResponse() = ThemeInfoResponse( + id = this.id, + name = this.name, + thumbnailUrl = this.thumbnailUrl, + description = this.description, + difficulty = this.difficulty.name, + price = this.price, + minParticipants = this.minParticipants, + maxParticipants = this.maxParticipants, + availableMinutes = this.availableMinutes, + expectedMinutesFrom = this.expectedMinutesFrom, + expectedMinutesTo = this.expectedMinutesTo +) + data class ThemeInfoListResponse( val themes: List ) -fun List.toInfoListResponse() = ThemeInfoListResponse( +fun List.toListResponse() = ThemeInfoListResponse( themes = this.map { it.toInfoResponse() } )