Repository: 3IDLES/idle-server
Pull request #272: [IDLE-571] ChatManager가 Center로서 공동 동작할 수 있도록 사용하는 id값 변경
Author: login: mjj111
Labels: name: 🎯리팩토링
description: 리팩토링 및 고도화 이슈
Created: 2025-05-06T03:14:51Z | Merged: 2025-05-06T06:27:01Z

## 1. 📄 Summary
요양보호사가 Center에게 채팅을 보내면,
 CenterManager들은 본인의 센터와 관련된 채팅을 공동으로 접근하고 사용할 수 있도록 하였습니다. 



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **신규 기능**
    - 채팅 메시지 전송 및 읽기 기능이 보호자와 센터장 역할별로 분리되었습니다. 이제 각각의 역할에 맞는 별도의 엔드포인트(`/send/carer`, `/read/carer`, `/send/center`, `/read/center`)를 통해 채팅을 이용할 수 있습니다.
    - 센터장 인증 정보를 기반으로 센터 ID를 자동으로 조회하는 기능이 추가되어 센터 관련 채팅 관리가 개선되었습니다.
- **버그 수정**
    - 센터 정보가 없을 경우 명확한 오류 메시지가 제공되어 문제 상황 인지가 쉬워졌습니다.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/idle-application/src/main/kotlin/com/swm/idle/application/chat/facade/ChatFacadeService.kt b/idle-application/src/main/kotlin/com/swm/idle/application/chat/facade/ChatFacadeService.kt
index 30e524a8..aa41c96a 100644
--- a/idle-application/src/main/kotlin/com/swm/idle/application/chat/facade/ChatFacadeService.kt
+++ b/idle-application/src/main/kotlin/com/swm/idle/application/chat/facade/ChatFacadeService.kt
@@ -5,10 +5,14 @@ import com.swm.idle.application.chat.domain.ChatRoomService
 import com.swm.idle.application.common.security.getUserAuthentication
 import com.swm.idle.application.notification.domain.DeviceTokenService
 import com.swm.idle.application.user.carer.domain.CarerService
+import com.swm.idle.application.user.center.service.domain.CenterManagerService
 import com.swm.idle.application.user.center.service.domain.CenterService
 import com.swm.idle.domain.chat.event.ChatRedisTemplate
 import com.swm.idle.domain.chat.vo.ChatRoomSummaryInfo
 import com.swm.idle.domain.chat.vo.ReadMessage
+import com.swm.idle.domain.user.center.entity.jpa.CenterManager
+import com.swm.idle.domain.user.center.exception.CenterException
+import com.swm.idle.domain.user.center.vo.BusinessRegistrationNumber
 import com.swm.idle.infrastructure.fcm.chat.ChatNotificationService
 import com.swm.idle.support.common.uuid.UuidCreator
 import com.swm.idle.support.transfer.chat.*
