Skip to content

[Refactor] Remove flowerSpotId from FlowerSpotCafe and support query BloomingStatus by FlowerSpotCafeId#135

Merged
LeeHanEum merged 7 commits into
developfrom
refactor/flowerspotcafe-improvement
Mar 27, 2026
Merged

[Refactor] Remove flowerSpotId from FlowerSpotCafe and support query BloomingStatus by FlowerSpotCafeId#135
LeeHanEum merged 7 commits into
developfrom
refactor/flowerspotcafe-improvement

Conversation

@LeeHanEum

@LeeHanEum LeeHanEum commented Mar 27, 2026

Copy link
Copy Markdown
Collaborator

🌱 관련 이슈

📌 작업 내용 및 특이 사항

  • FlowerSpotCafe 도메인 모델에서 FlowerSpotId 키 제거
  • flowerSpotCafeId blooming query 지원

📝 참고

📌 체크 리스트

  • 리뷰어를 추가하셨나요 ?
  • 변경사항에 대해 충분히 설명하고 있나요 ?

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for tracking blooming activity by cafe, enabling cafe-specific insights and analytics.
  • Bug Fixes

    • Updated category detail responses to correctly associate blooming data with cafes rather than flower spots.
  • Refactoring

    • Restructured blooming data retrieval to use cafe identifiers directly, improving query efficiency for cafe-related features.

LeeHanEum and others added 6 commits March 27, 2026 14:58
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…feId

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 27, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR refactors the blooming system to directly associate blooming records with FlowerSpotCafe entities via flowerSpotCafeId, eliminating the indirect flowerSpotId dependency. It adds cafe-based blooming query methods across domain, facade, and persistence layers while removing the deprecated flowerSpotId field from request/response models and the FlowerSpotCafe entity.

Changes

Cohort / File(s) Summary
API Response Layer
pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/CafeCategoryItemDetailPayloadResponse.kt, pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/item/MapCategoryItemResponse.kt
Removed flowerSpotId field from cafe detail payload response; updated schema documentation for flowerSpotId in map category item to reflect inclusion for FLOWER_SPOT type only.
API Request Layer
pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/flowerspotcafe/request/FlowerSpotCafeCreateRequest.kt, pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemDetailResponse.kt
Removed flowerSpotId field from create request; removed field assignment in detail response construction for cafe category items.
Blooming Domain Core
pida-core/core-domain/src/main/kotlin/com/pida/blooming/Blooming.kt, pida-core/core-domain/src/main/kotlin/com/pida/blooming/NewBlooming.kt
Added flowerSpotCafeId: Long? property to Blooming and NewBlooming classes; introduced new NewBlooming.FlowerSpotCafe variant; updated validation logic to enforce exactly one of three ID fields is non-null.
Blooming Service Layer
pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFacade.kt, pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingService.kt
Extended readBloomingDetails() and uploadBloomingStatus() to handle FlowerSpotCafe blooming via new optional flowerSpotCafeId parameter; added recentlyBloomingByCafeId(s) public methods with caching; updated verifyTodayBlooming() to handle cafe-based queries.
Blooming Repository/Finder
pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingRepository.kt, pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFinder.kt
Added four new cafe-scoped finder/repository methods: readTopByUserIdAndFlowerSpotCafeIdDesc(), readRecentlyBloomingByCafeId(), recentlyBloomingByCafeIds(), readTodayBloomingByUserIdAndFlowerSpotCafeId().
Category Item Strategies
pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/detail/CafeCategoryItemDetailReadStrategy.kt, pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/read/CafeCategoryItemReadStrategy.kt
Updated blooming lookups to query by cafe ID instead of spot ID; removed flowerSpotId field assignment in MapCategoryItem construction.
FlowerSpotCafe Domain
pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafe.kt, pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/NewFlowerSpotCafe.kt, pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafeFinder.kt, pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafeRepository.kt
Removed flowerSpotId property from FlowerSpotCafe and NewFlowerSpotCafe domain models; removed readAllByFlowerSpotId() finder and repository methods.
Notification Handler
pida-core/core-domain/src/main/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListener.kt
Added FlowerSpotCafeRepository dependency; extended region resolution logic to handle NewBlooming.FlowerSpotCafe variant.
Blooming Persistence
pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingEntity.kt, pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingJpaRepository.kt, pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCoreRepository.kt, pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCustomRepository.kt
Added flowerSpotCafeId field to BloomingEntity; introduced cafe-specific JPA and custom repository queries; extended BloomingCoreRepository to implement cafe-based query overrides; added logic to persist FlowerSpotCafe blooming type.
FlowerSpotCafe Persistence
pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeEntity.kt, pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeJpaRepository.kt, pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeCoreRepository.kt
Removed flowerSpotId constructor parameter and table index; removed flowerSpotId assignment in entity-to-domain mapping; removed findByFlowerSpotIdAndDeletedAtIsNull() JPA query method.
Test Fixtures
pida-core/core-domain/src/test/kotlin/com/pida/blooming/..., pida-core/core-domain/src/test/kotlin/com/pida/category/..., pida-core/core-domain/src/test/kotlin/com/pida/flowerspot/..., pida-core/core-domain/src/test/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListenerTest.kt
Updated mocked Blooming objects and test data to include new flowerSpotCafeId field; removed assertions checking flowerSpotId in cafe-related tests; updated service stubbing to use cafe ID queries; added FlowerSpotCafeRepository mock dependency.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • char-yb

