-
Notifications
You must be signed in to change notification settings - Fork 1
[RELEASE] v0.1.9 #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RELEASE] v0.1.9 #84
Changes from all commits
0e618d2
3256169
e34dc4e
2db3032
b11cc34
0791127
ea05a30
e6b5233
f4e43af
cfc04e5
98aaf72
0b9ea3f
5479489
c1b09e6
f9049b5
321c2e0
f9d139e
b526924
4d1ee4d
abc5ad9
c48ea59
846bffc
680c66d
d99a61b
7bbf833
3ded803
4558295
3287ce8
dbc99b7
9226b88
ae15127
2dca8ae
9172399
56e33ce
9f250fa
86869e7
37dbf0b
f6f0257
845745c
b985c3e
ce7da30
a8cf277
2c0f3c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| language: "ko-KR" | ||
|
|
||
| tone_instructions: | | ||
| μ‘΄λλ§μ μ¬μ©ν΄μ£ΌμΈμ. μ½λ 리뷰λ μΉμ νκ³ κ±΄μ€μ μΈ μ΄μ‘°λ‘ μμ±ν΄μ£ΌμΈμ. | ||
| λ¬Έμ μ μ μ§μ ν λλ μ΄μ μ κ°μ λ°©ν₯μ ν¨κ» μ μν΄μ£ΌμΈμ. | ||
|
|
||
| reviews: | ||
| auto_review: | ||
| enabled: true | ||
| drafts: false | ||
| profile: "assertive" | ||
| request_changes_workflow: false | ||
| high_level_summary: true | ||
| poem: false | ||
| review_status: true | ||
| collapse_walkthrough: false | ||
| path_filters: | ||
| - "!**/Q*.java" | ||
| - "!**/*MapperImpl.java" | ||
| - "!**/generated/**" | ||
| - "!**/.claude/**" | ||
| - "!**/build/**" | ||
| - "!**/*.md" | ||
|
|
||
| path_instructions: | ||
| - path: "BE/src/main/java/**/ui/**" | ||
| instructions: | | ||
| - Controller λ€μ΄λ°: `{λμ¬}{λλ©μΈ}Controller` (μ: `CreateRoomController`) | ||
| - Controllerμ `@Transactional` κΈμ§. νΈλμμ μ UseCase κ³μΈ΅μμ μ²λ¦¬. | ||
| - μλ΅μ λ°λμ `BaseResponse.onSuccess(data)` λλ `BaseResponse.onSuccess()` μ¬μ©. | ||
| - μΈμ¦μ΄ νμν μλν¬μΈνΈλ `@CurrentUser` νλΌλ―Έν° μ‘΄μ¬ μ¬λΆ νμΈ. | ||
| - μ λ ₯κ° κ²μ¦μ `@Valid` μ μ© μ¬λΆ νμΈ. | ||
|
|
||
| - path: "BE/src/main/java/**/application/usecase/**" | ||
| instructions: | | ||
| - UseCase λ€μ΄λ°: `{λμ¬}{λλ©μΈ}UseCase` (μ: `CreateRoomUseCase`) | ||
| - λ©μλλͺ μ `execute()` κ³ μ . λ¨, μ΄λ²€νΈ 리μ€λ μν μ κ²Ένλ κ²½μ° `handle()` νμ©. | ||
| - νΈλμμ μ UseCase λ¨μμμ `@Transactional` μ μ©. | ||
| - Cross-domain μμ‘΄ μ μ΄λ²€νΈ λ°ν λ°©μ μ¬μ©. μ§μ λλ©μΈ μλΉμ€ νΈμΆ μ§μ. | ||
| - `@TransactionalEventListener(phase = BEFORE_COMMIT)` μ¬μ© μ λΆλͺ¨ νΈλμμ κ³Ό μμμ± λ³΄μ₯ μ¬λΆ νμΈ. | ||
|
|
||
| - path: "BE/src/main/java/**/domain/**" | ||
| instructions: | | ||
| - Service λ€μ΄λ°: `{λλ©μΈ}Service` (μ: `RoomService`) | ||
| - λλ©μΈ λ μ΄μ΄λ λ€λ₯Έ λλ©μΈμ μ§μ μμ‘΄νλ©΄ μ λ¨. μ΄λ²€νΈ/μΈν°νμ΄μ€λ‘ λΆλ¦¬. | ||
| - μν°ν° μν λ³κ²½μ λλ©μΈ λ©μλλ‘ μΊ‘μν. setter μ§μ νΈμΆ μ§μ. | ||
|
|
||
| - path: "BE/src/main/java/**/application/dto/**" | ||
| instructions: | | ||
| - DTOλ `record` νμ μ¬μ©. | ||
| - μλ΅ DTO μ΄λ¦: `{λλ©μΈ}{λμ}Response` (μ: `RoomDetailResponse`) | ||
| - μμ² DTO μ΄λ¦: `{λμ}{λλ©μΈ}Request` (μ: `CreateRoomRequest`) | ||
|
|
||
| - path: "BE/src/main/java/**/infra/**" | ||
| instructions: | | ||
| - infra λ μ΄μ΄λ domain μΈν°νμ΄μ€ ꡬν체. | ||
| - domain λ μ΄μ΄μ Repository μΈν°νμ΄μ€λ₯Ό ꡬνν λλ `Impl` μ λ―Έμ¬ μ¬μ©. | ||
| - QueryDSL μ¬μ© μ Qν΄λμ€λ μλμμ±μ΄λ―λ‘ μ§μ νΈμ§ κΈμ§. | ||
|
|
||
| - path: "BE/src/main/java/**/global/exception/**" | ||
| instructions: | | ||
| - μλ¬ λ°μ μ `throw new RestApiException({Domain}ErrorStatus.XXX)` νμλ§ μ¬μ©. | ||
| - `RuntimeException` μ§μ throw κΈμ§. | ||
| - ErrorStatus enumμ ν΄λΉ λλ©μΈ ν¨ν€μ§μ `code/status/` νμμ μμΉ. | ||
|
|
||
| - path: "BE/src/main/java/**/global/alert/**" | ||
| instructions: | | ||
| - Discord μλ¦Όμ `SystemAlertPublisher.publish(...)` ν΅ν΄μλ§ λ°ν. | ||
| - CRITICAL: μ¦κ° λμ νμν μλΉμ€ μ₯μ . ERROR: κΈ°λ₯ μ€ν¨. WARN: μ£Όμ νμ. INFO: μ°Έκ³ μ 보. | ||
|
|
||
| - path: "BE/src/test/**" | ||
| instructions: | | ||
| - ν μ€νΈλ μ±κ³΅/μ€ν¨/κ²½κ³κ° μΌμ΄μ€ λͺ¨λ μμ±. | ||
| - UseCase λ¨μ ν μ€νΈ: `src/test/java/.../unit/usecase/` | ||
| - Service λ¨μ ν μ€νΈ: `src/test/java/.../unit/service/` | ||
| - `@Transactional` λ‘€λ°± μ¬μ© μ μλ λͺ μ. | ||
| - Mock λ¨μ© μ§μ. λλ©μΈ λ‘μ§ ν μ€νΈλ μ€μ κ°μ²΄ μ¬μ© κΆμ₯. | ||
|
|
||
| chat: | ||
| auto_reply: true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,4 +52,8 @@ pinpoint-agent/tools/ | |
| .claude | ||
|
|
||
| ### env ### | ||
| .env | ||
| .env | ||
| /analysis/ | ||
|
|
||
| # macOS artefacts | ||
| .DS_Store | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.project.dorumdorum.domain.chat.application.dto.response; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| public record ChatReadReceiptResponse( | ||
| String chatRoomNo, | ||
| String readerUserNo, | ||
| LocalDateTime readAt | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,12 +5,13 @@ | |
| import com.project.dorumdorum.domain.chat.domain.service.ChatRoomService; | ||
| import com.project.dorumdorum.domain.room.application.event.RoomConfirmedEvent; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.dao.DataIntegrityViolationException; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Propagation; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import org.springframework.transaction.event.TransactionPhase; | ||
| import org.springframework.transaction.event.TransactionalEventListener; | ||
|
|
||
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class CreateChatRoomUseCase { | ||
|
|
@@ -22,15 +23,38 @@ public class CreateChatRoomUseCase { | |
| * λ°© μ μ νμ μλ£(RoomConfirmedEvent) μμ β μ±ν λ°© μμ± λ° μ μ μ μ₯ | ||
| * - μ±ν λ°© μμΌλ©΄ μμ± ν λ―Έμ μ₯ λ©€λ² μ μ μ μ₯ | ||
| * - μ±ν λ°© μμΌλ©΄ μμ§ λ―Έμ μ₯μΈ λ©€λ²λ§ μΆκ° (RoommateAcceptedEventλ‘ μΌλΆ μ΄λ―Έ μ μ₯νμ μ μμ) | ||
| * | ||
| * BEFORE_COMMIT: λΆλͺ¨ νΈλμμ (ConfirmRoomAssignmentUseCase)μ μ°Έμ¬. | ||
| * μ±ν λ°© μμ± μ€ν¨ μ λ°© νμ λ λ‘€λ°± β μμμ± λ³΄μ₯. | ||
| * DataIntegrityViolationException: λμ μμ²μΌλ‘ μΈν μ€λ³΅ μμ±/μ μ₯ μ μ¬μ‘°νλ‘ μ²λ¦¬. | ||
| */ | ||
| @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
| @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) | ||
| public void handle(RoomConfirmedEvent event) { | ||
| ChatRoom chatRoom = chatRoomService.findByRoomNo(event.roomNo()) | ||
| .orElseGet(() -> chatRoomService.create(event.roomNo())); | ||
| ChatRoom chatRoom = findOrCreate(event.roomNo()); | ||
|
|
||
| event.allMemberUserNos().stream() | ||
| .filter(userNo -> !chatRoomMemberService.isMember(chatRoom, userNo)) | ||
| .forEach(userNo -> chatRoomMemberService.join(chatRoom, userNo)); | ||
| .forEach(userNo -> joinSafely(chatRoom, userNo)); | ||
| } | ||
|
|
||
| private ChatRoom findOrCreate(String roomNo) { | ||
| return chatRoomService.findByRoomNo(roomNo).orElseGet(() -> { | ||
| try { | ||
| return chatRoomService.create(roomNo); | ||
| } catch (DataIntegrityViolationException e) { | ||
| log.debug("[Chat] μ±ν λ°© μ€λ³΅ μμ± (λμ μμ²). roomNo={}", roomNo); | ||
| return chatRoomService.findByRoomNo(roomNo) | ||
| .orElseThrow(() -> e); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| private void joinSafely(ChatRoom chatRoom, String userNo) { | ||
| try { | ||
| chatRoomMemberService.join(chatRoom, userNo); | ||
| } catch (DataIntegrityViolationException e) { | ||
| log.debug("[Chat] μ±ν λ°© μ€λ³΅ μ μ₯ (λμ μμ²). chatRoomNo={}, userNo={}", | ||
| chatRoom.getChatRoomNo(), userNo); | ||
| } | ||
|
Comment on lines
+52
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: # First, let's find the file mentioned in the review
find . -name "CreateChatRoomUseCase.java" -type fRepository: DorumDorum/BE Length of output: 154 π Script executed: # Also explore the general structure
find . -name "*.java" -path "*/domain/chat/*" -type f | head -20Repository: DorumDorum/BE Length of output: 2001 π Script executed: # Search for ChatRoomMemberService to understand available methods
find . -name "*ChatRoomMemberService*" -type fRepository: DorumDorum/BE Length of output: 244 π Script executed: # Read the CreateChatRoomUseCase.java file to verify the code
wc -l ./src/main/java/com/project/dorumdorum/domain/chat/application/usecase/CreateChatRoomUseCase.javaRepository: DorumDorum/BE Length of output: 157 π Script executed: # Read lines around 52-58 to see the code in question
sed -n '45,65p' ./src/main/java/com/project/dorumdorum/domain/chat/application/usecase/CreateChatRoomUseCase.javaRepository: DorumDorum/BE Length of output: 609 π Script executed: # Read the ChatRoomMemberService to see available methods
cat ./src/main/java/com/project/dorumdorum/domain/chat/domain/service/ChatRoomMemberService.javaRepository: DorumDorum/BE Length of output: 2880 π Script executed: # Check for other usages of joinSafely to understand context
rg "joinSafely" -A 2 -B 2Repository: DorumDorum/BE Length of output: 1362 π Script executed: # Check for other direct calls to chatRoomMemberService.join() that might have similar issues
rg "chatRoomMemberService\.join" -A 2 -B 2Repository: DorumDorum/BE Length of output: 4402 Only ignore verified duplicate-member inserts. The Add a membership verification in the catch block to ensure the join actually succeeded before suppressing the exception: Fix private void joinSafely(ChatRoom chatRoom, String userNo) {
try {
chatRoomMemberService.join(chatRoom, userNo);
} catch (DataIntegrityViolationException e) {
+ if (!chatRoomMemberService.isMember(chatRoom, userNo)) {
+ throw e;
+ }
log.debug("[Chat] μ±ν
λ°© μ€λ³΅ μ
μ₯ (λμ μμ²). chatRoomNo={}, userNo={}",
chatRoom.getChatRoomNo(), userNo);
}
}π€ Prompt for AI Agents |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,13 +11,14 @@ | |
| import com.project.dorumdorum.domain.user.domain.entity.User; | ||
| import com.project.dorumdorum.domain.user.domain.service.UserService; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.dao.DataIntegrityViolationException; | ||
| import org.springframework.messaging.simp.SimpMessagingTemplate; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Propagation; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import org.springframework.transaction.event.TransactionPhase; | ||
| import org.springframework.transaction.event.TransactionalEventListener; | ||
|
|
||
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class JoinChatRoomUseCase { | ||
|
|
@@ -33,28 +34,50 @@ public class JoinChatRoomUseCase { | |
| * - μ±ν λ°© μμΌλ©΄ μμ± ν λ°©μ₯ + μ κ· λ©€λ² μ μ₯ | ||
| * - μ±ν λ°© μμΌλ©΄ μ κ· λ©€λ²λ§ μ μ₯ (μ€λ³΅ λ°©μ§) | ||
| * - μ μ₯ μμ€ν λ©μμ§ μ μ₯ | ||
| * | ||
| * BEFORE_COMMIT: λΆλͺ¨ νΈλμμ (DecideApplicationRequestUseCase.approve)μ μ°Έμ¬. | ||
| * μ±ν λ°© μ μ₯ μ€ν¨ μ λ£Έλ©μ΄νΈ μΉμΈλ λ‘€λ°± β μμμ± λ³΄μ₯. | ||
| * WebSocket λΈλ‘λμΊμ€νΈλ νΈλμμ μΈλΆ μμ μ΄λ―λ‘ μ€ν¨κ° TXμ μν₯μ μ£Όμ§ μλλ‘ λ 립 μ²λ¦¬. | ||
| */ | ||
| @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
| @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: find . -type f -name "JoinChatRoomUseCase.java" 2>/dev/nullRepository: DorumDorum/BE Length of output: 152 π Script executed: cat -n ./src/main/java/com/project/dorumdorum/domain/chat/application/usecase/JoinChatRoomUseCase.javaRepository: DorumDorum/BE Length of output: 4724 π Script executed: rg "convertAndSend" --type java -B 3 -A 1Repository: DorumDorum/BE Length of output: 8674 π Script executed: cat -n ./src/main/java/com/project/dorumdorum/domain/chat/application/event/RoommateKickedEventListener.javaRepository: DorumDorum/BE Length of output: 3864 π Script executed: cat -n ./src/main/java/com/project/dorumdorum/domain/chat/application/usecase/SendGroupChatMessageUseCase.java | head -60Repository: DorumDorum/BE Length of output: 3402 π Script executed: cat -n ./src/main/java/com/project/dorumdorum/domain/chat/application/usecase/SendGroupChatMessageUseCase.java | tail -20Repository: DorumDorum/BE Length of output: 912 Defer the WebSocket send to Both Use Possible fix+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
...
- broadcastSafely(chatRoom, response);
+ TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+ `@Override`
+ public void afterCommit() {
+ broadcastSafely(chatRoom, response);
+ }
+ });
...π€ Prompt for AI Agents |
||
| public void handle(RoommateAcceptedEvent event) { | ||
| ChatRoom chatRoom = chatRoomService.findByRoomNo(event.roomNo()) | ||
| .orElseGet(() -> { | ||
| ChatRoom created = chatRoomService.create(event.roomNo()); | ||
| chatRoomMemberService.join(created, event.hostUserNo()); | ||
| try { | ||
| chatRoomMemberService.join(created, event.hostUserNo()); | ||
| } catch (DataIntegrityViolationException e) { | ||
| log.debug("[Chat] λ°©μ₯ μ€λ³΅ μ μ₯ (λμ μμ²). roomNo={}, hostUserNo={}", | ||
| event.roomNo(), event.hostUserNo()); | ||
| } | ||
| return created; | ||
| }); | ||
|
|
||
| if (!chatRoomMemberService.isMember(chatRoom, event.acceptedUserNo())) { | ||
| chatRoomMemberService.join(chatRoom, event.acceptedUserNo()); | ||
| try { | ||
| chatRoomMemberService.join(chatRoom, event.acceptedUserNo()); | ||
| } catch (DataIntegrityViolationException e) { | ||
| log.debug("[Chat] μ κ· λ©€λ² μ€λ³΅ μ μ₯ (λμ μμ²). roomNo={}, userNo={}", | ||
| event.roomNo(), event.acceptedUserNo()); | ||
| return; | ||
| } | ||
|
Comment on lines
+47
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: fd -type f -name "JoinChatRoomUseCase.java"Repository: DorumDorum/BE Length of output: 227 π Script executed: cat -n src/main/java/com/project/dorumdorum/domain/chat/application/usecase/JoinChatRoomUseCase.javaRepository: DorumDorum/BE Length of output: 4724 π Script executed: fd -type f -name "*ChatRoomMember*" | head -20Repository: DorumDorum/BE Length of output: 227 π Script executed: rg "DataIntegrityViolationException" -A 5 -B 5 src/Repository: DorumDorum/BE Length of output: 43750 Re-check membership after exception to confirm duplicate, not other constraint failure. Lines 49 and 59 catch Suggested fix try {
chatRoomMemberService.join(created, event.hostUserNo());
} catch (DataIntegrityViolationException e) {
+ if (!chatRoomMemberService.isMember(created, event.hostUserNo())) {
+ throw e;
+ }
log.debug("[Chat] λ°©μ₯ μ€λ³΅ μ
μ₯ (λμ μμ²). roomNo={}, hostUserNo={}",
event.roomNo(), event.hostUserNo());
}
...
try {
chatRoomMemberService.join(chatRoom, event.acceptedUserNo());
} catch (DataIntegrityViolationException e) {
+ if (!chatRoomMemberService.isMember(chatRoom, event.acceptedUserNo())) {
+ throw e;
+ }
log.debug("[Chat] μ κ· λ©€λ² μ€λ³΅ μ
μ₯ (λμ μμ²). roomNo={}, userNo={}",
event.roomNo(), event.acceptedUserNo());
return;
}π€ Prompt for AI Agents |
||
| User accepted = userService.findById(event.acceptedUserNo()); | ||
| String displayName = (accepted.getNickname() != null && !accepted.getNickname().isBlank()) | ||
| ? accepted.getNickname() : accepted.getName(); | ||
| String content = displayName + "κ° μ μ₯νμ΅λλ€."; | ||
| ChatMessage message = chatMessageService.save(chatRoom, "SYSTEM", content, MessageType.SYSTEM, 0); | ||
| ChatMessageResponse response = new ChatMessageResponse( | ||
| message.getMessageNo(), chatRoom.getChatRoomNo(), | ||
| "SYSTEM", null, content, MessageType.SYSTEM.name(), message.getCreatedAt()); | ||
| "SYSTEM", null, content, MessageType.SYSTEM.name(), message.getCreatedAt(), message.getUnreadCount()); | ||
| broadcastSafely(chatRoom, response); | ||
| } | ||
| } | ||
|
|
||
| private void broadcastSafely(ChatRoom chatRoom, ChatMessageResponse response) { | ||
| try { | ||
| messagingTemplate.convertAndSend("/topic/chat-room/" + chatRoom.getChatRoomNo(), response); | ||
| } catch (Exception e) { | ||
| log.warn("[Chat] WebSocket λΈλ‘λμΊμ€νΈ μ€ν¨. chatRoomNo={}", chatRoom.getChatRoomNo(), e); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π§© Analysis chain
π Script executed:
Repository: DorumDorum/BE
Length of output: 3864
π Script executed:
rg -n "TransactionalEventListener|broadcastSafely|convertAndSend" --type java src/main/java/com/project/dorumdorum/domain/chat/Repository: DorumDorum/BE
Length of output: 2896
π Script executed:
Repository: DorumDorum/BE
Length of output: 39
π Script executed:
Repository: DorumDorum/BE
Length of output: 1612
π Script executed:
rg -n "TransactionSynchronizationManager" --type java src/Repository: DorumDorum/BE
Length of output: 39
π Script executed:
cat -n src/main/java/com/project/dorumdorum/domain/chat/application/usecase/JoinChatRoomUseCase.java | head -85Repository: DorumDorum/BE
Length of output: 4724
Move the WebSocket send to
afterCommit.broadcastSafely(...)is invoked from aBEFORE_COMMITlistener, so a later flush/commit failure can roll back the DB changes after clients already received the frame. Keep the DB work in this transaction, but register the broadcast forafterCommitusingTransactionSynchronizationManager.registerSynchronization().Suggested fix
Also applies to: 54-64
π€ Prompt for AI Agents