@@ -19,6 +23,7 @@ import java.util.*
 @Service
 @Transactional(readOnly = true)
 class ChatFacadeService(
+    private val centerManagerService: CenterManagerService,
     private val chatRedisTemplate: ChatRedisTemplate,
     private val messageService: ChatMessageService,
     private val notificationService: ChatNotificationService,
@@ -30,34 +35,66 @@ class ChatFacadeService(
 ) {
 
     @Transactional
-    fun sendMessage(request: SendChatMessageRequest, userId: UUID) {
-        val message = messageService.save(request, userId)
+    fun carerSend(request: SendChatMessageRequest, carerId: UUID) {
+        val message = messageService.save(request, carerId)
+        chatRedisTemplate.publish(message)
+
+        for(manager in getManagersByCenterId(UUID.fromString(request.receiverId))) {
+            if (chatRedisTemplate.isChatting(manager.id)) continue
+
+            val token = deviceTokenService.findByUserId(manager.id)
+            notificationService.send(message, request.senderName, token)
+        }
+    }
+
+    private fun getManagersByCenterId(centerId:UUID): List<CenterManager> {
+        val businessNumber = BusinessRegistrationNumber(centerService.getById(centerId).businessRegistrationNumber)
+        val centerManagers = centerManagerService.findAllByCenterBusinessRegistrationNumber(businessNumber)?: emptyList()
+        return centerManagers
+    }
+
+    @Transactional
+    fun centerSend(request: SendChatMessageRequest, managerId: UUID) {
+        val centerId = getCenterId(managerId)
+        val message = messageService.save(request, centerId)
         chatRedisTemplate.publish(message)
 
         if (chatRedisTemplate.isChatting(message.receiverId)) return
+
         val token = deviceTokenService.findByUserId(message.receiverId)
         notificationService.send(message, request.senderName, token)
     }
 
     @Transactional
-    fun readMessage(request: ReadChatMessagesReqeust, userId: UUID) {
-        messageService.read(request, userId)
+    fun carerRead(request: ReadChatMessagesReqeust, carerId: UUID) {
+        messageService.read(request, carerId)
+
+        val readMessage = ReadMessage(
+            chatRoomId = request.chatroomId,
+            receiverId = request.opponentId,
+            readUserId = carerId
+        )
+        chatRedisTemplate.publish(readMessage)
+    }
+
+    @Transactional
+    fun centerRead(request: ReadChatMessagesReqeust, managerId: UUID) {
+        val centerId = getCenterId(managerId)
+        messageService.read(request, centerId)
 
         val readMessage = ReadMessage(
             chatRoomId = request.chatroomId,
             receiverId = request.opponentId,
-            readUserId = userId
+            readUserId = centerId
         )
         chatRedisTemplate.publish(readMessage)
     }
 
     @Transactional
     fun createChatroom(request: CreateChatRoomRequest, isCarer: Boolean):CreateChatRoomResponse {
-        val (carerId, centerId) = if (isCarer) {
-            getUserAuthentication().userId to request.opponentId
-        } else {
-            request.opponentId to getUserAuthentication().userId
-        }
+        val (carerId, centerId) =
+            if (isCarer) getUserAuthentication().userId to request.opponentId
+            else request.opponentId to getCenterIdByAuthentication()
 
         val chatRoomId = chatroomService.create(
                 carerId = carerId,
@@ -67,6 +104,14 @@ class ChatFacadeService(
         return CreateChatRoomResponse(chatRoomId)
     }
 
+    fun getCenterIdByAuthentication():UUID {
+        val managerId = getUserAuthentication().userId
+        val manager = centerManagerService.getById(managerId)
+        val businessNumber = BusinessRegistrationNumber(manager.centerBusinessRegistrationNumber)
+        val center = centerService.findByBusinessRegistrationNumber(businessNumber)?: throw CenterException.NotFoundException()
+        return center.id
+    }
+
     fun getRecentMessages(chatRoomId: UUID, messageId: UUID?): List<ChatMessageResponse> {
         val effectiveMessageId = messageId ?: UuidCreator.create()
 
@@ -75,7 +120,9 @@ class ChatFacadeService(
     }
 
     fun getChatroomSummary(isCarer: Boolean): List<ChatRoomSummaryInfo> {
-        val userId = getUserAuthentication().userId
+        val userId: UUID = if (isCarer) getUserAuthentication().userId
+        else getCenterIdByAuthentication()
+
         val summary = chatroomService.findChatroomSummaries(userId, isCarer)
 
         return if (isCarer) {
@@ -90,4 +137,11 @@ class ChatFacadeService(
             }
         }
     }
+
+    private fun getCenterId(managerId:UUID): UUID {
+        val manager = centerManagerService.getById(managerId)
+        val businessNumber = BusinessRegistrationNumber(manager.centerBusinessRegistrationNumber)
+        val center = centerService.findByBusinessRegistrationNumber(businessNumber)?: throw CenterException.NotFoundException()
+        return center.id
+    }
 }
\ No newline at end of file
diff --git a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatSocketController.kt b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatSocketController.kt
index 03414bb5..d0421a72 100644
--- a/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatSocketController.kt
+++ b/idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatSocketController.kt
@@ -14,17 +14,31 @@ class ChatSocketController (
     private val chatMessageService: ChatFacadeService,
 ) {
 
-    @MessageMapping("/send")
-    fun sendMessage(@Payload request: SendChatMessageRequest,
+    @MessageMapping("/send/carer")
+    fun carerSendMessage(@Payload request: SendChatMessageRequest,
                              headerAccessor: SimpMessageHeaderAccessor) {
         val userId = UUID.fromString(headerAccessor.sessionAttributes?.get("userId") as String)
-        chatMessageService.sendMessage(request, userId)
+        chatMessageService.carerSend(request, userId)
     }
 
-    @MessageMapping("/read")
-    fun read(@Payload request: ReadChatMessagesReqeust,
+    @MessageMapping("/read/carer")
+    fun carerRead(@Payload request: ReadChatMessagesReqeust,
                       headerAccessor: SimpMessageHeaderAccessor) {
         val userId = UUID.fromString(headerAccessor.sessionAttributes?.get("userId") as String)
-        chatMessageService.readMessage(request, userId)
+        chatMessageService.carerRead(request, userId)
+    }
+
+    @MessageMapping("/send/center")
+    fun centerSendMessage(@Payload request: SendChatMessageRequest,
+                    headerAccessor: SimpMessageHeaderAccessor) {
+        val userId = UUID.fromString(headerAccessor.sessionAttributes?.get("userId") as String)
+        chatMessageService.centerSend(request, userId)
+    }
+
+    @MessageMapping("/read/center")
+    fun centerRead(@Payload request: ReadChatMessagesReqeust,
+             headerAccessor: SimpMessageHeaderAccessor) {
+        val userId = UUID.fromString(headerAccessor.sessionAttributes?.get("userId") as String)
+        chatMessageService.centerRead(request, userId)
     }
 }
\ No newline at end of file


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>idle-application/src/main/kotlin/com/swm/idle/application/chat/facade/ChatFacadeService.kt (1)</summary><blockquote>

`27-30`: _⚠️ Potential issue_

**동일 타입 의존성 두 번 주입됨**

`ChatMessageService` 가 `messageService` 와 `chatMessageService` 두 변수로 중복 주입되어 있습니다. Spring 은 같은 빈을 두 번 주입하긴 하지만, 의미상 혼란을 주고 유지보수 시 실수를 유발합니다. 하나로 통일하세요.

```diff
-    private val messageService: ChatMessageService,
-    ...
-    private val chatMessageService: ChatMessageService,
+    private val chatMessageService: ChatMessageService,
```

그리고 `carerSend`, `carerRead` 등에서 모두 `chatMessageService` 로 통일하면 됩니다.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatSocketController.kt (1)</summary><blockquote>

`17-22`: **엔드포인트 분리는 👍, 하지만 공통 처리 로직 중복을 줄여보세요**

역할별 엔드포인트(`/carer`, `/center`) 로 분리한 방향성은 명확하고 이해하기 쉽습니다. 다만, 메서드 네 개가 거의 동일한 흐름(유저 ID 추출 → Facade 호출)을 가지므로 중복을 제거할 여지가 있습니다. 예를 들어 고차 함수나 enum 기반 라우팅을 사용해 중복을 최소화하면 유지보수 비용이 줄어듭니다.  



Also applies to: 24-29, 31-36, 38-43

</blockquote></details>
<details>
<summary>idle-application/src/main/kotlin/com/swm/idle/application/chat/facade/ChatFacadeService.kt (3)</summary><blockquote>

`41-47`: **토큰이 없는 매니저에 대한 예외 처리 필요**

`deviceTokenService.findByUserId()` 가 `null` 을 돌려줄 수 있는 경우 `notificationService.send()` 에서 NPE 가 발생할 수 있습니다. `token ?: continue` 식의 방어 로직을 권장합니다.

---

`49-55`: **`centerManagers()` 호출 비용 최적화**

`carerSend()` 마다 DB 조회가 일어납니다. 트래픽이 많은 방(room)에서 메시지 빈번할 경우 병목이 될 수 있으니, 트랜잭션 범위 내 캐싱(예: `val managers = centerManagers()` 로 캐싱) 또는 레디스 캐시 도입을 고려해 보세요.

---

`133-139`: **`getCenter()` 로직 반복 호출로 인한 불필요한 DB 접근**

`getCenter()` 가 여러 메서드에서 반복 호출되며, 내부적으로 두 번의 DB 접근(센터 매니저 조회 → 센터 조회)을 수행합니다. 한 요청 내에서 동일 센터를 여러 번 사용하므로, Lazy 캐싱 혹은 메서드 인라인 캐싱으로 DB 부하를 줄일 수 있습니다.

```kotlin
private val center by lazy { /* 현재 구현 내용 */ }
```

같이 고려해 보세요.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 3a93a67b4c4f6c79bb833a4b0708f5d9e1f07e28 and 0a08dcf67af814d01c7a303b1f283ec7f8e0d855.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `idle-application/src/main/kotlin/com/swm/idle/application/chat/facade/ChatFacadeService.kt` (6 hunks)
* `idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatSocketController.kt` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Build and analyze

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-06T03:17:57Z

Review comments:
diff_hunk: @@ -14,17 +14,31 @@ class ChatSocketController (
     private val chatMessageService: ChatFacadeService,
 ) {
 
-    @MessageMapping("/send")
-    fun sendMessage(@Payload request: SendChatMessageRequest,
+    @MessageMapping("/send/carer")
+    fun carerSendMessage(@Payload request: SendChatMessageRequest,
                              headerAccessor: SimpMessageHeaderAccessor) {
         val userId = UUID.fromString(headerAccessor.sessionAttributes?.get("userId") as String)
path: idle-presentation/src/main/kotlin/com/swm/idle/presentation/chat/controller/ChatSocketController.kt
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**세션에서 userId 추출 시 NPE(TypeCastException) 가능성 존재**

`headerAccessor.sessionAttributes` 가 `null` 이거나, `"userId"` 키가 없거나, 값이 `String` 이 아니면 `as String` 에서 `TypeCastException` 이 발생합니다. 실운영 중 WebSocket 세션이 끊겼다가 복구되는 상황에서 빈번히 발생할 수 있으므로, 안전한 캐스팅과 예외 처리 로직을 권장합니다.

```diff
-val userId = UUID.fromString(headerAccessor.sessionAttributes?.get("userId") as String)
+val userId = headerAccessor.sessionAttributes
+    ?.get("userId") as? String
+    ?: throw IllegalStateException("userId 가 세션에 존재하지 않습니다.")
+val uuid = UUID.fromString(userId)
```

추가로, 동일한 코드가 네 군데 중복되어 있으니 `private fun extractUserId(header: SimpMessageHeaderAccessor): UUID` 같은 헬퍼 메서드로 묶으면 가독성과 재사용성이 향상됩니다.  



Also applies to: 27-27, 34-34, 41-41

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-06T03:17:56Z
author_association: NONE
diff_hunk: @@ -30,17 +34,38 @@ class ChatFacadeService(
 ) {
 
     @Transactional
-    fun sendMessage(request: SendChatMessageRequest, userId: UUID) {
+    fun carerSend(request: SendChatMessageRequest, userId: UUID) {
         val message = messageService.save(request, userId)
         chatRedisTemplate.publish(message)
 
+        for(centerManager in centerManagers()) {
+            if (chatRedisTemplate.isChatting(centerManager.id)) continue
+
+            val token = deviceTokenService.findByUserId(centerManager.id)
+            notificationService.send(message, request.senderName, token)
+        }
+    }
+
+    private fun centerManagers() = centerManagerService
+        .findAllByCenterBusinessRegistrationNumber(
+            BusinessRegistrationNumber(
+                getCenter().businessRegistrationNumber
+            )
+        )?: emptyList()
+
+    @Transactional
+    fun centerSend(request: SendChatMessageRequest, userId: UUID) {
+        val message = messageService.save(request, getCenter().id)
+        chatRedisTemplate.publish(message)
+
         if (chatRedisTemplate.isChatting(message.receiverId)) return
+
         val token = deviceTokenService.findByUserId(message.receiverId)
         notificationService.send(message, request.senderName, token)
     }
path: idle-application/src/main/kotlin/com/swm/idle/application/chat/facade/ChatFacadeService.kt
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**`centerSend()` 파라미터 `userId` 가 사용되지 않음**

컨트롤러에서 센터 매니저의 `userId` 를 넘기지만, 실제 메서드 내부에서는 `getCenter().id` 만 사용합니다. 불필요한 파라미터는 혼란을 초래하므로 제거하거나 로그/권한 체크 등에 활용하세요.

```diff
-fun centerSend(request: SendChatMessageRequest, userId: UUID) {
+fun centerSend(request: SendChatMessageRequest) {
```

컨트롤러‧인터페이스 변경도 함께 필요합니다.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
    fun centerSend(request: SendChatMessageRequest) {
        val message = messageService.save(request, getCenter().id)
        chatRedisTemplate.publish(message)

        if (chatRedisTemplate.isChatting(message.receiverId)) return

        val token = deviceTokenService.findByUserId(message.receiverId)
        notificationService.send(message, request.senderName, token)
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit e9e07e8
created_at: 2025-05-06T03:17:56Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-06T03:14:57Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

"""
## Walkthrough

채팅 기능이 사용자 역할(요양보호사와 센터 관리자)에 따라 분리되어 처리되도록 서비스와 컨트롤러가 리팩터링되었습니다. 각 역할별로 메시지 전송, 읽기, 채팅방 생성 및 요약 조회 메서드가 별도로 구현되었으며, 엔드포인트도 역할별로 분리되었습니다.

## Changes

| 파일/경로                                                                                       | 변경 요약                                                                                                   |
|------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|
| .../chat/facade/ChatFacadeService.kt                                                           | 채팅 관련 메서드가 요양보호사(carer)와 센터 관리자(center) 역할별로 분리됨. 역할별 메시지 전송/읽기/채팅방 생성/요약 조회 메서드 추가 및 수정. 센터 정보 조회 및 센터 관리자 목록 조회를 위한 private 메서드 추가. 생성자에 centerManagerService 의존성 추가. |
| .../chat/controller/ChatSocketController.kt                                                    | WebSocket 엔드포인트가 `/send/carer`, `/read/carer`, `/send/center`, `/read/center`로 역할별로 분리됨. 각 엔드포인트에서 역할별 서비스 메서드 호출하도록 변경. 기존의 통합 send/read 메서드는 역할별로 분리됨.                       |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Carer
    participant ChatSocketController
    participant ChatFacadeService
    participant CenterManagerService
    participant ChatService
    participant NotificationService

    Carer->>ChatSocketController: /send/carer (SendChatMessageRequest)
    ChatSocketController->>ChatFacadeService: carerSend(request, carerId)
    ChatFacadeService->>ChatService: sendMessage(request, carerId)
    ChatFacadeService->>NotificationService: notifyManagers()
    ChatFacadeService->>CenterManagerService: getManagersByCenterId(centerId)
```

```mermaid
sequenceDiagram
    participant CenterManager
    participant ChatSocketController
    participant ChatFacadeService
    participant CenterService
    participant ChatService
    participant NotificationService

    CenterManager->>ChatSocketController: /send/center (SendChatMessageRequest)
    ChatSocketController->>ChatFacadeService: centerSend(request, managerId)
    ChatFacadeService->>CenterService: getCenterId(managerId)
    ChatFacadeService->>ChatService: sendMessage(request, centerId)
    ChatFacadeService->>NotificationService: notifyCarer()
```

## Possibly related PRs

- #267: ChatFacadeService 내 메서드 시그니처 변경 및 코루틴 제거를 통한 채팅 메시지 저장 및 읽기 로직 단순화, 본 PR과는 역할별 메서드 분리와 관련된 코드 변경으로 연관됨.
"""

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0a08dcf67af814d01c7a303b1f283ec7f8e0d855 and e9e07e83912d53663905f6ca2a35cf11f90b2795.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `idle-application/src/main/kotlin/com/swm/idle/application/chat/facade/ChatFacadeService.kt` (6 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* idle-application/src/main/kotlin/com/swm/idle/application/chat/facade/ChatFacadeService.kt

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Build and analyze

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=3IDLES/idle-server&utm_content=272):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `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 generate unit testing code for this file.`
  -	`@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 generate unit testing code.`
  - `@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](https://www.coderabbit.ai/contact-us/support) 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](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in 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.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-05-06T05:54:02Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=3IDLES_idle-server&pullRequest=272) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=3IDLES_idle-server&pullRequest=272&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=3IDLES_idle-server&pullRequest=272&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=3IDLES_idle-server&pullRequest=272&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=3IDLES_idle-server&pullRequest=272&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=3IDLES_idle-server&pullRequest=272&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=3IDLES_idle-server&pullRequest=272)


author_association: NONE