Skip to content

Conversation

@ggamnunq
Copy link
Contributor

@ggamnunq ggamnunq commented Jul 29, 2025

지도에서 명소 조회 API

  • 현재 위/경도 기준 0.05만큼의 범위 조회

명소 기본 정보 조회 API

  • 명소 기본 정보

명소 실시간 혼잡도 조회 API

  • 현재 시간으로부터 7개의 시간의 혼잡도 조회 ( 3의 배수 단위 )
  • ex) 현재 16시일 경우 15시 기준으로 조회

Summary by CodeRabbit

  • New Features

    • Added real-time and historical congestion information for places, accessible via new and updated endpoints.
    • Introduced endpoints to retrieve detailed place information, including reviews and images.
    • Enhanced map-based search with filtering by place type and location.
  • Improvements

    • Data returned from APIs now includes more precise types for coordinates and congestion levels.
    • Reviews and images are now included in place details for a richer user experience.
    • Congestion data is now segmented by time for improved accuracy.
    • API documentation improved with grouping tags for place, festival, and user-related endpoints.
  • Bug Fixes

    • Fixed endpoint naming for real-time congestion data to improve clarity.

@ggamnunq ggamnunq self-assigned this Jul 29, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 29, 2025

Walkthrough

This update introduces a new service for querying place congestion and mapping data, refactors the controller to delegate logic to this service, and adds or updates repository methods for efficient data retrieval. Data transfer objects are updated for type safety, and Redis utility methods are expanded to support time-segmented congestion caching. Entity relationships and eager fetching strategies are clarified. Swagger annotations are added for API documentation grouping.

Changes

Cohort / File(s) Change Summary
Controller Refactor & Endpoint Update
src/main/kotlin/busanVibe/busan/domain/place/controller/PlaceCongestionController.kt
Controller now uses constructor-injected PlaceCongestionQueryService, implements endpoints to delegate to service, updates endpoint paths, and returns real data instead of null.
Entity Relationship Update
src/main/kotlin/busanVibe/busan/domain/place/domain/Place.kt
Activates and updates the reviews property to a non-nullable list, establishing explicit one-to-many mapping with Review entities.
DTO Type Adjustments
src/main/kotlin/busanVibe/busan/domain/place/dto/PlaceMapResponseDTO.kt
Updates DTO field types: introduces nullable IDs, switches Integer to Int, uses BigDecimal for coordinates, and adjusts congestion and time types for type safety.
Repository Enhancements
src/main/kotlin/busanVibe/busan/domain/place/repository/PlaceRepository.kt
Adds methods for querying places by location and type, and for fetching a place by ID with eager loading of reviews and images.
New Service Implementation
src/main/kotlin/busanVibe/busan/domain/place/service/PlaceCongestionQueryService.kt
Introduces service with methods for map retrieval, place detail fetching, and multi-timepoint congestion calculations, integrating repository and Redis utility usage.
Redis Utility Expansion
src/main/kotlin/busanVibe/busan/domain/place/service/PlaceRedisUtil.kt
Adds time-segmented congestion caching methods, refactors congestion retrieval, introduces logging, and ensures Spring component management.
Review Repository Enhancement
src/main/kotlin/busanVibe/busan/domain/review/domain/repository/ReviewRepository.kt
Adds method to fetch reviews by place with eager user loading using entity graph.
Swagger Tag Annotations
src/main/kotlin/busanVibe/busan/domain/festival/controller/FestivalController.kt, src/main/kotlin/busanVibe/busan/domain/place/controller/PlaceController.kt, src/main/kotlin/busanVibe/busan/domain/user/controller/UserController.kt
Adds OpenAPI/Swagger @Tag annotations to controllers for API documentation grouping.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant PlaceCongestionQueryService
    participant PlaceRepository
    participant ReviewRepository
    participant PlaceRedisUtil

    Client->>Controller: GET /map (type, latitude, longitude)
    Controller->>PlaceCongestionQueryService: getMap(type, latitude, longitude)
    PlaceCongestionQueryService->>PlaceRepository: findPlacesByLocationAndType(...)
    PlaceCongestionQueryService->>PlaceRedisUtil: getRedisCongestion(placeId)
    PlaceCongestionQueryService-->>Controller: MapListDto
    Controller-->>Client: ApiResponse(MapListDto)

    Client->>Controller: GET /place/{placeId}/real-time
    Controller->>PlaceCongestionQueryService: getCongestion(placeId)
    PlaceCongestionQueryService->>PlaceRedisUtil: getTimeCongestion(placeId, dateTime)
    PlaceCongestionQueryService-->>Controller: PlaceCongestionDto
    Controller-->>Client: ApiResponse(PlaceCongestionDto)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • 2025.07.28 #32: Introduces the initial PlaceCongestionController class with stub methods returning null; this PR completes and refines the same controller by implementing its methods and integrating service logic.

