From 0e03018852ed74a12f10e0a06e01eaf552c5d93c Mon Sep 17 00:00:00 2001 From: leehaneum Date: Fri, 27 Mar 2026 14:58:11 +0900 Subject: [PATCH 1/7] refactor: Remove flowerSpotId from FlowerSpotCafe domain and storage Co-Authored-By: Claude Opus 4.6 --- .../src/main/kotlin/com/pida/flowerspot/FlowerSpotCafe.kt | 1 - .../kotlin/com/pida/flowerspot/FlowerSpotCafeFinder.kt | 3 --- .../com/pida/flowerspot/FlowerSpotCafeRepository.kt | 2 -- .../main/kotlin/com/pida/flowerspot/NewFlowerSpotCafe.kt | 2 -- .../db/core/flowerspot/FlowerSpotCafeCoreRepository.kt | 8 -------- .../storage/db/core/flowerspot/FlowerSpotCafeEntity.kt | 8 +------- .../db/core/flowerspot/FlowerSpotCafeJpaRepository.kt | 2 -- 7 files changed, 1 insertion(+), 25 deletions(-) diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafe.kt b/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafe.kt index ff06691..3c1cceb 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafe.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafe.kt @@ -6,7 +6,6 @@ import java.time.LocalDateTime data class FlowerSpotCafe( val id: Long, - val flowerSpotId: Long, val name: String, val address: String?, val description: String?, diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafeFinder.kt b/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafeFinder.kt index 96ed043..e7b1032 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafeFinder.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafeFinder.kt @@ -11,7 +11,4 @@ class FlowerSpotCafeFinder( suspend fun readAll(): List = flowerSpotCafeRepository.findAll() suspend fun readAllByLocation(location: FlowerSpotLocation): List = flowerSpotCafeRepository.findAllByLocation(location) - - suspend fun readAllByFlowerSpotId(flowerSpotId: Long): List = - flowerSpotCafeRepository.findAllByFlowerSpotId(flowerSpotId) } diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafeRepository.kt b/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafeRepository.kt index 99db624..b6cb101 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafeRepository.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafeRepository.kt @@ -7,8 +7,6 @@ interface FlowerSpotCafeRepository { suspend fun findAllByLocation(location: FlowerSpotLocation): List - suspend fun findAllByFlowerSpotId(flowerSpotId: Long): List - suspend fun save(cafe: FlowerSpotCafe): FlowerSpotCafe suspend fun updateThumbnailUrl( diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/NewFlowerSpotCafe.kt b/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/NewFlowerSpotCafe.kt index 2bb7dfd..92ac89b 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/NewFlowerSpotCafe.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/NewFlowerSpotCafe.kt @@ -4,7 +4,6 @@ import com.pida.support.geo.GeoJson import com.pida.support.geo.Region data class NewFlowerSpotCafe( - val flowerSpotId: Long, val name: String, val address: String?, val description: String?, @@ -16,7 +15,6 @@ data class NewFlowerSpotCafe( fun toDomain(): FlowerSpotCafe = FlowerSpotCafe( id = 0, - flowerSpotId = flowerSpotId, name = name, address = address, description = description, diff --git a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeCoreRepository.kt b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeCoreRepository.kt index 07ea8bd..9d45f9c 100644 --- a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeCoreRepository.kt +++ b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeCoreRepository.kt @@ -47,19 +47,11 @@ class FlowerSpotCafeCoreRepository( ).map { it.toFlowerSpotCafe() } } - override suspend fun findAllByFlowerSpotId(flowerSpotId: Long): List = - tx.reader.coExecute { - flowerSpotCafeJpaRepository - .findByFlowerSpotIdAndDeletedAtIsNull(flowerSpotId) - .map { it.toFlowerSpotCafe() } - } - override suspend fun save(cafe: FlowerSpotCafe): FlowerSpotCafe = tx.writer.coExecute { val point = cafe.pinPoint as GeoJson.Point val entity = FlowerSpotCafeEntity( - flowerSpotId = cafe.flowerSpotId, name = cafe.name, address = cafe.address, description = cafe.description, diff --git a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeEntity.kt b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeEntity.kt index 48a1628..34d7b02 100644 --- a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeEntity.kt +++ b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeEntity.kt @@ -8,17 +8,12 @@ import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated -import jakarta.persistence.Index import jakarta.persistence.Table import org.locationtech.jts.geom.Point @Entity -@Table( - name = "t_flower_spot_cafe", - indexes = [Index(name = "idx_flower_spot_cafe_flower_spot_id_deleted_at", columnList = "flower_spot_id,deleted_at")], -) +@Table(name = "t_flower_spot_cafe") class FlowerSpotCafeEntity( - val flowerSpotId: Long, val name: String, val address: String?, val description: String?, @@ -33,7 +28,6 @@ class FlowerSpotCafeEntity( fun toFlowerSpotCafe(): FlowerSpotCafe = FlowerSpotCafe( id = id!!, - flowerSpotId = flowerSpotId, name = name, address = address, description = description, diff --git a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeJpaRepository.kt b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeJpaRepository.kt index 75db1cb..0b2f106 100644 --- a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeJpaRepository.kt +++ b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeJpaRepository.kt @@ -26,6 +26,4 @@ interface FlowerSpotCafeJpaRepository : JpaRepository - - fun findByFlowerSpotIdAndDeletedAtIsNull(flowerSpotId: Long): List } From bae36efbdab1fc053010736bb9b790f1f107c6d7 Mon Sep 17 00:00:00 2001 From: leehaneum Date: Fri, 27 Mar 2026 14:58:31 +0900 Subject: [PATCH 2/7] refactor: Remove flowerSpotId from FlowerSpotCafe API layer Co-Authored-By: Claude Opus 4.6 --- .../detail/CafeCategoryItemDetailPayloadResponse.kt | 6 ------ .../v2/category/response/item/MapCategoryItemResponse.kt | 2 +- .../flowerspotcafe/request/FlowerSpotCafeCreateRequest.kt | 3 --- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/CafeCategoryItemDetailPayloadResponse.kt b/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/CafeCategoryItemDetailPayloadResponse.kt index 8913e99..256d4d4 100644 --- a/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/CafeCategoryItemDetailPayloadResponse.kt +++ b/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/CafeCategoryItemDetailPayloadResponse.kt @@ -16,12 +16,6 @@ data class CafeCategoryItemDetailPayloadResponse( requiredMode = Schema.RequiredMode.NOT_REQUIRED, ) val mapUrl: String?, - @field:Schema( - description = "연결된 벚꽃길 ID", - example = "15", - requiredMode = Schema.RequiredMode.NOT_REQUIRED, - ) - val flowerSpotId: Long?, @field:Schema( description = "최근 방문 횟수", example = "12", diff --git a/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/item/MapCategoryItemResponse.kt b/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/item/MapCategoryItemResponse.kt index be939ba..0e08809 100644 --- a/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/item/MapCategoryItemResponse.kt +++ b/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/item/MapCategoryItemResponse.kt @@ -79,7 +79,7 @@ data class MapCategoryItemResponse( ) val endDate: LocalDate?, @field:Schema( - description = "벚꽃길 ID (CAFE인 경우에만 포함)", + description = "벚꽃길 ID (FLOWER_SPOT인 경우에만 포함)", example = "15", requiredMode = Schema.RequiredMode.NOT_REQUIRED, ) diff --git a/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/flowerspotcafe/request/FlowerSpotCafeCreateRequest.kt b/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/flowerspotcafe/request/FlowerSpotCafeCreateRequest.kt index 8de89ad..8ece0d5 100644 --- a/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/flowerspotcafe/request/FlowerSpotCafeCreateRequest.kt +++ b/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/flowerspotcafe/request/FlowerSpotCafeCreateRequest.kt @@ -6,8 +6,6 @@ import io.swagger.v3.oas.annotations.media.Schema @Schema(description = "꽃 명소 카페 생성 요청") data class FlowerSpotCafeCreateRequest( - @Schema(description = "꽃 명소 ID", example = "1") - val flowerSpotId: Long, @Schema(description = "카페 이름", example = "벚꽃 카페") val name: String, @Schema(description = "주소", example = "서울특별시 영등포구 여의도동", required = false) @@ -33,7 +31,6 @@ data class FlowerSpotCafeCreateRequest( fun toNewFlowerSpotCafe(): NewFlowerSpotCafe = NewFlowerSpotCafe( - flowerSpotId = flowerSpotId, name = name, address = address, description = description, From c34139c831525f803d7c280db3307ed96b0e6662 Mon Sep 17 00:00:00 2001 From: leehaneum Date: Fri, 27 Mar 2026 14:58:35 +0900 Subject: [PATCH 3/7] feat: Add flowerSpotCafeId to Blooming domain model Co-Authored-By: Claude Opus 4.6 --- .../src/main/kotlin/com/pida/blooming/Blooming.kt | 4 +++- .../src/main/kotlin/com/pida/blooming/NewBlooming.kt | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/blooming/Blooming.kt b/pida-core/core-domain/src/main/kotlin/com/pida/blooming/Blooming.kt index 31c3f21..40842b4 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/blooming/Blooming.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/blooming/Blooming.kt @@ -8,9 +8,11 @@ data class Blooming( val userId: Long, val flowerSpotId: Long?, val flowerEventId: Long?, + val flowerSpotCafeId: Long?, val createdAt: LocalDateTime, ) { init { - require((flowerSpotId == null) != (flowerEventId == null)) + val nonNullCount = listOfNotNull(flowerSpotId, flowerEventId, flowerSpotCafeId).size + require(nonNullCount == 1) } } diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/blooming/NewBlooming.kt b/pida-core/core-domain/src/main/kotlin/com/pida/blooming/NewBlooming.kt index 2d57c36..ed21e58 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/blooming/NewBlooming.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/blooming/NewBlooming.kt @@ -5,6 +5,7 @@ sealed class NewBlooming { abstract val status: BloomingStatus abstract val flowerSpotId: Long? abstract val flowerEventId: Long? + abstract val flowerSpotCafeId: Long? data class FlowerSpot( override val userId: Long, @@ -12,6 +13,7 @@ sealed class NewBlooming { override val status: BloomingStatus, ) : NewBlooming() { override val flowerEventId: Long? = null + override val flowerSpotCafeId: Long? = null } data class FlowerEvent( @@ -20,5 +22,15 @@ sealed class NewBlooming { override val status: BloomingStatus, ) : NewBlooming() { override val flowerSpotId: Long? = null + override val flowerSpotCafeId: Long? = null + } + + data class FlowerSpotCafe( + override val userId: Long, + override val flowerSpotCafeId: Long, + override val status: BloomingStatus, + ) : NewBlooming() { + override val flowerSpotId: Long? = null + override val flowerEventId: Long? = null } } From fcc3c8d47b7a05b45d4cd809c654694d79c3c732 Mon Sep 17 00:00:00 2001 From: leehaneum Date: Fri, 27 Mar 2026 14:58:52 +0900 Subject: [PATCH 4/7] feat: Add flowerSpotCafeId blooming query support Co-Authored-By: Claude Opus 4.6 --- .../com/pida/blooming/BloomingFacade.kt | 23 +++++++- .../com/pida/blooming/BloomingFinder.kt | 14 +++++ .../com/pida/blooming/BloomingRepository.kt | 14 +++++ .../com/pida/blooming/BloomingService.kt | 35 +++++++++--- ...oomedFirstVoteNotificationEventListener.kt | 3 + .../core/blooming/BloomingCoreRepository.kt | 36 ++++++++++++ .../core/blooming/BloomingCustomRepository.kt | 56 +++++++++++++++++++ .../db/core/blooming/BloomingEntity.kt | 2 + .../db/core/blooming/BloomingJpaRepository.kt | 5 ++ 9 files changed, 179 insertions(+), 9 deletions(-) diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFacade.kt b/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFacade.kt index fe5b8b1..4b8945e 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFacade.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFacade.kt @@ -74,13 +74,31 @@ class BloomingFacade( suspend fun readBloomingDetails( flowerSpotId: Long? = null, flowerEventId: Long? = null, + flowerSpotCafeId: Long? = null, ): BloomingDetails = when { - flowerSpotId != null && flowerEventId == null -> readBloomingDetailsBySpotId(flowerSpotId) - flowerSpotId == null && flowerEventId != null -> readBloomingDetailsByEventId(flowerEventId) + flowerSpotId != null && flowerEventId == null && flowerSpotCafeId == null -> readBloomingDetailsBySpotId(flowerSpotId) + flowerSpotId == null && flowerEventId != null && flowerSpotCafeId == null -> readBloomingDetailsByEventId(flowerEventId) + flowerSpotId == null && flowerEventId == null && flowerSpotCafeId != null -> readBloomingDetailsByCafeId(flowerSpotCafeId) else -> throw ErrorException(ErrorType.INVALID_REQUEST) } + private suspend fun readBloomingDetailsByCafeId(flowerSpotCafeId: Long): BloomingDetails = + coroutineScope { + val bloomings = bloomingService.recentlyBloomingByCafeId(flowerSpotCafeId) + val latestBlooming = bloomings.maxByOrNull { it.createdAt } + val userProfileDeferred = + async { + latestBlooming?.userId?.let { userService.getProfile(it) } + } + + return@coroutineScope buildBloomingDetails( + bloomings = bloomings, + nickname = userProfileDeferred.await()?.nickname, + updatedAt = latestBlooming?.createdAt, + ) + } + private suspend fun readBloomingDetailsByEventId(flowerEventId: Long): BloomingDetails = coroutineScope { val bloomings = bloomingService.recentlyBloomingByEventId(flowerEventId) @@ -105,6 +123,7 @@ class BloomingFacade( when (newBlooming) { is NewBlooming.FlowerSpot -> ImagePrefix.FLOWERSPOT.value to newBlooming.flowerSpotId is NewBlooming.FlowerEvent -> ImagePrefix.FLOWEREVENT.value to newBlooming.flowerEventId + is NewBlooming.FlowerSpotCafe -> ImagePrefix.FLOWERSPOT.value to newBlooming.flowerSpotCafeId } return BloomingImageUploadUrl.from( diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFinder.kt b/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFinder.kt index 83e59b5..8a4d795 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFinder.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFinder.kt @@ -16,6 +16,11 @@ class BloomingFinder( flowerEventId: Long, ): Blooming? = bloomingRepository.findTopByUserIdAndEventIdDesc(userId, flowerEventId) + suspend fun readTopByUserIdAndFlowerSpotCafeIdDesc( + userId: Long, + flowerSpotCafeId: Long, + ): Blooming? = bloomingRepository.findTopByUserIdAndCafeIdDesc(userId, flowerSpotCafeId) + suspend fun readAllByUserId(userId: Long): List = bloomingRepository.findAllByUserId(userId) suspend fun readAllByFlowerSpotId(flowerSpotId: Long): List = bloomingRepository.findAllByFlowerSpotId(flowerSpotId) @@ -24,10 +29,14 @@ class BloomingFinder( suspend fun readRecentlyBloomingByEventId(eventId: Long): List = bloomingRepository.findRecentlyByEventId(eventId) + suspend fun readRecentlyBloomingByCafeId(cafeId: Long): List = bloomingRepository.findRecentlyByCafeId(cafeId) + fun recentlyBloomingBySpotIds(spotIds: List): List = bloomingRepository.findRecentBySpotIds(spotIds) fun recentlyBloomingByEventIds(eventIds: List): List = bloomingRepository.findRecentByEventIds(eventIds) + fun recentlyBloomingByCafeIds(cafeIds: List): List = bloomingRepository.findRecentByCafeIds(cafeIds) + fun readTodayBloomingByUserId( userId: Long, flowerSpotId: Long, @@ -37,4 +46,9 @@ class BloomingFinder( userId: Long, flowerEventId: Long, ): Blooming? = bloomingRepository.findTodayEventBloomingByUserId(userId, flowerEventId) + + fun readTodayBloomingByUserIdAndFlowerSpotCafeId( + userId: Long, + flowerSpotCafeId: Long, + ): Blooming? = bloomingRepository.findTodayCafeBloomingByUserId(userId, flowerSpotCafeId) } diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingRepository.kt b/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingRepository.kt index a7adb97..3be0b09 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingRepository.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingRepository.kt @@ -16,6 +16,11 @@ interface BloomingRepository { flowerEventId: Long, ): Blooming? + suspend fun findTopByUserIdAndCafeIdDesc( + userId: Long, + flowerSpotCafeId: Long, + ): Blooming? + suspend fun findAllByUserId(userId: Long): List suspend fun findAllByFlowerSpotId(flowerSpotId: Long): List @@ -24,10 +29,14 @@ interface BloomingRepository { suspend fun findRecentlyByEventId(eventId: Long): List + suspend fun findRecentlyByCafeId(cafeId: Long): List + fun findRecentBySpotIds(spotIds: List): List fun findRecentByEventIds(eventIds: List): List + fun findRecentByCafeIds(cafeIds: List): List + fun findTodayBloomingByUserId( userId: Long, flowerSpotId: Long, @@ -38,6 +47,11 @@ interface BloomingRepository { flowerEventId: Long, ): Blooming? + fun findTodayCafeBloomingByUserId( + userId: Long, + flowerSpotCafeId: Long, + ): Blooming? + fun findBloomedSpotIdsByFlowerSpotIds(spotIds: List): List fun findBloomedEventIdsByFlowerEventIds(eventIds: List): List diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingService.kt b/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingService.kt index ff23f81..e271c08 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingService.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingService.kt @@ -17,6 +17,7 @@ class BloomingService( companion object { const val BLOOMING_SPOT_KEY = "blooming:spot" const val BLOOMING_EVENT_KEY = "blooming:event" + const val BLOOMING_CAFE_KEY = "blooming:cafe" const val BLOOMING_TTL = 30L } @@ -29,6 +30,11 @@ class BloomingService( newBlooming.userId, newBlooming.flowerEventId, ) + is NewBlooming.FlowerSpotCafe -> + bloomingFinder.readTopByUserIdAndFlowerSpotCafeIdDesc( + newBlooming.userId, + newBlooming.flowerSpotCafeId, + ) } bloomingValidator.addValidate(blooming) @@ -41,12 +47,17 @@ class BloomingService( suspend fun recentlyBloomingByEventId(eventId: Long): List = bloomingFinder.readRecentlyBloomingByEventId(eventId) + suspend fun recentlyBloomingByCafeId(cafeId: Long): List = bloomingFinder.readRecentlyBloomingByCafeId(cafeId) + fun recentlyBloomingBySpotIds(spotIds: List): List = spotIds.flatMap { spotId -> cachedRecentlyBloomingBySpotId(spotId) } fun recentlyBloomingByEventIds(eventIds: List): List = eventIds.flatMap { eventId -> cachedRecentlyBloomingByEventId(eventId) } + fun recentlyBloomingByCafeIds(cafeIds: List): List = + cafeIds.flatMap { cafeId -> cachedRecentlyBloomingByCafeId(cafeId) } + private fun cachedRecentlyBloomingBySpotId(spotId: Long): List = Cache.cacheBlocking( ttl = BLOOMING_TTL, @@ -65,20 +76,29 @@ class BloomingService( bloomingFinder.recentlyBloomingByEventIds(listOf(eventId)) } + private fun cachedRecentlyBloomingByCafeId(cafeId: Long): List = + Cache.cacheBlocking( + ttl = BLOOMING_TTL, + key = "$BLOOMING_CAFE_KEY:$cafeId", + typeReference = object : TypeReference>() {}, + ) { + bloomingFinder.recentlyBloomingByCafeIds(listOf(cafeId)) + } + fun verifyTodayBlooming( userId: Long, flowerSpotId: Long? = null, flowerEventId: Long? = null, + flowerSpotCafeId: Long? = null, ): Boolean { val blooming = when { - flowerSpotId != null && flowerEventId == null -> bloomingFinder.readTodayBloomingByUserId(userId, flowerSpotId) - flowerSpotId == null && flowerEventId != null -> - bloomingFinder.readTodayBloomingByUserIdAndFlowerEventId( - userId, - flowerEventId, - ) - + flowerSpotId != null && flowerEventId == null && flowerSpotCafeId == null -> + bloomingFinder.readTodayBloomingByUserId(userId, flowerSpotId) + flowerSpotId == null && flowerEventId != null && flowerSpotCafeId == null -> + bloomingFinder.readTodayBloomingByUserIdAndFlowerEventId(userId, flowerEventId) + flowerSpotId == null && flowerEventId == null && flowerSpotCafeId != null -> + bloomingFinder.readTodayBloomingByUserIdAndFlowerSpotCafeId(userId, flowerSpotCafeId) else -> throw ErrorException(ErrorType.INVALID_REQUEST) } @@ -89,6 +109,7 @@ class BloomingService( when (newBlooming) { is NewBlooming.FlowerSpot -> cacheRepository.delete("$BLOOMING_SPOT_KEY:${newBlooming.flowerSpotId}") is NewBlooming.FlowerEvent -> cacheRepository.delete("$BLOOMING_EVENT_KEY:${newBlooming.flowerEventId}") + is NewBlooming.FlowerSpotCafe -> cacheRepository.delete("$BLOOMING_CAFE_KEY:${newBlooming.flowerSpotCafeId}") } } } diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListener.kt b/pida-core/core-domain/src/main/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListener.kt index c121913..21597f0 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListener.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListener.kt @@ -4,6 +4,7 @@ import com.pida.blooming.BloomingAddedEvent import com.pida.blooming.BloomingStatus import com.pida.blooming.NewBlooming import com.pida.flowerevent.FlowerEventRepository +import com.pida.flowerspot.FlowerSpotCafeRepository import com.pida.flowerspot.FlowerSpotRepository import com.pida.support.extension.logger import com.pida.support.geo.Region @@ -19,6 +20,7 @@ import org.springframework.stereotype.Service class BloomedFirstVoteNotificationEventListener( private val flowerSpotRepository: FlowerSpotRepository, private val flowerEventRepository: FlowerEventRepository, + private val flowerSpotCafeRepository: FlowerSpotCafeRepository, private val firstVoteChecker: BloomedFirstVoteChecker, private val bloomedNotificationService: BloomedNotificationService, ) { @@ -48,6 +50,7 @@ class BloomedFirstVoteNotificationEventListener( when (newBlooming) { is NewBlooming.FlowerSpot -> flowerSpotRepository.findBy(newBlooming.flowerSpotId).region is NewBlooming.FlowerEvent -> flowerEventRepository.findBy(newBlooming.flowerEventId).region + is NewBlooming.FlowerSpotCafe -> flowerSpotCafeRepository.findBy(newBlooming.flowerSpotCafeId).region } } }.onFailure { error -> diff --git a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCoreRepository.kt b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCoreRepository.kt index e4b04db..424f7e6 100644 --- a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCoreRepository.kt +++ b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCoreRepository.kt @@ -23,6 +23,7 @@ class BloomingCoreRepository( userId = newBlooming.userId, flowerSpotId = newBlooming.flowerSpotId, flowerEventId = null, + flowerSpotCafeId = null, status = newBlooming.status, ) is NewBlooming.FlowerEvent -> @@ -30,6 +31,15 @@ class BloomingCoreRepository( userId = newBlooming.userId, flowerSpotId = null, flowerEventId = newBlooming.flowerEventId, + flowerSpotCafeId = null, + status = newBlooming.status, + ) + is NewBlooming.FlowerSpotCafe -> + BloomingEntity( + userId = newBlooming.userId, + flowerSpotId = null, + flowerEventId = null, + flowerSpotCafeId = newBlooming.flowerSpotCafeId, status = newBlooming.status, ) } @@ -52,6 +62,14 @@ class BloomingCoreRepository( bloomingJpaRepository.findTopByUserIdAndFlowerEventIdOrderByCreatedAtDesc(userId, flowerEventId)?.toBlooming() } + override suspend fun findTopByUserIdAndCafeIdDesc( + userId: Long, + flowerSpotCafeId: Long, + ): Blooming? = + Tx.readable { + bloomingJpaRepository.findTopByUserIdAndFlowerSpotCafeIdOrderByCreatedAtDesc(userId, flowerSpotCafeId)?.toBlooming() + } + override suspend fun findAllByUserId(userId: Long): List = Tx.readable { bloomingJpaRepository.findAllByUserId(userId).map { it.toBlooming() } @@ -72,6 +90,11 @@ class BloomingCoreRepository( bloomingCustomRepository.recentlyByEventId(eventId).map { it.toBlooming() } } + override suspend fun findRecentlyByCafeId(cafeId: Long): List = + Tx.coReadable { + bloomingCustomRepository.recentlyByCafeId(cafeId).map { it.toBlooming() } + } + override fun findRecentBySpotIds(spotIds: List): List = Tx.readable { bloomingCustomRepository.recentlyBySpotIds(spotIds).map { it.toBlooming() } @@ -82,6 +105,11 @@ class BloomingCoreRepository( bloomingCustomRepository.recentlyByEventIds(eventIds).map { it.toBlooming() } } + override fun findRecentByCafeIds(cafeIds: List): List = + Tx.readable { + bloomingCustomRepository.recentlyByCafeIds(cafeIds).map { it.toBlooming() } + } + override fun findTodayBloomingByUserId( userId: Long, flowerSpotId: Long, @@ -98,6 +126,14 @@ class BloomingCoreRepository( bloomingCustomRepository.findTodayEventBloomingByUserId(userId, flowerEventId)?.toBlooming() } + override fun findTodayCafeBloomingByUserId( + userId: Long, + flowerSpotCafeId: Long, + ): Blooming? = + Tx.readable { + bloomingCustomRepository.findTodayCafeBloomingByUserId(userId, flowerSpotCafeId)?.toBlooming() + } + override fun findBloomedSpotIdsByFlowerSpotIds(spotIds: List): List = Tx.readable { bloomingCustomRepository.findBloomedSpotIdsByFlowerSpotIds(spotIds) diff --git a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCustomRepository.kt b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCustomRepository.kt index 94c6d57..704c1d2 100644 --- a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCustomRepository.kt +++ b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCustomRepository.kt @@ -39,6 +39,22 @@ class BloomingCustomRepository( return entityManager.createQuery(query, jdslRenderContext).resultList } + fun recentlyByCafeId(cafeId: Long): List { + val threshold = LocalDateTime.now().minusDays(DATE_THRESHOLD) + + val query = + jpql { + select(entity(BloomingEntity::class)) + .from(entity(BloomingEntity::class)) + .whereAnd( + path(BloomingEntity::flowerSpotCafeId).eq(cafeId), + path(BloomingEntity::createdAt).gt(threshold), + ) + } + + return entityManager.createQuery(query, jdslRenderContext).resultList + } + fun recentlyByEventId(eventId: Long): List { val threshold = LocalDateTime.now().minusDays(DATE_THRESHOLD) @@ -73,6 +89,24 @@ class BloomingCustomRepository( return entityManager.createQuery(query, jdslRenderContext).resultList } + fun recentlyByCafeIds(cafeIds: List): List { + if (cafeIds.isEmpty()) return emptyList() + + val threshold = LocalDateTime.now().minusDays(DATE_THRESHOLD) + + val query = + jpql { + select(entity(BloomingEntity::class)) + .from(entity(BloomingEntity::class)) + .whereAnd( + path(BloomingEntity::flowerSpotCafeId).`in`(cafeIds), + path(BloomingEntity::createdAt).gt(threshold), + ) + } + + return entityManager.createQuery(query, jdslRenderContext).resultList + } + fun recentlyByEventIds(eventIds: List): List { if (eventIds.isEmpty()) return emptyList() @@ -135,6 +169,28 @@ class BloomingCustomRepository( return entityManager.createQuery(query, jdslRenderContext).resultList.firstOrNull() } + fun findTodayCafeBloomingByUserId( + userId: Long, + flowerSpotCafeId: Long, + ): BloomingEntity? { + val startOfDay = LocalDate.now().atStartOfDay() + val endOfDay = startOfDay.plusDays(1) + + val query = + jpql { + select(entity(BloomingEntity::class)) + .from(entity(BloomingEntity::class)) + .whereAnd( + path(BloomingEntity::userId).eq(userId), + path(BloomingEntity::flowerSpotCafeId).eq(flowerSpotCafeId), + path(BloomingEntity::createdAt).greaterThanOrEqualTo(startOfDay), + path(BloomingEntity::createdAt).lessThan(endOfDay), + ) + } + + return entityManager.createQuery(query, jdslRenderContext).resultList.firstOrNull() + } + fun findBloomedSpotIdsByFlowerSpotIds(spotIds: List): List { if (spotIds.isEmpty()) return emptyList() diff --git a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingEntity.kt b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingEntity.kt index 7ce9a40..875b3b0 100644 --- a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingEntity.kt +++ b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingEntity.kt @@ -22,6 +22,7 @@ class BloomingEntity( val userId: Long, val flowerSpotId: Long?, val flowerEventId: Long?, + val flowerSpotCafeId: Long?, @Enumerated(value = EnumType.STRING) @Column(columnDefinition = "varchar(50)") val status: BloomingStatus, @@ -32,6 +33,7 @@ class BloomingEntity( userId = userId, flowerSpotId = flowerSpotId, flowerEventId = flowerEventId, + flowerSpotCafeId = flowerSpotCafeId, status = status, createdAt = createdAt, ) diff --git a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingJpaRepository.kt b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingJpaRepository.kt index 8670dc2..f7e2cae 100644 --- a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingJpaRepository.kt +++ b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingJpaRepository.kt @@ -16,6 +16,11 @@ interface BloomingJpaRepository : flowerEventId: Long, ): BloomingEntity? + fun findTopByUserIdAndFlowerSpotCafeIdOrderByCreatedAtDesc( + userId: Long, + flowerSpotCafeId: Long, + ): BloomingEntity? + fun findAllByUserId(userId: Long): List fun findAllByFlowerSpotId(flowerSpotId: Long): List From a63295f0ca1050ca09e2c3462233dc3df1f2b9ad Mon Sep 17 00:00:00 2001 From: leehaneum Date: Fri, 27 Mar 2026 14:59:08 +0900 Subject: [PATCH 5/7] refactor: Restore blooming logic in cafe strategies with flowerSpotCafeId Co-Authored-By: Claude Opus 4.6 --- .../detail/MapCategoryItemCommonDetailResponse.kt | 4 ++-- .../response/detail/MapCategoryItemDetailResponse.kt | 3 +-- .../pida/category/item/detail/MapCategoryItemDetail.kt | 2 +- .../detail/CafeCategoryItemDetailReadStrategy.kt | 3 +-- .../strategy/read/CafeCategoryItemReadStrategy.kt | 9 ++++----- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemCommonDetailResponse.kt b/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemCommonDetailResponse.kt index ac9d880..10260b4 100644 --- a/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemCommonDetailResponse.kt +++ b/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemCommonDetailResponse.kt @@ -47,6 +47,6 @@ data class MapCategoryItemCommonDetailResponse( arraySchema = Schema(description = "카드에 노출할 배지 목록"), ) val badges: List, - @field:Schema(description = "개화 상태 상세 정보") - val bloomingDetails: BloomingDetailsResponse, + @field:Schema(description = "개화 상태 상세 정보", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + val bloomingDetails: BloomingDetailsResponse? = null, ) diff --git a/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemDetailResponse.kt b/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemDetailResponse.kt index 5df6b56..68bc216 100644 --- a/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemDetailResponse.kt +++ b/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemDetailResponse.kt @@ -38,7 +38,7 @@ data class MapCategoryItemDetailResponse( imageUrls = item.imageUrls.map(FlowerSpotImageResponse::from), bloomingStatus = item.bloomingStatus, badges = item.badges.map(MapCategoryBadgeResponse::from), - bloomingDetails = BloomingDetailsResponse.from(mapCategoryItemDetail.bloomingDetails), + bloomingDetails = mapCategoryItemDetail.bloomingDetails?.let { BloomingDetailsResponse.from(it) }, ), detail = when (mapCategoryItemDetail.categoryLabel) { @@ -59,7 +59,6 @@ data class MapCategoryItemDetailResponse( CafeCategoryItemDetailPayloadResponse( thumbnailUrl = item.thumbnailUrl, mapUrl = item.mapUrl, - flowerSpotId = item.flowerSpotId, recentlyVisitedCount = item.recentlyVisitedCount, ), ) diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/category/item/detail/MapCategoryItemDetail.kt b/pida-core/core-domain/src/main/kotlin/com/pida/category/item/detail/MapCategoryItemDetail.kt index c2d7479..ed02c15 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/category/item/detail/MapCategoryItemDetail.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/category/item/detail/MapCategoryItemDetail.kt @@ -8,5 +8,5 @@ data class MapCategoryItemDetail( val categoryId: Long, val categoryLabel: CategoryLabel, val item: MapCategoryItem, - val bloomingDetails: BloomingDetails, + val bloomingDetails: BloomingDetails? = null, ) diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/detail/CafeCategoryItemDetailReadStrategy.kt b/pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/detail/CafeCategoryItemDetailReadStrategy.kt index 3196669..723c045 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/detail/CafeCategoryItemDetailReadStrategy.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/detail/CafeCategoryItemDetailReadStrategy.kt @@ -24,7 +24,7 @@ class CafeCategoryItemDetailReadStrategy( itemId: Long, ): MapCategoryItemDetail { val cafe = flowerSpotCafeFinder.readBy(itemId) - val bloomingDetails = bloomingFacade.readBloomingDetails(flowerSpotId = cafe.flowerSpotId) + val bloomingDetails = bloomingFacade.readBloomingDetails(flowerSpotCafeId = cafe.id) val representativeBloomingStatus = bloomingDetails.representativeBloomingStatus() val badges = mapCategoryBadgeFinder.findAllGroupedByTarget( @@ -45,7 +45,6 @@ class CafeCategoryItemDetailReadStrategy( pinPoint = cafe.pinPoint, region = cafe.region, mapUrl = cafe.mapUrl, - flowerSpotId = cafe.flowerSpotId, recentlyVisitedCount = bloomingDetails.totalCount, bloomingStatus = representativeBloomingStatus, badges = diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/read/CafeCategoryItemReadStrategy.kt b/pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/read/CafeCategoryItemReadStrategy.kt index bcd8ab4..718ef79 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/read/CafeCategoryItemReadStrategy.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/read/CafeCategoryItemReadStrategy.kt @@ -38,10 +38,10 @@ class CafeCategoryItemReadStrategy( }.let { items -> region?.let { targetRegion -> items.filter { it.region == targetRegion } } ?: items } - val recentBloomingBySpotId = + val recentBloomingByCafeId = bloomingService - .recentlyBloomingBySpotIds(cafes.map { it.flowerSpotId }) - .groupBy { it.flowerSpotId } + .recentlyBloomingByCafeIds(cafes.map { it.id }) + .groupBy { it.flowerSpotCafeId } val badgesByCafeId = mapCategoryBadgeFinder.findAllGroupedByTarget( targetType = MapCategoryBadgeTargetType.FLOWER_SPOT_CAFE, @@ -49,7 +49,7 @@ class CafeCategoryItemReadStrategy( ) return cafes.map { cafe -> - val recentBloomings = recentBloomingBySpotId[cafe.flowerSpotId] ?: emptyList() + val recentBloomings = recentBloomingByCafeId[cafe.id] ?: emptyList() val representativeBloomingStatus = recentBloomings .groupBy { it.status } @@ -65,7 +65,6 @@ class CafeCategoryItemReadStrategy( pinPoint = cafe.pinPoint, region = cafe.region, mapUrl = cafe.mapUrl, - flowerSpotId = cafe.flowerSpotId, recentlyVisitedCount = recentBloomings.size.toLong(), bloomingStatus = representativeBloomingStatus, badges = From 9751d60db1ea5ab2bbd4ff04636c6e89cabd5439 Mon Sep 17 00:00:00 2001 From: leehaneum Date: Fri, 27 Mar 2026 14:59:47 +0900 Subject: [PATCH 6/7] test: Update tests for flowerSpotCafeId blooming changes Co-Authored-By: Claude Opus 4.6 --- .../kotlin/com/pida/blooming/BloomingFacadeTest.kt | 5 +++++ .../com/pida/blooming/BloomingServiceTest.kt | 1 + .../CafeCategoryItemDetailReadStrategyTest.kt | 9 +++------ .../category/CafeCategoryItemReadStrategyTest.kt | 14 +++++--------- .../EventCategoryItemDetailReadStrategyTest.kt | 2 +- .../category/EventCategoryItemReadStrategyTest.kt | 1 + .../FlowerSpotCategoryItemReadStrategyTest.kt | 1 + .../com/pida/flowerspot/FlowerSpotFacadeTest.kt | 3 +++ .../perf/FlowerSpotPerformanceBenchmarkRunner.kt | 1 + ...loomedFirstVoteNotificationEventListenerTest.kt | 3 +++ 10 files changed, 24 insertions(+), 16 deletions(-) diff --git a/pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingFacadeTest.kt b/pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingFacadeTest.kt index 63a2e61..2286d31 100644 --- a/pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingFacadeTest.kt +++ b/pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingFacadeTest.kt @@ -42,6 +42,7 @@ class BloomingFacadeTest { userId = 2L, flowerSpotId = null, flowerEventId = 7L, + flowerSpotCafeId = null, createdAt = LocalDateTime.of(2026, 3, 18, 10, 0), ), Blooming( @@ -50,6 +51,7 @@ class BloomingFacadeTest { userId = 3L, flowerSpotId = null, flowerEventId = 7L, + flowerSpotCafeId = null, createdAt = LocalDateTime.of(2026, 3, 19, 11, 0), ), Blooming( @@ -58,6 +60,7 @@ class BloomingFacadeTest { userId = 4L, flowerSpotId = null, flowerEventId = 7L, + flowerSpotCafeId = null, createdAt = LocalDateTime.of(2026, 3, 19, 9, 0), ), ) @@ -112,6 +115,7 @@ class BloomingFacadeTest { userId = 1L, flowerSpotId = 3L, flowerEventId = null, + flowerSpotCafeId = null, createdAt = LocalDateTime.of(2026, 3, 19, 10, 0), ) every { eventPublisher.publishEvent(BloomingAddedEvent(newBlooming)) } returns Unit @@ -162,6 +166,7 @@ class BloomingFacadeTest { userId = 2L, flowerSpotId = null, flowerEventId = 7L, + flowerSpotCafeId = null, createdAt = LocalDateTime.of(2026, 3, 19, 11, 0), ) every { eventPublisher.publishEvent(BloomingAddedEvent(newBlooming)) } returns Unit diff --git a/pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingServiceTest.kt b/pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingServiceTest.kt index e68a681..38fa837 100644 --- a/pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingServiceTest.kt +++ b/pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingServiceTest.kt @@ -26,6 +26,7 @@ class BloomingServiceTest { userId = 10L, flowerSpotId = null, flowerEventId = 20L, + flowerSpotCafeId = null, createdAt = LocalDateTime.of(2026, 3, 19, 9, 0), ) diff --git a/pida-core/core-domain/src/test/kotlin/com/pida/category/CafeCategoryItemDetailReadStrategyTest.kt b/pida-core/core-domain/src/test/kotlin/com/pida/category/CafeCategoryItemDetailReadStrategyTest.kt index eb028e7..4fcc4ca 100644 --- a/pida-core/core-domain/src/test/kotlin/com/pida/category/CafeCategoryItemDetailReadStrategyTest.kt +++ b/pida-core/core-domain/src/test/kotlin/com/pida/category/CafeCategoryItemDetailReadStrategyTest.kt @@ -22,7 +22,7 @@ import java.time.LocalDateTime class CafeCategoryItemDetailReadStrategyTest { @Test - fun `카페 상세는 연결된 벚꽃길의 개화 상세를 함께 응답한다`(): Unit = + fun `카페 상세는 개화 상세를 함께 응답한다`(): Unit = runBlocking { val flowerSpotCafeFinder = mockk() val bloomingFacade = mockk() @@ -32,7 +32,6 @@ class CafeCategoryItemDetailReadStrategyTest { coEvery { flowerSpotCafeFinder.readBy(20L) } returns FlowerSpotCafe( id = 20L, - flowerSpotId = 3L, name = "벚꽃뷰 카페", address = "서울특별시 송파구 석촌호수로 12", description = "석촌호수 근처 카페", @@ -42,7 +41,7 @@ class CafeCategoryItemDetailReadStrategyTest { mapUrl = "https://place.map.kakao.com/123456", deletedAt = null, ) - coEvery { bloomingFacade.readBloomingDetails(flowerSpotId = 3L) } returns + coEvery { bloomingFacade.readBloomingDetails(flowerSpotCafeId = 20L) } returns BloomingDetails( totalCount = 5L, nickname = "피다", @@ -67,7 +66,6 @@ class CafeCategoryItemDetailReadStrategyTest { result.categoryId shouldBe 2L result.categoryLabel shouldBe CategoryLabel.CAFE - result.item.flowerSpotId shouldBe 3L result.item.thumbnailUrl shouldBe "https://cdn.example.com/cafe-thumbnail.jpg" result.item.recentlyVisitedCount shouldBe 5L result.item.bloomingStatus shouldBe BloomingStatus.BLOOMED @@ -88,7 +86,6 @@ class CafeCategoryItemDetailReadStrategyTest { coEvery { flowerSpotCafeFinder.readBy(21L) } returns FlowerSpotCafe( id = 21L, - flowerSpotId = 4L, name = "호수뷰 카페", address = "서울특별시 송파구 잠실동", description = "호수 근처 카페", @@ -98,7 +95,7 @@ class CafeCategoryItemDetailReadStrategyTest { mapUrl = "https://place.map.kakao.com/654321", deletedAt = null, ) - coEvery { bloomingFacade.readBloomingDetails(flowerSpotId = 4L) } returns + coEvery { bloomingFacade.readBloomingDetails(flowerSpotCafeId = 21L) } returns BloomingDetails( totalCount = 1L, nickname = "피다", diff --git a/pida-core/core-domain/src/test/kotlin/com/pida/category/CafeCategoryItemReadStrategyTest.kt b/pida-core/core-domain/src/test/kotlin/com/pida/category/CafeCategoryItemReadStrategyTest.kt index 1a5d3ed..5cb7e40 100644 --- a/pida-core/core-domain/src/test/kotlin/com/pida/category/CafeCategoryItemReadStrategyTest.kt +++ b/pida-core/core-domain/src/test/kotlin/com/pida/category/CafeCategoryItemReadStrategyTest.kt @@ -43,7 +43,6 @@ class CafeCategoryItemReadStrategyTest { val cafe = FlowerSpotCafe( id = 20L, - flowerSpotId = 3L, name = "벚꽃뷰 카페", address = "서울특별시 송파구 석촌호수로 12", description = "석촌호수 근처 카페", @@ -56,14 +55,15 @@ class CafeCategoryItemReadStrategyTest { coEvery { mapCategoryService.findAllByCategoryLabel(CategoryLabel.CAFE) } returns listOf(category) coEvery { flowerSpotCafeFinder.readAll() } returns listOf(cafe) - every { bloomingService.recentlyBloomingBySpotIds(listOf(3L)) } returns + every { bloomingService.recentlyBloomingByCafeIds(listOf(20L)) } returns listOf( Blooming( id = 1L, status = BloomingStatus.BLOOMED, userId = 1L, - flowerSpotId = 3L, + flowerSpotId = null, flowerEventId = null, + flowerSpotCafeId = 20L, createdAt = java.time.LocalDateTime.of(2026, 3, 19, 10, 0), ), ) @@ -78,7 +78,6 @@ class CafeCategoryItemReadStrategyTest { result shouldHaveSize 1 result.first().id shouldBe 20L - result.first().flowerSpotId shouldBe 3L result.first().thumbnailUrl shouldBe "https://cdn.example.com/cafe-thumbnail.jpg" result.first().recentlyVisitedCount shouldBe 1L result.first().bloomingStatus shouldBe BloomingStatus.BLOOMED @@ -108,7 +107,6 @@ class CafeCategoryItemReadStrategyTest { val cafe = FlowerSpotCafe( id = 21L, - flowerSpotId = 4L, name = "호수뷰 카페", address = "서울특별시 송파구 잠실동", description = "호수 근처 카페", @@ -121,7 +119,7 @@ class CafeCategoryItemReadStrategyTest { coEvery { mapCategoryService.findAllByCategoryLabel(CategoryLabel.CAFE) } returns listOf(category) coEvery { flowerSpotCafeFinder.readAllByLocation(location) } returns listOf(cafe) - every { bloomingService.recentlyBloomingBySpotIds(listOf(4L)) } returns emptyList() + every { bloomingService.recentlyBloomingByCafeIds(listOf(21L)) } returns emptyList() coEvery { mapCategoryBadgeFinder.findAllGroupedByTarget(MapCategoryBadgeTargetType.FLOWER_SPOT_CAFE, listOf(21L)) } returns emptyMap() @@ -198,7 +196,6 @@ class CafeCategoryItemReadStrategyTest { val seoulCafe = FlowerSpotCafe( id = 22L, - flowerSpotId = 5L, name = "서울 카페", address = "서울특별시 송파구 잠실동", description = null, @@ -211,7 +208,6 @@ class CafeCategoryItemReadStrategyTest { val busanCafe = FlowerSpotCafe( id = 23L, - flowerSpotId = 6L, name = "부산 카페", address = "부산광역시 수영구 광안동", description = null, @@ -224,7 +220,7 @@ class CafeCategoryItemReadStrategyTest { coEvery { mapCategoryService.findAllByCategoryLabel(CategoryLabel.CAFE) } returns listOf(category) coEvery { flowerSpotCafeFinder.readAll() } returns listOf(seoulCafe, busanCafe) - every { bloomingService.recentlyBloomingBySpotIds(listOf(5L)) } returns emptyList() + every { bloomingService.recentlyBloomingByCafeIds(listOf(22L)) } returns emptyList() coEvery { mapCategoryBadgeFinder.findAllGroupedByTarget(MapCategoryBadgeTargetType.FLOWER_SPOT_CAFE, listOf(22L)) } returns emptyMap() diff --git a/pida-core/core-domain/src/test/kotlin/com/pida/category/EventCategoryItemDetailReadStrategyTest.kt b/pida-core/core-domain/src/test/kotlin/com/pida/category/EventCategoryItemDetailReadStrategyTest.kt index 360871a..c81d82d 100644 --- a/pida-core/core-domain/src/test/kotlin/com/pida/category/EventCategoryItemDetailReadStrategyTest.kt +++ b/pida-core/core-domain/src/test/kotlin/com/pida/category/EventCategoryItemDetailReadStrategyTest.kt @@ -74,6 +74,6 @@ class EventCategoryItemDetailReadStrategyTest { listOf( MapCategoryBadgeType.REGION to "서울", ) - result.bloomingDetails.totalCount shouldBe 3L + result.bloomingDetails?.totalCount shouldBe 3L } } diff --git a/pida-core/core-domain/src/test/kotlin/com/pida/category/EventCategoryItemReadStrategyTest.kt b/pida-core/core-domain/src/test/kotlin/com/pida/category/EventCategoryItemReadStrategyTest.kt index 63a216c..b7e36d1 100644 --- a/pida-core/core-domain/src/test/kotlin/com/pida/category/EventCategoryItemReadStrategyTest.kt +++ b/pida-core/core-domain/src/test/kotlin/com/pida/category/EventCategoryItemReadStrategyTest.kt @@ -54,6 +54,7 @@ class EventCategoryItemReadStrategyTest { userId = 1L, flowerSpotId = null, flowerEventId = 10L, + flowerSpotCafeId = null, createdAt = LocalDate.of(2026, 3, 19).atStartOfDay(), ), ) diff --git a/pida-core/core-domain/src/test/kotlin/com/pida/category/FlowerSpotCategoryItemReadStrategyTest.kt b/pida-core/core-domain/src/test/kotlin/com/pida/category/FlowerSpotCategoryItemReadStrategyTest.kt index f6c69cb..0174854 100644 --- a/pida-core/core-domain/src/test/kotlin/com/pida/category/FlowerSpotCategoryItemReadStrategyTest.kt +++ b/pida-core/core-domain/src/test/kotlin/com/pida/category/FlowerSpotCategoryItemReadStrategyTest.kt @@ -73,6 +73,7 @@ class FlowerSpotCategoryItemReadStrategyTest { userId = 1L, flowerSpotId = 30L, flowerEventId = null, + flowerSpotCafeId = null, createdAt = LocalDateTime.of(2026, 3, 19, 10, 0), ), ) diff --git a/pida-core/core-domain/src/test/kotlin/com/pida/flowerspot/FlowerSpotFacadeTest.kt b/pida-core/core-domain/src/test/kotlin/com/pida/flowerspot/FlowerSpotFacadeTest.kt index 5184b2d..fc198dc 100644 --- a/pida-core/core-domain/src/test/kotlin/com/pida/flowerspot/FlowerSpotFacadeTest.kt +++ b/pida-core/core-domain/src/test/kotlin/com/pida/flowerspot/FlowerSpotFacadeTest.kt @@ -46,6 +46,7 @@ class FlowerSpotFacadeTest { userId = 1L, flowerSpotId = 10L, flowerEventId = null, + flowerSpotCafeId = null, status = BloomingStatus.BLOOMED, createdAt = LocalDateTime.of(2026, 3, 20, 10, 0), ), @@ -54,6 +55,7 @@ class FlowerSpotFacadeTest { userId = 2L, flowerSpotId = 10L, flowerEventId = null, + flowerSpotCafeId = null, status = BloomingStatus.BLOOMED, createdAt = LocalDateTime.of(2026, 3, 20, 11, 0), ), @@ -62,6 +64,7 @@ class FlowerSpotFacadeTest { userId = 3L, flowerSpotId = 20L, flowerEventId = null, + flowerSpotCafeId = null, status = BloomingStatus.LITTLE, createdAt = LocalDateTime.of(2026, 3, 20, 9, 0), ), diff --git a/pida-core/core-domain/src/test/kotlin/com/pida/flowerspot/perf/FlowerSpotPerformanceBenchmarkRunner.kt b/pida-core/core-domain/src/test/kotlin/com/pida/flowerspot/perf/FlowerSpotPerformanceBenchmarkRunner.kt index fb1316d..42659ba 100644 --- a/pida-core/core-domain/src/test/kotlin/com/pida/flowerspot/perf/FlowerSpotPerformanceBenchmarkRunner.kt +++ b/pida-core/core-domain/src/test/kotlin/com/pida/flowerspot/perf/FlowerSpotPerformanceBenchmarkRunner.kt @@ -261,6 +261,7 @@ private data class BenchmarkFixture( userId = offset.toLong(), flowerSpotId = spot.id, flowerEventId = null, + flowerSpotCafeId = null, status = when (offset % 3) { 0 -> BloomingStatus.BLOOMED diff --git a/pida-core/core-domain/src/test/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListenerTest.kt b/pida-core/core-domain/src/test/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListenerTest.kt index 840e931..09f0913 100644 --- a/pida-core/core-domain/src/test/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListenerTest.kt +++ b/pida-core/core-domain/src/test/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListenerTest.kt @@ -5,6 +5,7 @@ import com.pida.blooming.BloomingStatus import com.pida.blooming.NewBlooming import com.pida.flowerevent.FlowerEvent import com.pida.flowerevent.FlowerEventRepository +import com.pida.flowerspot.FlowerSpotCafeRepository import com.pida.flowerspot.FlowerSpotRepository import com.pida.support.geo.GeoJson import com.pida.support.geo.Region @@ -21,12 +22,14 @@ class BloomedFirstVoteNotificationEventListenerTest { fun `꽃 이벤트 첫 만개 투표면 해당 지역 만개 알림을 발송한다`() { val flowerSpotRepository = mockk() val flowerEventRepository = mockk() + val flowerSpotCafeRepository = mockk() val firstVoteChecker = mockk() val bloomedNotificationService = mockk() val listener = BloomedFirstVoteNotificationEventListener( flowerSpotRepository = flowerSpotRepository, flowerEventRepository = flowerEventRepository, + flowerSpotCafeRepository = flowerSpotCafeRepository, firstVoteChecker = firstVoteChecker, bloomedNotificationService = bloomedNotificationService, ) From 48e77bb35dcb5848e536424a8c08da271f13b399 Mon Sep 17 00:00:00 2001 From: leehaneum Date: Fri, 27 Mar 2026 15:09:52 +0900 Subject: [PATCH 7/7] fix: Restore bloomingDetails to non-null since all strategies provide it Co-Authored-By: Claude Opus 4.6 --- .../response/detail/MapCategoryItemCommonDetailResponse.kt | 4 ++-- .../category/response/detail/MapCategoryItemDetailResponse.kt | 2 +- .../com/pida/category/item/detail/MapCategoryItemDetail.kt | 2 +- .../pida/category/EventCategoryItemDetailReadStrategyTest.kt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemCommonDetailResponse.kt b/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemCommonDetailResponse.kt index 10260b4..ac9d880 100644 --- a/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemCommonDetailResponse.kt +++ b/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemCommonDetailResponse.kt @@ -47,6 +47,6 @@ data class MapCategoryItemCommonDetailResponse( arraySchema = Schema(description = "카드에 노출할 배지 목록"), ) val badges: List, - @field:Schema(description = "개화 상태 상세 정보", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - val bloomingDetails: BloomingDetailsResponse? = null, + @field:Schema(description = "개화 상태 상세 정보") + val bloomingDetails: BloomingDetailsResponse, ) diff --git a/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemDetailResponse.kt b/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemDetailResponse.kt index 68bc216..6b009c6 100644 --- a/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemDetailResponse.kt +++ b/pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemDetailResponse.kt @@ -38,7 +38,7 @@ data class MapCategoryItemDetailResponse( imageUrls = item.imageUrls.map(FlowerSpotImageResponse::from), bloomingStatus = item.bloomingStatus, badges = item.badges.map(MapCategoryBadgeResponse::from), - bloomingDetails = mapCategoryItemDetail.bloomingDetails?.let { BloomingDetailsResponse.from(it) }, + bloomingDetails = BloomingDetailsResponse.from(mapCategoryItemDetail.bloomingDetails), ), detail = when (mapCategoryItemDetail.categoryLabel) { diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/category/item/detail/MapCategoryItemDetail.kt b/pida-core/core-domain/src/main/kotlin/com/pida/category/item/detail/MapCategoryItemDetail.kt index ed02c15..c2d7479 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/category/item/detail/MapCategoryItemDetail.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/category/item/detail/MapCategoryItemDetail.kt @@ -8,5 +8,5 @@ data class MapCategoryItemDetail( val categoryId: Long, val categoryLabel: CategoryLabel, val item: MapCategoryItem, - val bloomingDetails: BloomingDetails? = null, + val bloomingDetails: BloomingDetails, ) diff --git a/pida-core/core-domain/src/test/kotlin/com/pida/category/EventCategoryItemDetailReadStrategyTest.kt b/pida-core/core-domain/src/test/kotlin/com/pida/category/EventCategoryItemDetailReadStrategyTest.kt index c81d82d..360871a 100644 --- a/pida-core/core-domain/src/test/kotlin/com/pida/category/EventCategoryItemDetailReadStrategyTest.kt +++ b/pida-core/core-domain/src/test/kotlin/com/pida/category/EventCategoryItemDetailReadStrategyTest.kt @@ -74,6 +74,6 @@ class EventCategoryItemDetailReadStrategyTest { listOf( MapCategoryBadgeType.REGION to "서울", ) - result.bloomingDetails?.totalCount shouldBe 3L + result.bloomingDetails.totalCount shouldBe 3L } }