Poem

🐰 Hops skip past the spotty old way,
Cafes bloom direct in the light of day!
No more distant IDs to chase around,
FlowerSpotCafe blooms—efficiency found! 🌸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the two main changes: removing flowerSpotId from FlowerSpotCafe and adding query support by FlowerSpotCafeId, which directly align with the changeset's core refactoring objectives.
Linked Issues check ✅ Passed The PR successfully implements the core coding requirements from issue #134: removes flowerSpotId from FlowerSpotCafe domain/storage/API layers and adds blooming query support keyed by flowerSpotCafeId across all relevant components.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the stated objectives: removing flowerSpotId from FlowerSpotCafe, adding flowerSpotCafeId to Blooming model, and implementing cafe-based blooming queries. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/flowerspotcafe-improvement

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@LeeHanEum LeeHanEum changed the title Refactor/flowerspotcafe improvement [Refactor] Remove flowerSpotId from FlowerSpotCafe and support query BloomingStatus by FlowerSpotCafeId Mar 27, 2026
@LeeHanEum LeeHanEum self-assigned this Mar 27, 2026
@LeeHanEum LeeHanEum requested a review from char-yb March 27, 2026 06:16
@LeeHanEum LeeHanEum marked this pull request as ready for review March 27, 2026 06:16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
pida-core/core-domain/src/test/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListenerTest.kt (1)

22-65: ⚠️ Potential issue | 🟡 Minor

Missing test for the new NewBlooming.FlowerSpotCafe branch.

The test now injects FlowerSpotCafeRepository (Lines 25 and 32), but only verifies the NewBlooming.FlowerEvent flow.
Please add a case that sends NewBlooming.FlowerSpotCafe, stubs flowerSpotCafeRepository.findBy(...), and verifies region-based notification behavior for that branch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@pida-core/core-domain/src/test/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListenerTest.kt`
around lines 22 - 65, Add a parallel test case that exercises the
NewBlooming.FlowerSpotCafe branch: mock/stub
flowerSpotCafeRepository.findBy(...) to return a FlowerSpotCafe instance (set
region = Region.SEOUL), ensure
firstVoteChecker.isFirstBloomedVoteOfYear(Region.SEOUL) returns true and
bloomedNotificationService.sendBloomedNotificationForRegion(Region.SEOUL)
returns a value, call listener.handleBloomingAddedEvent(...) with
NewBlooming.FlowerSpotCafe (userId and flowerSpotCafeId), then verify
flowerSpotCafeRepository.findBy(...) was called and
bloomedNotificationService.sendBloomedNotificationForRegion(Region.SEOUL) was
invoked; reference: FlowerSpotCafeRepository, FlowerSpotCafe,
NewBlooming.FlowerSpotCafe, listener.handleBloomingAddedEvent,
flowerSpotCafeRepository.findBy, firstVoteChecker.isFirstBloomedVoteOfYear,
bloomedNotificationService.sendBloomedNotificationForRegion.
pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/detail/CafeCategoryItemDetailReadStrategy.kt (1)

35-58: ⚠️ Potential issue | 🟠 Major

Cafe detail now drops flowerSpotId, but API response still exposes it.

MapCategoryItemResponse (pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/item/MapCategoryItemResponse.kt, Lines 86-120) still serializes flowerSpotId = mapCategoryItem.flowerSpotId. With this strategy returning a MapCategoryItem without flowerSpotId, clients can receive unexpected null for cafe items.
Either map a transitional value here or remove/update that API contract in the same change set.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/detail/CafeCategoryItemDetailReadStrategy.kt`
around lines 35 - 58, MapCategoryItem constructed in
CafeCategoryItemDetailReadStrategy (returning MapCategoryItem in
MapCategoryItemDetail) does not set flowerSpotId while MapCategoryItemResponse
still serializes mapCategoryItem.flowerSpotId, causing nulls for cafe items; fix
by either adding a flowerSpotId field when instantiating MapCategoryItem in
CafeCategoryItemDetailReadStrategy (use cafe.flowerSpotId or a defined
transitional fallback) or update MapCategoryItemResponse to stop serializing
flowerSpotId (remove or mark deprecated) so the API contract and MapCategoryItem
align.
pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingEntity.kt (1)