Poem

A rabbit hops through code so bright,
Mapping places, day and night.
Congestion tracked by Redis' might,
Reviews and types now set just right.
With DTOs and queries new,
This patch brings features fresh and true!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (9)
src/main/kotlin/busanVibe/busan/domain/place/repository/PlaceRepository.kt (1)

50-58: Method name is misleading - doesn't fetch reviews

The method name findByIdWithReviewAndImage suggests it fetches reviews, but the query only eagerly loads openTime and placeImages. Consider renaming to findByIdWithOpenTimeAndImages for clarity.

-    fun findByIdWithReviewAndImage(@Param("placeId") placeId: Long): Place?
+    fun findByIdWithOpenTimeAndImages(@Param("placeId") placeId: Long): Place?
src/main/kotlin/busanVibe/busan/domain/place/domain/Place.kt (1)

50-51: Consider nullable type or default value for reviews property

The reviews property is declared as non-nullable List<Review> without a default value. This could potentially cause issues if the property is accessed before JPA initializes it, especially in scenarios where the entity is created programmatically.

Consider one of these approaches:

-    val reviews: List<Review>,
+    val reviews: List<Review> = emptyList(),

Or make it nullable:

-    val reviews: List<Review>,
+    val reviews: List<Review>?,
src/main/kotlin/busanVibe/busan/domain/review/domain/repository/ReviewRepository.kt (1)

21-22: Consider consolidating duplicate functionality

The new findByPlace method appears to duplicate the functionality of the existing findForDetails method (lines 12-19). Both methods:

  • Take a Place parameter
  • Return List<Review>
  • Eagerly fetch the user association

Consider whether both methods are necessary or if one approach should be preferred. The @EntityGraph approach is generally cleaner than JOIN FETCH queries.

If the @EntityGraph approach is preferred, consider deprecating or removing findForDetails:

+    @Deprecated("Use findByPlace instead")
     fun findForDetails(@Param("place")place: Place): List<Review>
src/main/kotlin/busanVibe/busan/domain/place/controller/PlaceCongestionController.kt (1)

47-51: Consider implementing or removing the distribution endpoint

The /distribution endpoint still returns null. Should this endpoint be:

  1. Implemented as part of this PR
  2. Removed if not needed
  3. Left as a placeholder for future implementation

Please clarify the intended behavior for this endpoint.

Would you like me to help implement this endpoint or should it be removed from the controller?

src/main/kotlin/busanVibe/busan/domain/place/service/PlaceCongestionQueryService.kt (2)

26-27: Consider making latitude/longitude ranges configurable

The hardcoded range values of 0.05 degrees (~5.5km) might not be suitable for all use cases. Consider making these configurable through application properties to allow flexibility based on different requirements.

