From 1073704571392607c3a94e28d0474f0a49157f72 Mon Sep 17 00:00:00 2001 From: suyeoniii Date: Thu, 19 Sep 2024 00:13:03 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20#93=20=ED=99=88=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../popular_route/GetPopularRoutesUseCase.kt | 16 +++++ .../popular_route/dto/PopularRouteDto.kt | 6 ++ .../GetRecommendedRoutesUseCase.kt | 16 +++++ .../dto/RecommendedRouteDto.kt | 8 +++ .../controller/route/RouteHomeController.kt | 66 +++++++++++-------- .../{ => home}/GetPopularRoutesResponse.kt | 2 +- .../GetRecommendedRoutesResponse.kt | 8 +-- .../route/dto/{ => home}/PopularRouteDto.kt | 2 +- .../RecommendedRouteDto.kt} | 12 ++-- .../domain/popular_route/PopularRoute.kt | 24 +++++++ .../popular_route/PopularRouteService.kt | 20 ++++++ .../recommended_route/RecommendedRoute.kt | 31 +++++++++ .../RecommendedRouteService.kt | 20 ++++++ .../popular_route/PopularRouteRepository.kt | 18 +++++ .../RecommendedRouteRepository.kt | 24 +++++++ src/main/resources/schema.sql | 19 ++++++ 16 files changed, 253 insertions(+), 39 deletions(-) create mode 100644 src/main/kotlin/com/routebox/routebox/application/popular_route/GetPopularRoutesUseCase.kt create mode 100644 src/main/kotlin/com/routebox/routebox/application/popular_route/dto/PopularRouteDto.kt create mode 100644 src/main/kotlin/com/routebox/routebox/application/recommended_route/GetRecommendedRoutesUseCase.kt create mode 100644 src/main/kotlin/com/routebox/routebox/application/recommended_route/dto/RecommendedRouteDto.kt rename src/main/kotlin/com/routebox/routebox/controller/route/dto/{ => home}/GetPopularRoutesResponse.kt (86%) rename src/main/kotlin/com/routebox/routebox/controller/route/dto/{ => home}/GetRecommendedRoutesResponse.kt (54%) rename src/main/kotlin/com/routebox/routebox/controller/route/dto/{ => home}/PopularRouteDto.kt (86%) rename src/main/kotlin/com/routebox/routebox/controller/route/dto/{RecommendRouteDto.kt => home/RecommendedRouteDto.kt} (60%) create mode 100644 src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRoute.kt create mode 100644 src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRouteService.kt create mode 100644 src/main/kotlin/com/routebox/routebox/domain/recommended_route/RecommendedRoute.kt create mode 100644 src/main/kotlin/com/routebox/routebox/domain/recommended_route/RecommendedRouteService.kt create mode 100644 src/main/kotlin/com/routebox/routebox/infrastructure/popular_route/PopularRouteRepository.kt create mode 100644 src/main/kotlin/com/routebox/routebox/infrastructure/recommended_route/RecommendedRouteRepository.kt diff --git a/src/main/kotlin/com/routebox/routebox/application/popular_route/GetPopularRoutesUseCase.kt b/src/main/kotlin/com/routebox/routebox/application/popular_route/GetPopularRoutesUseCase.kt new file mode 100644 index 0000000..2ffb62c --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/application/popular_route/GetPopularRoutesUseCase.kt @@ -0,0 +1,16 @@ +package com.routebox.routebox.application.popular_route + +import com.routebox.routebox.application.popular_route.dto.PopularRouteDto +import com.routebox.routebox.domain.popular_route.PopularRouteService +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional + +@Component +class GetPopularRoutesUseCase( + private val popularRouteService: PopularRouteService, +) { + @Transactional(readOnly = true) + operator fun invoke(): List { + return popularRouteService.getPopularRoutes() + } +} diff --git a/src/main/kotlin/com/routebox/routebox/application/popular_route/dto/PopularRouteDto.kt b/src/main/kotlin/com/routebox/routebox/application/popular_route/dto/PopularRouteDto.kt new file mode 100644 index 0000000..8fcd135 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/application/popular_route/dto/PopularRouteDto.kt @@ -0,0 +1,6 @@ +package com.routebox.routebox.application.popular_route.dto + +data class PopularRouteDto( + val id: Long, + val name: String, +) diff --git a/src/main/kotlin/com/routebox/routebox/application/recommended_route/GetRecommendedRoutesUseCase.kt b/src/main/kotlin/com/routebox/routebox/application/recommended_route/GetRecommendedRoutesUseCase.kt new file mode 100644 index 0000000..f032d70 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/application/recommended_route/GetRecommendedRoutesUseCase.kt @@ -0,0 +1,16 @@ +package com.routebox.routebox.application.recommended_route + +import com.routebox.routebox.application.recommended_route.dto.RecommendedRouteDto +import com.routebox.routebox.domain.recommended_route.RecommendedRouteService +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional + +@Component +class GetRecommendedRoutesUseCase( + private val recommendedRouteService: RecommendedRouteService, +) { + @Transactional(readOnly = true) + operator fun invoke(): List { + return recommendedRouteService.getRecommendRoutes() + } +} diff --git a/src/main/kotlin/com/routebox/routebox/application/recommended_route/dto/RecommendedRouteDto.kt b/src/main/kotlin/com/routebox/routebox/application/recommended_route/dto/RecommendedRouteDto.kt new file mode 100644 index 0000000..a86361a --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/application/recommended_route/dto/RecommendedRouteDto.kt @@ -0,0 +1,8 @@ +package com.routebox.routebox.application.recommended_route.dto +data class RecommendedRouteDto( + val id: Long, + val name: String, + val description: String?, + val commonComment: String?, + val routeImageUrl: String?, +) diff --git a/src/main/kotlin/com/routebox/routebox/controller/route/RouteHomeController.kt b/src/main/kotlin/com/routebox/routebox/controller/route/RouteHomeController.kt index f4fc8fd..05be4f3 100644 --- a/src/main/kotlin/com/routebox/routebox/controller/route/RouteHomeController.kt +++ b/src/main/kotlin/com/routebox/routebox/controller/route/RouteHomeController.kt @@ -1,13 +1,16 @@ package com.routebox.routebox.controller.route -import com.routebox.routebox.controller.route.dto.GetPopularRoutesResponse -import com.routebox.routebox.controller.route.dto.GetRecommendedRoutesResponse -import com.routebox.routebox.controller.route.dto.PopularRouteDto -import com.routebox.routebox.controller.route.dto.RecommendRouteDto +import com.routebox.routebox.application.popular_route.GetPopularRoutesUseCase +import com.routebox.routebox.application.recommended_route.GetRecommendedRoutesUseCase +import com.routebox.routebox.controller.route.dto.home.GetPopularRoutesResponse +import com.routebox.routebox.controller.route.dto.home.GetRecommendedRoutesResponse +import com.routebox.routebox.controller.route.dto.home.PopularRouteDto +import com.routebox.routebox.controller.route.dto.home.RecommendedRouteDto +import com.routebox.routebox.security.UserPrincipal import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.responses.ApiResponses +import io.swagger.v3.oas.annotations.security.SecurityRequirement import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.validation.annotation.Validated import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping @@ -17,39 +20,48 @@ import org.springframework.web.bind.annotation.RestController @RestController @Validated @RequestMapping("/api") -class RouteHomeController { +class RouteHomeController( + private val getRecommendedRoutesUseCase: GetRecommendedRoutesUseCase, + private val getPopularRoutesUseCase: GetPopularRoutesUseCase, +) { @Operation( summary = "추천 루트 조회", description = "추천 루트 조회", - ) - @ApiResponses( - ApiResponse(responseCode = "200"), + security = [SecurityRequirement(name = "access-token")], ) @GetMapping("/v1/routes/recommend") - fun getRecommendedRoutes(): GetRecommendedRoutesResponse { - val comment = "8월엔 여기로 여행 어때요?" - val mockData = listOf( - RecommendRouteDto.from(1, "경주 200% 즐기는 법", "경주 200% 즐기는 법", "https://routebox-resources.s3.ap-northeast-2.amazonaws.com/image/1.jpg"), - RecommendRouteDto.from(2, "대구 먹방 여행", "대구 200% 즐기는 법", "https://routebox-resources.s3.ap-northeast-2.amazonaws.com/image/2.jpg"), - RecommendRouteDto.from(3, "대전 빵 여행", "대전 200% 즐기는 법", "https://routebox-resources.s3.ap-northeast-2.amazonaws.com/image/3.jpg"), + fun getRecommendedRoutes( + @AuthenticationPrincipal userPrincipal: UserPrincipal, + ): GetRecommendedRoutesResponse { + val routes = getRecommendedRoutesUseCase() + return GetRecommendedRoutesResponse.from( + comment = routes.firstOrNull()?.commonComment, + routes = routes.map { + RecommendedRouteDto.from( + it.id, + it.name, + it.description, + it.routeImageUrl, + ) + }, ) - return GetRecommendedRoutesResponse.from(comment, mockData) } @Operation( summary = "인기 루트 조회", description = "인기 루트 조회", - ) - @ApiResponses( - ApiResponse(responseCode = "200"), + security = [SecurityRequirement(name = "access-token")], ) @GetMapping("/v1/routes/popular") - fun getPopularRoutes(): GetPopularRoutesResponse { - val mockData = listOf( - PopularRouteDto(1, "8월에 꼭 가야하는 장소"), - PopularRouteDto(2, "여자친구에게 칭찬 왕창 받은 데이트 코스"), - PopularRouteDto(3, "친구들과 함께 떠나고 싶은 여행지"), - ) - return GetPopularRoutesResponse.from(mockData) + fun getPopularRoutes( + @AuthenticationPrincipal userPrincipal: UserPrincipal, + ): GetPopularRoutesResponse { + val routes = getPopularRoutesUseCase().map { + PopularRouteDto.from( + it.id, + it.name, + ) + } + return GetPopularRoutesResponse.from(routes) } } diff --git a/src/main/kotlin/com/routebox/routebox/controller/route/dto/GetPopularRoutesResponse.kt b/src/main/kotlin/com/routebox/routebox/controller/route/dto/home/GetPopularRoutesResponse.kt similarity index 86% rename from src/main/kotlin/com/routebox/routebox/controller/route/dto/GetPopularRoutesResponse.kt rename to src/main/kotlin/com/routebox/routebox/controller/route/dto/home/GetPopularRoutesResponse.kt index d9a5a39..c19ee3a 100644 --- a/src/main/kotlin/com/routebox/routebox/controller/route/dto/GetPopularRoutesResponse.kt +++ b/src/main/kotlin/com/routebox/routebox/controller/route/dto/home/GetPopularRoutesResponse.kt @@ -1,4 +1,4 @@ -package com.routebox.routebox.controller.route.dto +package com.routebox.routebox.controller.route.dto.home import io.swagger.v3.oas.annotations.media.Schema diff --git a/src/main/kotlin/com/routebox/routebox/controller/route/dto/GetRecommendedRoutesResponse.kt b/src/main/kotlin/com/routebox/routebox/controller/route/dto/home/GetRecommendedRoutesResponse.kt similarity index 54% rename from src/main/kotlin/com/routebox/routebox/controller/route/dto/GetRecommendedRoutesResponse.kt rename to src/main/kotlin/com/routebox/routebox/controller/route/dto/home/GetRecommendedRoutesResponse.kt index 013cdcf..b6441d9 100644 --- a/src/main/kotlin/com/routebox/routebox/controller/route/dto/GetRecommendedRoutesResponse.kt +++ b/src/main/kotlin/com/routebox/routebox/controller/route/dto/home/GetRecommendedRoutesResponse.kt @@ -1,16 +1,16 @@ -package com.routebox.routebox.controller.route.dto +package com.routebox.routebox.controller.route.dto.home import io.swagger.v3.oas.annotations.media.Schema data class GetRecommendedRoutesResponse( @Schema(description = "오늘의 추천루트 문구") - val comment: String, + val comment: String?, @Schema(description = "추천 루트 목록") - val routes: List, + val routes: List, ) { companion object { - fun from(comment: String, routes: List): GetRecommendedRoutesResponse = GetRecommendedRoutesResponse( + fun from(comment: String?, routes: List): GetRecommendedRoutesResponse = GetRecommendedRoutesResponse( comment = comment, routes = routes, ) diff --git a/src/main/kotlin/com/routebox/routebox/controller/route/dto/PopularRouteDto.kt b/src/main/kotlin/com/routebox/routebox/controller/route/dto/home/PopularRouteDto.kt similarity index 86% rename from src/main/kotlin/com/routebox/routebox/controller/route/dto/PopularRouteDto.kt rename to src/main/kotlin/com/routebox/routebox/controller/route/dto/home/PopularRouteDto.kt index beabcbe..3a6e800 100644 --- a/src/main/kotlin/com/routebox/routebox/controller/route/dto/PopularRouteDto.kt +++ b/src/main/kotlin/com/routebox/routebox/controller/route/dto/home/PopularRouteDto.kt @@ -1,4 +1,4 @@ -package com.routebox.routebox.controller.route.dto +package com.routebox.routebox.controller.route.dto.home import io.swagger.v3.oas.annotations.media.Schema diff --git a/src/main/kotlin/com/routebox/routebox/controller/route/dto/RecommendRouteDto.kt b/src/main/kotlin/com/routebox/routebox/controller/route/dto/home/RecommendedRouteDto.kt similarity index 60% rename from src/main/kotlin/com/routebox/routebox/controller/route/dto/RecommendRouteDto.kt rename to src/main/kotlin/com/routebox/routebox/controller/route/dto/home/RecommendedRouteDto.kt index 3869b8b..dfc9384 100644 --- a/src/main/kotlin/com/routebox/routebox/controller/route/dto/RecommendRouteDto.kt +++ b/src/main/kotlin/com/routebox/routebox/controller/route/dto/home/RecommendedRouteDto.kt @@ -1,8 +1,8 @@ -package com.routebox.routebox.controller.route.dto +package com.routebox.routebox.controller.route.dto.home import io.swagger.v3.oas.annotations.media.Schema -data class RecommendRouteDto( +data class RecommendedRouteDto( @Schema(description = "루트 ID") val id: Long, @@ -10,17 +10,17 @@ data class RecommendRouteDto( val routeName: String, @Schema(description = "루트 설명") - val routeDescription: String, + val routeDescription: String?, @Schema(description = "루트 대표 이미지") - val routeImageUrl: String, + val routeImageUrl: String?, ) { companion object { - fun from(id: Long, routeName: String, routeDescription: String, routeImageUrl: String): RecommendRouteDto = RecommendRouteDto( + fun from(id: Long, routeName: String, routeDescription: String?, routeImageUrl: String?): RecommendedRouteDto = RecommendedRouteDto( id = id, routeName = routeName, routeDescription = routeDescription, - routeImageUrl = routeImageUrl, + routeImageUrl = if (routeImageUrl.isNullOrEmpty()) null else routeImageUrl, ) } } diff --git a/src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRoute.kt b/src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRoute.kt new file mode 100644 index 0000000..14e3397 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRoute.kt @@ -0,0 +1,24 @@ +package com.routebox.routebox.domain.popular_route + +import com.routebox.routebox.domain.common.TimeTrackedBaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table + +@Table(name = "popular_routes") +@Entity +class PopularRoute( + id: Long = 0, + routeId: Long, +) : TimeTrackedBaseEntity() { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "popular_route_id") + val id: Long = id + + val routeId: Long = routeId +} diff --git a/src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRouteService.kt b/src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRouteService.kt new file mode 100644 index 0000000..877ab53 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/domain/popular_route/PopularRouteService.kt @@ -0,0 +1,20 @@ +package com.routebox.routebox.domain.popular_route + +import com.routebox.routebox.application.popular_route.dto.PopularRouteDto +import com.routebox.routebox.infrastructure.popular_route.PopularRouteRepository +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class PopularRouteService( + private val popularRouteRepository: PopularRouteRepository, +) { + + /** + * 인기 루트 조회 + */ + @Transactional(readOnly = true) + fun getPopularRoutes(): List { + return popularRouteRepository.findAllPopularRoutes() + } +} diff --git a/src/main/kotlin/com/routebox/routebox/domain/recommended_route/RecommendedRoute.kt b/src/main/kotlin/com/routebox/routebox/domain/recommended_route/RecommendedRoute.kt new file mode 100644 index 0000000..aa6221e --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/domain/recommended_route/RecommendedRoute.kt @@ -0,0 +1,31 @@ +package com.routebox.routebox.domain.recommended_route + +import com.routebox.routebox.domain.common.TimeTrackedBaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import java.time.LocalDateTime + +@Table(name = "recommended_routes") +@Entity +class RecommendedRoute( + id: Long = 0, + routeId: Long, + showFrom: LocalDateTime, + commonComment: String? = null, +) : TimeTrackedBaseEntity() { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "recommended_route_id") + val id: Long = id + + val routeId: Long = routeId + + val showFrom: LocalDateTime = showFrom + + val commonComment: String? = commonComment +} diff --git a/src/main/kotlin/com/routebox/routebox/domain/recommended_route/RecommendedRouteService.kt b/src/main/kotlin/com/routebox/routebox/domain/recommended_route/RecommendedRouteService.kt new file mode 100644 index 0000000..b30a8f8 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/domain/recommended_route/RecommendedRouteService.kt @@ -0,0 +1,20 @@ +package com.routebox.routebox.domain.recommended_route + +import com.routebox.routebox.application.recommended_route.dto.RecommendedRouteDto +import com.routebox.routebox.infrastructure.recommended_route.RecommendedRouteRepository +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class RecommendedRouteService( + private val recommendedRouteRepository: RecommendedRouteRepository, +) { + + /** + * 추천 루트 조회 + */ + @Transactional(readOnly = true) + fun getRecommendRoutes(): List { + return recommendedRouteRepository.findAllRecommendedRoutes() + } +} diff --git a/src/main/kotlin/com/routebox/routebox/infrastructure/popular_route/PopularRouteRepository.kt b/src/main/kotlin/com/routebox/routebox/infrastructure/popular_route/PopularRouteRepository.kt new file mode 100644 index 0000000..c2a3a08 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/infrastructure/popular_route/PopularRouteRepository.kt @@ -0,0 +1,18 @@ +package com.routebox.routebox.infrastructure.popular_route + +import com.routebox.routebox.application.popular_route.dto.PopularRouteDto +import com.routebox.routebox.domain.popular_route.PopularRoute +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query + +interface PopularRouteRepository : JpaRepository { + @Query( + """ + SELECT NEW com.routebox.routebox.application.popular_route.dto.PopularRouteDto(r.id, r.name) + FROM PopularRoute pr + JOIN Route r ON pr.routeId = r.id + WHERE r.isPublic = true + """, + ) + fun findAllPopularRoutes(): List +} diff --git a/src/main/kotlin/com/routebox/routebox/infrastructure/recommended_route/RecommendedRouteRepository.kt b/src/main/kotlin/com/routebox/routebox/infrastructure/recommended_route/RecommendedRouteRepository.kt new file mode 100644 index 0000000..fdaf9e6 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/infrastructure/recommended_route/RecommendedRouteRepository.kt @@ -0,0 +1,24 @@ +package com.routebox.routebox.infrastructure.recommended_route + +import com.routebox.routebox.application.recommended_route.dto.RecommendedRouteDto +import com.routebox.routebox.domain.recommended_route.RecommendedRoute +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query + +interface RecommendedRouteRepository : JpaRepository { + @Query( + """ + SELECT NEW com.routebox.routebox.application.recommended_route.dto.RecommendedRouteDto( + r.id, r.name, r.description, rr.commonComment, + CASE WHEN rai.fileUrl IS NOT NULL THEN rai.fileUrl ELSE "" END + ) + FROM RecommendedRoute rr + JOIN Route r ON rr.routeId = r.id + LEFT JOIN RouteActivity ra ON ra.route = r + LEFT JOIN RouteActivityImage rai ON rai.activity = ra + WHERE r.isPublic = true AND rr.showFrom <= CURRENT_TIMESTAMP + GROUP BY r.id + """, + ) + fun findAllRecommendedRoutes(): List +} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index bfb43c2..b335e28 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -250,3 +250,22 @@ CREATE TABLE withdrawal_histories created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성 시간', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '업데이트 시간' ); + +CREATE TABLE popular_routes +( + popular_route_id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '인기 루트 ID', + route_id BIGINT NOT NULL COMMENT '루트 ID', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성 시간', + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '업데이트 시간' +); + +CREATE TABLE recommended_routes +( + recommended_route_id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '추천 루트 ID', + route_id BIGINT NOT NULL COMMENT '루트 ID', + show_from DATETIME NOT NULL COMMENT '표시 시작 시간', + common_comment VARCHAR(255) NULL COMMENT '추천 루트 상단에 나오는 공통 코멘트, 가장 앞쪽 데이터의 코멘트가 노출됨', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성 시간', + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '업데이트 시간' +); +