14-25: ⚠️ Potential issue | 🟠 Major

Add an index for flower_spot_cafe_id before shipping this.

This PR introduces cafe-scoped blooming reads, but t_blooming still only indexes flower_spot_id. On a grown table the new cafe lookups will fall back to scans and undercut the latency goal of this change. At minimum, mirror the existing spot indexes for flower_spot_cafe_id.

Suggested index additions
 `@Table`(
     name = "t_blooming",
     indexes = [
         Index(name = "idx_blooming_flower_spot_id_created_at", columnList = "flower_spot_id,created_at"),
         Index(name = "idx_blooming_flower_spot_id_status_created_at", columnList = "flower_spot_id,status,created_at"),
+        Index(name = "idx_blooming_flower_spot_cafe_id_created_at", columnList = "flower_spot_cafe_id,created_at"),
+        Index(name = "idx_blooming_flower_spot_cafe_id_status_created_at", columnList = "flower_spot_cafe_id,status,created_at"),
     ],
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingEntity.kt`
around lines 14 - 25, Add matching indexes for flower_spot_cafe_id to the
t_blooming table so cafe-scoped queries use indexes; inside the `@Table`(indexes =
[...]) on BloomingEntity, add two new Index entries mirroring the existing
idx_blooming_flower_spot_id_created_at and
idx_blooming_flower_spot_id_status_created_at but for flower_spot_cafe_id
(suggested names: idx_blooming_flower_spot_cafe_id_created_at and
idx_blooming_flower_spot_cafe_id_status_created_at) with columnList
"flower_spot_cafe_id,created_at" and "flower_spot_cafe_id,status,created_at"
respectively.
pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingService.kt (1)

88-106: ⚠️ Potential issue | 🟡 Minor

Update bloomingVerifyByTarget endpoint to support flowerSpotCafeId parameter.

The verifyTodayBlooming service method accepts and handles flowerSpotCafeId (with explicit logic at line 100-101), but the API endpoint /blooming/verify/today does not expose this parameter. Either add flowerSpotCafeId as an optional @RequestParam to the controller endpoint and pass it to the service call, or remove the parameter from the service signature if cafe-scoped verification is not needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingService.kt`
around lines 88 - 106, The controller endpoint handling bloomingVerifyByTarget
(the /blooming/verify/today mapping) is missing the optional flowerSpotCafeId
parameter while the service method verifyTodayBlooming(userId, flowerSpotId,
flowerEventId, flowerSpotCafeId) expects it; either add an optional
`@RequestParam`("flowerSpotCafeId") Long? flowerSpotCafeId to the controller
method signature and pass it through to
bloomingService.verifyTodayBlooming(...), or remove the flowerSpotCafeId
parameter from BloomingService.verifyTodayBlooming and all related calls if
cafe-scoped verification is not required.
🧹 Nitpick comments (4)
pida-core/core-domain/src/main/kotlin/com/pida/blooming/Blooming.kt (1)

15-16: Add a diagnostic message to the constructor invariant.

Line 16 throws a bare IllegalArgumentException; include field context to speed up debugging when invalid combinations are created.

Suggested patch
-        require(nonNullCount == 1)
+        require(nonNullCount == 1) {
+            "Exactly one of flowerSpotId, flowerEventId, flowerSpotCafeId must be non-null " +
+                "(flowerSpotId=$flowerSpotId, flowerEventId=$flowerEventId, flowerSpotCafeId=$flowerSpotCafeId)"
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pida-core/core-domain/src/main/kotlin/com/pida/blooming/Blooming.kt` around
lines 15 - 16, The constructor currently enforces exactly one of flowerSpotId,
flowerEventId, flowerSpotCafeId is non-null using require(nonNullCount == 1) but
throws a bare IllegalArgumentException; update the invariant in Blooming (around
the val nonNullCount calculation and its require) to include a diagnostic
message that lists which fields are non-null and their values (or at least their
presence) so the exception shows context (e.g., build a small message
referencing flowerSpotId, flowerEventId, flowerSpotCafeId and pass it as the
lambda message to require).
pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingServiceTest.kt (1)

47-83: Add validation tests for flowerSpotCafeId request combinations.

The suite currently covers only spot/event invalid combinations. With cafe-target support added, add cases for:

  • cafe-only valid path
  • cafe+spot invalid
  • cafe+event invalid
  • all-three invalid

This will guard the new input matrix from regressions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingServiceTest.kt`
around lines 47 - 83, Add tests in BloomingServiceTest for the new
flowerSpotCafeId combinations by extending the existing verifyTodayBlooming
cases: add one test where only flowerSpotCafeId is provided (should not throw
and should follow the valid path), and three tests where flowerSpotCafeId is
combined with flowerSpotId, combined with flowerEventId, and where all three are
provided (each should throw ErrorException with ErrorType.INVALID_REQUEST). Use
the same setup pattern and assertions as the existing tests (mock
BloomingAppender, BloomingValidator, BloomingFinder, CacheRepository and call
BloomingService.verifyTodayBlooming) and name tests to mirror the current style
to cover cafe-only valid, cafe+spot invalid, cafe+event invalid, and all-three
invalid scenarios.
pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingFacadeTest.kt (1)

45-45: Add one facade test for the new cafe branch.

Now that Blooming has a third target key, this file only updates the legacy spot/event fixtures. A dedicated readBloomingDetails(flowerSpotCafeId = …) or uploadBloomingStatus(NewBlooming.FlowerSpotCafe) case here would catch regressions in the new facade path.

Also applies to: 54-54, 63-63, 118-118, 169-169

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingFacadeTest.kt`
at line 45, Add a new unit test in BloomingFacadeTest that exercises the new
cafe branch by calling the facade path that handles FlowerSpotCafe (e.g., invoke
readBloomingDetails(flowerSpotCafeId = <someId>) or
uploadBloomingStatus(NewBlooming.FlowerSpotCafe)) instead of passing null;
update or add assertions to verify the facade returns/updates the expected
Blooming details for the cafe key (use the same fixture setup used for legacy
spot/event tests so the new test mirrors those cases), ensuring the new target
key path in the Blooming facade is covered and will catch regressions.
pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFacade.kt (1)

86-100: Consider simplifying the async pattern - no parallel work is being performed.

The coroutineScope and async wrapper add overhead without benefit here. Unlike readBloomingDetailsBySpotId which parallelizes fetching bloomings and recent reporter, this method fetches bloomings first, then derives latestBlooming, then fetches the user profile sequentially.

♻️ Simplified version without unnecessary coroutineScope
-    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 readBloomingDetailsByCafeId(flowerSpotCafeId: Long): BloomingDetails {
+        val bloomings = bloomingService.recentlyBloomingByCafeId(flowerSpotCafeId)
+        val latestBlooming = bloomings.maxByOrNull { it.createdAt }
+        val nickname = latestBlooming?.userId?.let { userService.getProfile(it) }?.nickname
+
+        return buildBloomingDetails(
+            bloomings = bloomings,
+            nickname = nickname,
+            updatedAt = latestBlooming?.createdAt,
+        )
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFacade.kt`
around lines 86 - 100, The readBloomingDetailsByCafeId function uses
coroutineScope and async unnecessarily since work is sequential; remove
coroutineScope and the async block, call
bloomingService.recentlyBloomingByCafeId to get bloomings, compute
latestBlooming, then directly call
userService.getProfile(latestBlooming?.userId) (suspend) and pass its nickname
into buildBloomingDetails (use buildBloomingDetails(bloomings = bloomings,
nickname = profile?.nickname, updatedAt = latestBlooming?.createdAt)). Keep
existing function name readBloomingDetailsByCafeId and other identifiers
(bloomingService.recentlyBloomingByCafeId, userService.getProfile,
buildBloomingDetails) so behavior and types remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCustomRepository.kt`:
- Around line 42-56: The BloomingEntity is missing an index on flowerSpotCafeId,
causing queries like recentlyByCafeId, recentlyByCafeIds, and
findTodayCafeBloomingByUserId to trigger full table scans; add a database index
(ideally a composite index on flowerSpotCafeId and createdAt) to the
BloomingEntity JPA mapping (e.g., via `@Table`(indexes = [...]) or equivalent) so
the database can use the index for WHERE clauses filtering by flowerSpotCafeId
and createdAt and improve query performance.

---

Outside diff comments:
In `@pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingService.kt`:
- Around line 88-106: The controller endpoint handling bloomingVerifyByTarget
(the /blooming/verify/today mapping) is missing the optional flowerSpotCafeId
parameter while the service method verifyTodayBlooming(userId, flowerSpotId,
flowerEventId, flowerSpotCafeId) expects it; either add an optional
`@RequestParam`("flowerSpotCafeId") Long? flowerSpotCafeId to the controller
method signature and pass it through to
bloomingService.verifyTodayBlooming(...), or remove the flowerSpotCafeId
parameter from BloomingService.verifyTodayBlooming and all related calls if
cafe-scoped verification is not required.

In
`@pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/detail/CafeCategoryItemDetailReadStrategy.kt`:
- Around line 35-58: MapCategoryItem constructed in
CafeCategoryItemDetailReadStrategy (returning MapCategoryItem in
MapCategoryItemDetail) does not set flowerSpotId while MapCategoryItemResponse
still serializes mapCategoryItem.flowerSpotId, causing nulls for cafe items; fix
by either adding a flowerSpotId field when instantiating MapCategoryItem in
CafeCategoryItemDetailReadStrategy (use cafe.flowerSpotId or a defined
transitional fallback) or update MapCategoryItemResponse to stop serializing
flowerSpotId (remove or mark deprecated) so the API contract and MapCategoryItem
align.

In
`@pida-core/core-domain/src/test/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListenerTest.kt`:
- Around line 22-65: Add a parallel test case that exercises the
NewBlooming.FlowerSpotCafe branch: mock/stub
flowerSpotCafeRepository.findBy(...) to return a FlowerSpotCafe instance (set
region = Region.SEOUL), ensure
firstVoteChecker.isFirstBloomedVoteOfYear(Region.SEOUL) returns true and
bloomedNotificationService.sendBloomedNotificationForRegion(Region.SEOUL)
returns a value, call listener.handleBloomingAddedEvent(...) with
NewBlooming.FlowerSpotCafe (userId and flowerSpotCafeId), then verify
flowerSpotCafeRepository.findBy(...) was called and
bloomedNotificationService.sendBloomedNotificationForRegion(Region.SEOUL) was
invoked; reference: FlowerSpotCafeRepository, FlowerSpotCafe,
NewBlooming.FlowerSpotCafe, listener.handleBloomingAddedEvent,
flowerSpotCafeRepository.findBy, firstVoteChecker.isFirstBloomedVoteOfYear,
bloomedNotificationService.sendBloomedNotificationForRegion.

In
`@pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingEntity.kt`:
- Around line 14-25: Add matching indexes for flower_spot_cafe_id to the
t_blooming table so cafe-scoped queries use indexes; inside the `@Table`(indexes =
[...]) on BloomingEntity, add two new Index entries mirroring the existing
idx_blooming_flower_spot_id_created_at and
idx_blooming_flower_spot_id_status_created_at but for flower_spot_cafe_id
(suggested names: idx_blooming_flower_spot_cafe_id_created_at and
idx_blooming_flower_spot_cafe_id_status_created_at) with columnList
"flower_spot_cafe_id,created_at" and "flower_spot_cafe_id,status,created_at"
respectively.

---

Nitpick comments:
In `@pida-core/core-domain/src/main/kotlin/com/pida/blooming/Blooming.kt`:
- Around line 15-16: The constructor currently enforces exactly one of
flowerSpotId, flowerEventId, flowerSpotCafeId is non-null using
require(nonNullCount == 1) but throws a bare IllegalArgumentException; update
the invariant in Blooming (around the val nonNullCount calculation and its
require) to include a diagnostic message that lists which fields are non-null
and their values (or at least their presence) so the exception shows context
(e.g., build a small message referencing flowerSpotId, flowerEventId,
flowerSpotCafeId and pass it as the lambda message to require).

In `@pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFacade.kt`:
- Around line 86-100: The readBloomingDetailsByCafeId function uses
coroutineScope and async unnecessarily since work is sequential; remove
coroutineScope and the async block, call
bloomingService.recentlyBloomingByCafeId to get bloomings, compute
latestBlooming, then directly call
userService.getProfile(latestBlooming?.userId) (suspend) and pass its nickname
into buildBloomingDetails (use buildBloomingDetails(bloomings = bloomings,
nickname = profile?.nickname, updatedAt = latestBlooming?.createdAt)). Keep
existing function name readBloomingDetailsByCafeId and other identifiers
(bloomingService.recentlyBloomingByCafeId, userService.getProfile,
buildBloomingDetails) so behavior and types remain unchanged.

In
`@pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingFacadeTest.kt`:
- Line 45: Add a new unit test in BloomingFacadeTest that exercises the new cafe
branch by calling the facade path that handles FlowerSpotCafe (e.g., invoke
readBloomingDetails(flowerSpotCafeId = <someId>) or
uploadBloomingStatus(NewBlooming.FlowerSpotCafe)) instead of passing null;
update or add assertions to verify the facade returns/updates the expected
Blooming details for the cafe key (use the same fixture setup used for legacy
spot/event tests so the new test mirrors those cases), ensuring the new target
key path in the Blooming facade is covered and will catch regressions.

In
`@pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingServiceTest.kt`:
- Around line 47-83: Add tests in BloomingServiceTest for the new
flowerSpotCafeId combinations by extending the existing verifyTodayBlooming
cases: add one test where only flowerSpotCafeId is provided (should not throw
and should follow the valid path), and three tests where flowerSpotCafeId is
combined with flowerSpotId, combined with flowerEventId, and where all three are
provided (each should throw ErrorException with ErrorType.INVALID_REQUEST). Use
the same setup pattern and assertions as the existing tests (mock
BloomingAppender, BloomingValidator, BloomingFinder, CacheRepository and call
BloomingService.verifyTodayBlooming) and name tests to mirror the current style
to cover cafe-only valid, cafe+spot invalid, cafe+event invalid, and all-three
invalid scenarios.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ac1796ed-e885-4554-a2dc-4441b4d172ee

📥 Commits

Reviewing files that changed from the base of the PR and between 71e9ab7 and 48e77bb.

📒 Files selected for processing (33)
  • pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/CafeCategoryItemDetailPayloadResponse.kt
  • pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemDetailResponse.kt
  • pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/item/MapCategoryItemResponse.kt
  • pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/flowerspotcafe/request/FlowerSpotCafeCreateRequest.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/blooming/Blooming.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFacade.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingFinder.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingRepository.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/blooming/BloomingService.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/blooming/NewBlooming.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/detail/CafeCategoryItemDetailReadStrategy.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/category/strategy/read/CafeCategoryItemReadStrategy.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafe.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafeFinder.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafeRepository.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/NewFlowerSpotCafe.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListener.kt
  • pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingFacadeTest.kt
  • pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingServiceTest.kt
  • pida-core/core-domain/src/test/kotlin/com/pida/category/CafeCategoryItemDetailReadStrategyTest.kt
  • pida-core/core-domain/src/test/kotlin/com/pida/category/CafeCategoryItemReadStrategyTest.kt
  • pida-core/core-domain/src/test/kotlin/com/pida/category/EventCategoryItemReadStrategyTest.kt
  • pida-core/core-domain/src/test/kotlin/com/pida/category/FlowerSpotCategoryItemReadStrategyTest.kt
  • pida-core/core-domain/src/test/kotlin/com/pida/flowerspot/FlowerSpotFacadeTest.kt
  • pida-core/core-domain/src/test/kotlin/com/pida/flowerspot/perf/FlowerSpotPerformanceBenchmarkRunner.kt
  • pida-core/core-domain/src/test/kotlin/com/pida/notification/bloomed/BloomedFirstVoteNotificationEventListenerTest.kt
  • pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCoreRepository.kt
  • pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCustomRepository.kt
  • pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingEntity.kt
  • pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingJpaRepository.kt
  • pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeCoreRepository.kt
  • pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeEntity.kt
  • pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeJpaRepository.kt
💤 Files with no reviewable changes (9)
  • pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafe.kt
  • pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/MapCategoryItemDetailResponse.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafeRepository.kt
  • pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeJpaRepository.kt
  • pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotCafeCoreRepository.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotCafeFinder.kt
  • pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/category/response/detail/CafeCategoryItemDetailPayloadResponse.kt
  • pida-core/core-api/src/main/kotlin/com/pida/presentation/v2/flowerspotcafe/request/FlowerSpotCafeCreateRequest.kt
  • pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/NewFlowerSpotCafe.kt

Comment on lines +42 to +56
fun recentlyByCafeId(cafeId: Long): List<BloomingEntity> {
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
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there's an existing index on flowerSpotCafeId in migrations or entity annotations
rg -n "flowerSpotCafeId" --type=kt -C3 | rg -i "index|@Column"

# Check for Flyway/Liquibase migrations related to blooming table
fd -e sql -e xml | xargs rg -l -i "blooming" 2>/dev/null | head -5 | xargs cat 2>/dev/null | head -100

Repository: Team-PIDA/Pida-Server

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Search for flowerSpotCafeId references with proper ripgrep syntax
rg "flowerSpotCafeId" -C3 | head -80

# Find BloomingEntity definition
fd -name "BloomingEntity*" -type f

# Find migration files related to blooming
fd -name "*.sql" -o -name "*.xml" | xargs rg -l -i "blooming" 2>/dev/null

Repository: Team-PIDA/Pida-Server

Length of output: 16174


🏁 Script executed:

#!/bin/bash
# Read BloomingEntity to check for index annotations
echo "=== BloomingEntity.kt ===" 
cat -n pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingEntity.kt

# Find migration files related to blooming
echo -e "\n=== Looking for migration files ==="
find . -type f \( -name "*.sql" -o -name "*.xml" \) | head -20

# Search for blooming table migrations
echo -e "\n=== Migration files mentioning 'blooming' ==="
find . -type f \( -name "*.sql" -o -name "*.xml" \) -exec grep -l "blooming" {} \; 2>/dev/null

Repository: Team-PIDA/Pida-Server

Length of output: 1931


🏁 Script executed:

#!/bin/bash
# Search for usages of recentlyByCafeId
echo "=== Usages of recentlyByCafeId ==="
rg "recentlyByCafeId" -C3

# Check docker/init.sql for blooming table definition
echo -e "\n=== docker/init.sql content ==="
cat docker/init.sql | grep -A 20 -i "blooming" || echo "No blooming table in docker/init.sql"

# Check if there are Flyway migration directories
echo -e "\n=== Flyway migration directories ==="
find . -type d -name "db" -o -type d -name "migration*" | head -10

Repository: Team-PIDA/Pida-Server

Length of output: 4053


🏁 Script executed:

#!/bin/bash
# Read the full BloomingCustomRepository to see all query methods
echo "=== BloomingCustomRepository.kt ==="
cat -n pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCustomRepository.kt

Repository: Team-PIDA/Pida-Server

Length of output: 16220


Add a database index on flowerSpotCafeId to improve query performance.

The recentlyByCafeId method (lines 42–56) queries the blooming table filtering on flowerSpotCafeId and createdAt, but the entity currently has no index on flowerSpotCafeId. Additional methods like recentlyByCafeIds and findTodayCafeBloomingByUserId filter on the same column. Without an index, these queries will result in full table scans on large datasets.

Consider adding a composite index on (flowerSpotCafeId, createdAt) to align with the existing indexing strategy for flowerSpotId.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/blooming/BloomingCustomRepository.kt`
around lines 42 - 56, The BloomingEntity is missing an index on
flowerSpotCafeId, causing queries like recentlyByCafeId, recentlyByCafeIds, and
findTodayCafeBloomingByUserId to trigger full table scans; add a database index
(ideally a composite index on flowerSpotCafeId and createdAt) to the
BloomingEntity JPA mapping (e.g., via `@Table`(indexes = [...]) or equivalent) so
the database can use the index for WHERE clauses filtering by flowerSpotCafeId
and createdAt and improve query performance.

@char-yb char-yb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blooming에 cafeId 확인했고, Cache도 잘 적용했네용
LGTM

@LeeHanEum LeeHanEum merged commit c037562 into develop Mar 27, 2026
2 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 1, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

♻️ FlowerSpotCafe 데이터 모델 수정 및 벚꽃 장소 조회 레이턴시 개선

2 participants