+import org.springframework.beans.factory.annotation.Value
+
 @Service
 class PlaceCongestionQueryService(
     private val placeRepository: PlaceRepository,
     private val placeRedisUtil: PlaceRedisUtil,
     private val reviewRepository: ReviewRepository,
+    @Value("\${place.search.latitude-range:0.05}") private val latitudeRange: Double,
+    @Value("\${place.search.longitude-range:0.05}") private val longitudeRange: Double
 ) {
 
-    private val latitudeRange: Double = 0.05
-    private val longitudeRange: Double = 0.05

102-102: Consider using English for log messages

The log messages are in Korean. For better maintainability and consistency, consider using English for log messages.

-        log.info("현재 시간: ${current}시")
+        log.info("Current time: $current")
 
-        log.info("배열 담기 시작")
+        log.info("Starting congestion data collection")
 
-        log.info("배열 담기 끝")
+        log.info("Completed congestion data collection")

Also applies to: 109-109, 120-120

src/main/kotlin/busanVibe/busan/domain/place/service/PlaceRedisUtil.kt (3)

36-41: Optimize redundant Redis call

When setIfAbsent returns false, the existing value is already known to exist. The additional get call is unnecessary.

         if (success == true) {
-            log.info("혼잡도 기록 저장 완료: $key, 저장된 혼잡도: $congestion")
+            log.info("Congestion saved: $key, value: $congestion")
         } else {
-            val existing = redisTemplate.opsForValue().get(key)
-            log.info("이미 존재하는 혼잡도 기록: $key, 기존 혼잡도: $existing")
+            log.info("Congestion already exists: $key")
         }

If you need the existing value for logging, consider using getAndSet or accepting that the value exists without fetching it.


45-59: Potential race condition in concurrent scenarios

In high-concurrency scenarios, there's a small window where setPlaceTimeCongestion might not set the value (if another thread sets it first), and the subsequent get could still return null.

Consider using a more robust approach:

     fun getTimeCongestion(placeId: Long, dateTime: LocalDateTime): Float {
         val roundedHour = (dateTime.hour / 3) * 3
         val key = "place:congestion:$placeId-${dateTime.year}-${dateTime.monthValue}-${dateTime.dayOfMonth}-$roundedHour"
 
         val value = redisTemplate.opsForValue().get(key)
 
         return if (value != null) {
-            log.info("이미 존재하는 혼잡도 기록: $key, 기존 혼잡도: $value")
+            log.info("Existing congestion found: $key, value: $value")
             value.toFloatOrNull() ?: 0f
         } else {
-            setPlaceTimeCongestion(placeId, dateTime.withHour(roundedHour))
-            val newValue = redisTemplate.opsForValue().get(key)
-            newValue?.toFloatOrNull() ?: 0f
+            val congestion = getRandomCongestion()
+            val stored = redisTemplate.opsForValue().setIfAbsent(key, congestion.toString(), Duration.ofHours(24))
+            if (stored == true) {
+                log.info("New congestion created: $key, value: $congestion")
+                congestion
+            } else {
+                // Another thread created it, fetch the value
+                redisTemplate.opsForValue().get(key)?.toFloatOrNull() ?: 0f
+            }
         }
     }

62-64: Consider using Kotlin's Random for better idiomatic code

While Math.random() works, Kotlin's Random is more idiomatic and provides better control.

+import kotlin.random.Random
+
     private fun getRandomCongestion(): Float {
-        return (Math.random() * 4 + 1).toFloat()
+        return Random.nextFloat() * 4 + 1
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c650a51 and 19e7825.

📒 Files selected for processing (7)
  • src/main/kotlin/busanVibe/busan/domain/place/controller/PlaceCongestionController.kt (2 hunks)
  • src/main/kotlin/busanVibe/busan/domain/place/domain/Place.kt (2 hunks)
  • src/main/kotlin/busanVibe/busan/domain/place/dto/PlaceMapResponseDTO.kt (3 hunks)
  • src/main/kotlin/busanVibe/busan/domain/place/repository/PlaceRepository.kt (2 hunks)
  • src/main/kotlin/busanVibe/busan/domain/place/service/PlaceCongestionQueryService.kt (1 hunks)
  • src/main/kotlin/busanVibe/busan/domain/place/service/PlaceRedisUtil.kt (1 hunks)
  • src/main/kotlin/busanVibe/busan/domain/review/domain/repository/ReviewRepository.kt (2 hunks)
🔇 Additional comments (13)
src/main/kotlin/busanVibe/busan/domain/place/repository/PlaceRepository.kt (2)

8-8: LGTM: BigDecimal import for coordinate precision

Good choice using BigDecimal for geographic coordinates to maintain precision in location-based queries.


32-48: LGTM: Well-designed location-based query with conditional type filtering

The query correctly:

  • Uses BETWEEN for efficient coordinate range filtering
  • Implements conditional type filtering with SpEL expression to bypass when type is "ALL"
  • Eagerly fetches openTime association to avoid N+1 queries
  • Uses BigDecimal for coordinate precision
src/main/kotlin/busanVibe/busan/domain/place/domain/Place.kt (1)

5-5: LGTM: Review import added for relationship mapping

src/main/kotlin/busanVibe/busan/domain/review/domain/repository/ReviewRepository.kt (1)

5-5: LGTM: EntityGraph import added

src/main/kotlin/busanVibe/busan/domain/place/dto/PlaceMapResponseDTO.kt (5)

6-6: LGTM: BigDecimal import for coordinate precision

Consistent with the repository layer changes for geographic coordinate handling.


18-18: LGTM: Nullable ID fields improve type safety

Making placeId and id nullable (Long?) is appropriate for optional identifier fields.

Also applies to: 28-28


21-21: LGTM: Kotlin primitive Int for better performance

Replacing boxed Integer with Kotlin primitive Int for congestion levels improves performance and type consistency.

Also applies to: 30-30, 44-44


22-24: LGTM: BigDecimal coordinates for precision

Using BigDecimal for latitude and longitude coordinates ensures precision and consistency with the repository layer's coordinate filtering.

Also applies to: 33-34


32-32: LGTM: Primitive Int for count and time fields

Using Int for reviewAmount and standardTime is appropriate for these numeric fields.

Also applies to: 43-43

src/main/kotlin/busanVibe/busan/domain/place/controller/PlaceCongestionController.kt (3)

5-5: LGTM: Proper service layer separation

Good practice injecting PlaceCongestionQueryService and delegating business logic to the service layer.

Also applies to: 16-18


27-28: LGTM: Service delegation with proper return values

The endpoints now properly delegate to the service layer and return actual data wrapped in ApiResponse instead of null values.

Also applies to: 35-36, 43-44


39-39: LGTM: Improved endpoint URL naming

Renaming from /read-time to /real-time is more intuitive and grammatically correct.

src/main/kotlin/busanVibe/busan/domain/place/service/PlaceCongestionQueryService.kt (1)

124-124: Precision conversion matches DTO type

Confirmed that PlaceCongestionDto.realTimeCongestionLevel is defined as an Int in src/main/kotlin/busanVibe/busan/domain/place/dto/PlaceMapResponseDTO.kt. Using .toInt() aligns with the API contract—no precision-loss issue to address.

Comment on lines +23 to +25
@RequestParam("type", required = false, defaultValue = "ALL") type: PlaceType,
@RequestParam("latitude")latitude: Double,
@RequestParam("longtitude")longtitude: Double): ApiResponse<PlaceMapResponseDTO.MapListDto>?{
@RequestParam("longitude")longtitude: Double): ApiResponse<PlaceMapResponseDTO.MapListDto>{
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix parameter name typo

There's a typo in the parameter name: longtitude should be longitude.

-        @RequestParam("longitude")longtitude: Double): ApiResponse<PlaceMapResponseDTO.MapListDto>{
+        @RequestParam("longitude")longitude: Double): ApiResponse<PlaceMapResponseDTO.MapListDto>{

Also update the service call:

-        val places = placeCongestionQueryService.getMap(type, latitude, longtitude)
+        val places = placeCongestionQueryService.getMap(type, latitude, longitude)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@RequestParam("type", required = false, defaultValue = "ALL") type: PlaceType,
@RequestParam("latitude")latitude: Double,
@RequestParam("longtitude")longtitude: Double): ApiResponse<PlaceMapResponseDTO.MapListDto>?{
@RequestParam("longitude")longtitude: Double): ApiResponse<PlaceMapResponseDTO.MapListDto>{
@RequestParam("type", required = false, defaultValue = "ALL") type: PlaceType,
@RequestParam("latitude")latitude: Double,
@RequestParam("longitude")longitude: Double): ApiResponse<PlaceMapResponseDTO.MapListDto>{
val places = placeCongestionQueryService.getMap(type, latitude, longitude)
//
}
🤖 Prompt for AI Agents
In
src/main/kotlin/busanVibe/busan/domain/place/controller/PlaceCongestionController.kt
around lines 23 to 25, fix the typo in the parameter name by renaming
`longtitude` to `longitude`. Also update any corresponding service calls or
references to use the corrected parameter name `longitude` to ensure consistency
and avoid errors.

Comment on lines +47 to +56
val placeDtoList :List<PlaceMapResponseDTO.PlaceMapInfoDto> = placeList.map {
PlaceMapResponseDTO.PlaceMapInfoDto(
placeId = it.id,
name = it.name,
type = it.type.capitalEnglish,
latitude = it.latitude,
longitude = it.longitude,
congestionLevel = placeRedisUtil.getRedisCongestion(it.id)
)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Potential N+1 performance issue with Redis calls

The current implementation makes individual Redis calls for each place when fetching congestion levels. For large result sets, this could impact performance.

Consider implementing a batch fetch method in PlaceRedisUtil to retrieve congestion data for multiple places in a single operation:

// In PlaceRedisUtil
fun getRedisCongestionBatch(placeIds: List<Long>): Map<Long, Int> {
    val keys = placeIds.map { "place:congestion:$it" }
    val values = redisTemplate.opsForValue().multiGet(keys)
    return placeIds.zip(values ?: emptyList()).associate { (id, value) ->
        id to (value?.toIntOrNull() ?: generateAndStoreCongestion(id))
    }
}

Then update the mapping:

+        val congestionMap = placeRedisUtil.getRedisCongestionBatch(placeList.map { it.id })
         val placeDtoList :List<PlaceMapResponseDTO.PlaceMapInfoDto> = placeList.map {
             PlaceMapResponseDTO.PlaceMapInfoDto(
                 placeId = it.id,
                 name = it.name,
                 type = it.type.capitalEnglish,
                 latitude = it.latitude,
                 longitude = it.longitude,
-                congestionLevel = placeRedisUtil.getRedisCongestion(it.id)
+                congestionLevel = congestionMap[it.id] ?: 0
             )
         }
🤖 Prompt for AI Agents
In
src/main/kotlin/busanVibe/busan/domain/place/service/PlaceCongestionQueryService.kt
between lines 47 and 56, the code currently fetches congestion levels from Redis
one-by-one for each place, causing a potential N+1 performance issue. To fix
this, implement a batch fetch method in PlaceRedisUtil that takes a list of
place IDs and retrieves all congestion data in a single Redis multiGet call.
Then, update the placeDtoList mapping to first collect all place IDs, call this
batch method once, and map the congestion levels from the returned map instead
of calling Redis individually for each place.

latitude = place.latitude,
longitude = place.longitude,
address = place.address,
isOpen = true,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Hardcoded isOpen value doesn't reflect actual status

The isOpen field is hardcoded to true, which doesn't represent the actual open/closed status of the place. The comment mentions "openTime" but it's not being used.

Consider implementing actual open/closed logic based on the place's open times:

isOpen = determineIfOpen(place.openTimes, LocalDateTime.now())
🤖 Prompt for AI Agents
In
src/main/kotlin/busanVibe/busan/domain/place/service/PlaceCongestionQueryService.kt
at line 93, the isOpen field is currently hardcoded to true, which does not
reflect the actual open or closed status of the place. To fix this, replace the
hardcoded true with a call to a function that determines if the place is open
based on its openTimes and the current time, such as isOpen =
determineIfOpen(place.openTimes, LocalDateTime.now()). Ensure the
determineIfOpen function correctly evaluates the openTimes to return the
accurate open status.

Comment on lines 18 to +27
fun getRedisCongestion(placeId: Long?): Int{

val key = "place:congestion:$placeId"
val randomCongestion: Int = (Math.random() * 5 + 1).toInt()
val randomCongestion = getRandomCongestion().toInt().toString()

redisTemplate.opsForValue()
.set(key, randomCongestion.toString())
.set(key, randomCongestion)

return Integer.parseInt(randomCongestion)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Method always overwrites existing congestion values

The current implementation always generates a new random value and overwrites any existing congestion data. This doesn't preserve historical data and makes the congestion values inconsistent across calls.

Consider checking if a value exists before overwriting:

     fun getRedisCongestion(placeId: Long?): Int{
         val key = "place:congestion:$placeId"
-        val randomCongestion = getRandomCongestion().toInt().toString()
-
-        redisTemplate.opsForValue()
-            .set(key, randomCongestion)
-
-        return Integer.parseInt(randomCongestion)
+        
+        val existingValue = redisTemplate.opsForValue().get(key)
+        if (existingValue != null) {
+            return existingValue.toIntOrNull() ?: 0
+        }
+        
+        val randomCongestion = getRandomCongestion().toInt()
+        redisTemplate.opsForValue().set(key, randomCongestion.toString(), Duration.ofHours(1))
+        return randomCongestion
     }
🤖 Prompt for AI Agents
In src/main/kotlin/busanVibe/busan/domain/place/service/PlaceRedisUtil.kt
between lines 18 and 27, the getRedisCongestion method always overwrites
existing congestion values with a new random value, losing any previously stored
data. Modify the method to first check if a congestion value already exists in
Redis for the given placeId; if it does, return that existing value instead of
generating and setting a new one. Only generate and set a new random congestion
value if no existing value is found.

ggamnunq and others added 2 commits July 30, 2025 05:06
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/main/kotlin/busanVibe/busan/domain/place/service/PlaceCongestionQueryService.kt (2)

47-56: N+1 Redis calls issue still present.

This is the same performance issue flagged in the previous review. The code still makes individual Redis calls for each place when fetching congestion levels.


93-93: Hardcoded isOpen value still doesn't reflect actual status.

This is the same issue flagged in the previous review. The isOpen field remains hardcoded to true.

🧹 Nitpick comments (3)
src/main/kotlin/busanVibe/busan/domain/place/service/PlaceCongestionQueryService.kt (3)

26-28: Consider making latitude and longitude ranges configurable.

The hardcoded range values of 0.05 degrees might need adjustment based on different use cases or regions. Consider moving these to application properties for better flexibility.

-    private val latitudeRange: Double = 0.05
-    private val longitudeRange: Double = 0.05
+    @Value("\${place.search.latitude-range:0.05}")
+    private val latitudeRange: Double = 0.05
+    
+    @Value("\${place.search.longitude-range:0.05}")
+    private val longitudeRange: Double = 0.05

Don't forget to import org.springframework.beans.factory.annotation.Value.


104-119: Consider extracting complex time calculation logic.

The time calculation logic is complex and would benefit from being extracted into a separate method for better readability and testability.

+    private fun generateTimePoints(current: LocalDateTime, roundedBase: Int): List<LocalDateTime> {
+        return (6 downTo 0).map { i ->
+            val hour = (roundedBase - i * 3 + 24) % 24
+            current.withHour(hour)
+                .withMinute(0).withSecond(0).withNano(0)
+                .let {
+                    if (hour > current.hour) it.minusDays(1) else it
+                }
+        }
+    }
+
     @Transactional(readOnly = true)
     fun getCongestion(placeId: Long): PlaceMapResponseDTO.PlaceCongestionDto {
 
         val current = LocalDateTime.now()
         log.info("현재 시간: ${current}시")
 
         val roundedBase = (current.hour / 3) * 3
 
-        // 최근 6개 3시간 단위 시간 생성 (기준시간 포함 총 7개)
-        val hours = (6 downTo 0).map { i -> (roundedBase - i * 3 + 24) % 24 }
-
         log.info("배열 담기 시작")
 
-        val byTimePercent: List<Float> = hours.map { hour ->
-            val adjustedDateTime = current.withHour(hour)
-                .withMinute(0).withSecond(0).withNano(0)
-                .let {
-                    if (hour > current.hour) it.minusDays(1) else it
-                }
-            placeRedisUtil.getTimeCongestion(placeId, adjustedDateTime)
-        }
+        val timePoints = generateTimePoints(current, roundedBase)
+        val byTimePercent: List<Float> = timePoints.map { dateTime ->
+            placeRedisUtil.getTimeCongestion(placeId, dateTime)
+        }

122-126: Inconsistent congestion level types.

The byTimePercent list contains Float values while realTimeCongestionLevel is converted to Int. Consider using consistent types for congestion levels throughout the response.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 562b97a and 0c2f278.

📒 Files selected for processing (1)
  • src/main/kotlin/busanVibe/busan/domain/place/service/PlaceCongestionQueryService.kt (1 hunks)
🔇 Additional comments (1)
src/main/kotlin/busanVibe/busan/domain/place/service/PlaceCongestionQueryService.kt (1)

81-82: Good fix for handling empty review set.

The code now properly handles the case when there are no reviews by returning 0f instead of NaN.

@ggamnunq ggamnunq merged commit e6d865a into main Jul 29, 2025
2 checks passed
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.

2 participants