Skip to content

Commit 51ea390

Browse files
authored
[REFACTOR] 채팅 도메인 요구사항 반영 (#201)
* ♻️refactor: 채팅 도메인 요구사항 반영 - 채팅방 목록 조회 시 썸네일 필드 추가 - 채팅방 상세 조회 시 방장을 나타내는 필드 추가 - 채팅방 목록 조회 시 최근 메시지 순 정렬 적용 * ♻️refactor: 채팅 도메인 요구사항 추가 반영 건 - 모임 조회 시 채팅방 ID도 함께 전달 - 모임 승인제 채팅방 참여 이벤트 추가 - 모임 추방 시 채팅방 탈퇴 이벤트 추가 * ♻️refactor: 채팅 도메인 요구사항 반영 - 기억을 잃어버리고 모임 정보 조회 시 ID를 던지는 로직을 추가를 안하고 커밋을 올려, 재커밋
1 parent 8d4895a commit 51ea390

12 files changed

Lines changed: 148 additions & 15 deletions

File tree

src/main/java/team/wego/wegobackend/chat/application/dto/response/ChatRoomItemResponse.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public record ChatRoomItemResponse(
88
Long chatRoomId,
99
ChatType chatType,
1010
String chatRoomName,
11+
String thumbnail,
1112
Long groupId,
1213
int participantCount,
1314
LastMessageResponse lastMessage,
@@ -17,6 +18,7 @@ public record ChatRoomItemResponse(
1718
public static ChatRoomItemResponse of(
1819
ChatRoom chatRoom,
1920
String chatRoomName,
21+
String thumbnail,
2022
int participantCount,
2123
LastMessageResponse lastMessage,
2224
int unreadCount
@@ -25,6 +27,7 @@ public static ChatRoomItemResponse of(
2527
chatRoom.getId(),
2628
chatRoom.getChatType(),
2729
chatRoomName,
30+
thumbnail,
2831
chatRoom.getGroup() != null ? chatRoom.getGroup().getId() : null,
2932
participantCount,
3033
lastMessage,

src/main/java/team/wego/wegobackend/chat/application/dto/response/ChatRoomResponse.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public record ChatRoomResponse(
99
Long chatRoomId,
1010
ChatType chatType,
1111
String chatRoomName,
12+
String thumbnail,
1213
Long groupId,
1314
int participantCount,
1415
List<ParticipantResponse> participants,
@@ -18,12 +19,14 @@ public record ChatRoomResponse(
1819
public static ChatRoomResponse of(
1920
ChatRoom chatRoom,
2021
String chatRoomName,
22+
String thumbnail,
2123
List<ParticipantResponse> participants
2224
) {
2325
return new ChatRoomResponse(
2426
chatRoom.getId(),
2527
chatRoom.getChatType(),
2628
chatRoomName,
29+
thumbnail,
2730
chatRoom.getGroup() != null ? chatRoom.getGroup().getId() : null,
2831
participants.size(),
2932
participants,

src/main/java/team/wego/wegobackend/chat/application/dto/response/ParticipantResponse.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,17 @@ public record ParticipantResponse(
1010
String nickName,
1111
String profileImage,
1212
ParticipantStatus status,
13+
boolean isOwner,
1314
LocalDateTime joinedAt
1415
) {
15-
public static ParticipantResponse from(ChatParticipant participant) {
16+
public static ParticipantResponse from(ChatParticipant participant, boolean isOwner) {
1617
return new ParticipantResponse(
1718
participant.getId(),
1819
participant.getUser().getId(),
1920
participant.getUser().getNickName(),
2021
participant.getUser().getProfileImage(),
2122
participant.getStatus(),
23+
isOwner,
2224
participant.getJoinedAt()
2325
);
2426
}

src/main/java/team/wego/wegobackend/chat/application/listener/ChatEventListener.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
import team.wego.wegobackend.chat.config.ChatProperties;
1010
import team.wego.wegobackend.chat.domain.entity.JoinType;
1111
import team.wego.wegobackend.group.v2.application.event.GroupCreatedEvent;
12+
import team.wego.wegobackend.group.v2.application.event.GroupJoinApprovedEvent;
1213
import team.wego.wegobackend.group.v2.application.event.GroupJoinedEvent;
14+
import team.wego.wegobackend.group.v2.application.event.GroupJoinKickedEvent;
1315
import team.wego.wegobackend.group.v2.application.event.GroupLeftEvent;
1416

1517
@Component
@@ -68,6 +70,34 @@ public void handleGroupJoined(GroupJoinedEvent event) {
6870
}
6971
}
7072

73+
/**
74+
* 모임 참여 승인 시 채팅방 자동 참여 (승인제 모임)
75+
*/
76+
@EventListener
77+
@Async
78+
public void handleGroupJoinApproved(GroupJoinApprovedEvent event) {
79+
log.info("모임 참여 승인 이벤트 수신 - groupId: {}, targetUserId: {}",
80+
event.groupId(), event.targetUserId());
81+
82+
if (!chatProperties.getAutoJoin().isEnabled()) {
83+
log.debug("자동 참여 비활성화 - groupId: {}", event.groupId());
84+
return;
85+
}
86+
87+
try {
88+
chatRoomService.joinChatRoomByGroup(
89+
event.groupId(),
90+
event.targetUserId(),
91+
JoinType.AUTO
92+
);
93+
log.info("채팅방 자동 참여 완료 (승인) - groupId: {}, userId: {}",
94+
event.groupId(), event.targetUserId());
95+
} catch (Exception e) {
96+
log.error("채팅방 자동 참여 실패 (승인) - groupId: {}, userId: {}",
97+
event.groupId(), event.targetUserId(), e);
98+
}
99+
}
100+
71101
/**
72102
* 모임 퇴장 시 채팅방 퇴장 처리 (선택 사항)
73103
*/
@@ -89,4 +119,26 @@ public void handleGroupLeft(GroupLeftEvent event) {
89119
event.groupId(), event.leaverUserId(), e);
90120
}
91121
}
122+
123+
/**
124+
* 모임 추방 시 채팅방 퇴장 처리
125+
*/
126+
@EventListener
127+
@Async
128+
public void handleGroupKicked(GroupJoinKickedEvent event) {
129+
log.info("모임 추방 이벤트 수신 - groupId: {}, targetUserId: {}",
130+
event.groupId(), event.targetUserId());
131+
132+
try {
133+
chatRoomService.leaveChatRoomByGroup(
134+
event.groupId(),
135+
event.targetUserId()
136+
);
137+
log.info("채팅방 추방 처리 완료 - groupId: {}, userId: {}",
138+
event.groupId(), event.targetUserId());
139+
} catch (Exception e) {
140+
log.error("채팅방 추방 처리 실패 - groupId: {}, userId: {}",
141+
event.groupId(), event.targetUserId(), e);
142+
}
143+
}
92144
}

src/main/java/team/wego/wegobackend/chat/application/service/ChatRoomService.java

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package team.wego.wegobackend.chat.application.service;
22

3+
import java.util.Comparator;
34
import java.util.List;
45
import java.util.stream.Collectors;
56
import lombok.RequiredArgsConstructor;
@@ -24,7 +25,10 @@
2425
import team.wego.wegobackend.chat.domain.repository.ChatMessageRepository;
2526
import team.wego.wegobackend.chat.domain.repository.ChatParticipantRepository;
2627
import team.wego.wegobackend.chat.domain.repository.ChatRoomRepository;
28+
import team.wego.wegobackend.group.v2.domain.entity.GroupImageV2;
29+
import team.wego.wegobackend.group.v2.domain.entity.GroupImageV2VariantType;
2730
import team.wego.wegobackend.group.v2.domain.entity.GroupV2;
31+
import team.wego.wegobackend.group.v2.domain.repository.GroupImageV2Repository;
2832
import team.wego.wegobackend.group.v2.domain.repository.GroupV2Repository;
2933
import team.wego.wegobackend.user.domain.User;
3034
import team.wego.wegobackend.user.repository.UserRepository;
@@ -40,6 +44,7 @@ public class ChatRoomService {
4044
private final ChatMessageRepository chatMessageRepository;
4145
private final UserRepository userRepository;
4246
private final GroupV2Repository groupV2Repository;
47+
private final GroupImageV2Repository groupImageV2Repository;
4348

4449
/**
4550
* 내 채팅방 목록 조회
@@ -49,6 +54,10 @@ public ChatRoomListResponse getMyChatRooms(Long userId) {
4954

5055
List<ChatRoomItemResponse> items = chatRooms.stream()
5156
.map(chatRoom -> buildChatRoomItem(chatRoom, userId))
57+
.sorted(Comparator.comparing(
58+
item -> item.lastMessage() != null ? item.lastMessage().timestamp() : null, //Group 채팅의 경우 lastMessage가 비어있는 경우 존재 -> NPE 처리
59+
Comparator.nullsLast(Comparator.reverseOrder()) // null 처리 + 최신순
60+
))
5261
.collect(Collectors.toList());
5362

5463
return ChatRoomListResponse.from(items);
@@ -61,15 +70,17 @@ public ChatRoomResponse getChatRoom(Long userId, Long roomId) {
6170
ChatRoom chatRoom = findChatRoomById(roomId);
6271
validateParticipant(chatRoom.getId(), userId);
6372

73+
Long hostId = chatRoom.getHostId();
6474
List<ParticipantResponse> participants = chatParticipantRepository
6575
.findActiveParticipants(roomId)
6676
.stream()
67-
.map(ParticipantResponse::from)
77+
.map(p -> ParticipantResponse.from(p, isOwner(p, hostId)))
6878
.collect(Collectors.toList());
6979

7080
String chatRoomName = resolveChatRoomName(chatRoom, userId);
81+
String thumbnail = resolveThumbnail(chatRoom, userId);
7182

72-
return ChatRoomResponse.of(chatRoom, chatRoomName, participants);
83+
return ChatRoomResponse.of(chatRoom, chatRoomName, thumbnail, participants);
7384
}
7485

7586
/**
@@ -79,10 +90,11 @@ public ParticipantListResponse getParticipants(Long userId, Long roomId) {
7990
ChatRoom chatRoom = findChatRoomById(roomId);
8091
validateParticipant(chatRoom.getId(), userId);
8192

93+
Long hostId = chatRoom.getHostId();
8294
List<ParticipantResponse> participants = chatParticipantRepository
8395
.findActiveParticipants(roomId)
8496
.stream()
85-
.map(ParticipantResponse::from)
97+
.map(p -> ParticipantResponse.from(p, isOwner(p, hostId)))
8698
.collect(Collectors.toList());
8799

88100
return ParticipantListResponse.of(roomId, participants);
@@ -268,6 +280,7 @@ private void validateHost(ChatRoom chatRoom, Long userId) {
268280

269281
private ChatRoomItemResponse buildChatRoomItem(ChatRoom chatRoom, Long userId) {
270282
String chatRoomName = resolveChatRoomName(chatRoom, userId);
283+
String thumbnail = resolveThumbnail(chatRoom, userId);
271284
int participantCount = chatParticipantRepository.countActiveParticipants(chatRoom.getId());
272285

273286
LastMessageResponse lastMessage = chatMessageRepository.findLatestByChatRoomId(chatRoom.getId())
@@ -279,19 +292,21 @@ private ChatRoomItemResponse buildChatRoomItem(ChatRoom chatRoom, Long userId) {
279292

280293
int unreadCount = calculateUnreadCount(chatRoom.getId(), userId);
281294

282-
return ChatRoomItemResponse.of(chatRoom, chatRoomName, participantCount, lastMessage, unreadCount);
295+
return ChatRoomItemResponse.of(chatRoom, chatRoomName, thumbnail, participantCount, lastMessage, unreadCount);
283296
}
284297

285298
private ChatRoomResponse buildChatRoomResponse(ChatRoom chatRoom, Long userId) {
286299
String chatRoomName = resolveChatRoomName(chatRoom, userId);
300+
String thumbnail = resolveThumbnail(chatRoom, userId);
287301

302+
Long hostId = chatRoom.getHostId();
288303
List<ParticipantResponse> participants = chatParticipantRepository
289304
.findActiveParticipants(chatRoom.getId())
290305
.stream()
291-
.map(ParticipantResponse::from)
306+
.map(p -> ParticipantResponse.from(p, isOwner(p, hostId)))
292307
.collect(Collectors.toList());
293308

294-
return ChatRoomResponse.of(chatRoom, chatRoomName, participants);
309+
return ChatRoomResponse.of(chatRoom, chatRoomName, thumbnail, participants);
295310
}
296311

297312
private String resolveChatRoomName(ChatRoom chatRoom, Long userId) {
@@ -308,6 +323,39 @@ private String resolveChatRoomName(ChatRoom chatRoom, Long userId) {
308323
.orElse("알 수 없음");
309324
}
310325

326+
private String resolveThumbnail(ChatRoom chatRoom, Long userId) {
327+
if (chatRoom.getChatType() == ChatType.GROUP && chatRoom.getGroup() != null) {
328+
// 그룹 채팅: 그룹의 첫 번째 이미지의 THUMBNAIL_100_100 variant
329+
List<GroupImageV2> images = groupImageV2Repository
330+
.findAllByGroupIdWithVariants(chatRoom.getGroup().getId());
331+
332+
if (images.isEmpty()) {
333+
return null;
334+
}
335+
336+
return images.get(0).getVariants().stream()
337+
.filter(v -> v.getType() == GroupImageV2VariantType.THUMBNAIL_100_100)
338+
.findFirst()
339+
.map(v -> v.getImageUrl())
340+
.orElse(null);
341+
}
342+
343+
// DM인 경우 상대방 프로필 이미지 반환
344+
return chatParticipantRepository.findActiveParticipants(chatRoom.getId())
345+
.stream()
346+
.filter(p -> !p.getUser().getId().equals(userId))
347+
.findFirst()
348+
.map(p -> p.getUser().getProfileImage())
349+
.orElse(null);
350+
}
351+
352+
private boolean isOwner(ChatParticipant participant, Long hostId) {
353+
if (hostId == null) {
354+
return false;
355+
}
356+
return participant.getUser().getId().equals(hostId);
357+
}
358+
311359
private int calculateUnreadCount(Long roomId, Long userId) {
312360
return chatParticipantRepository.findByChatRoomIdAndUserId(roomId, userId)
313361
.filter(ChatParticipant::isActive)

src/main/java/team/wego/wegobackend/chat/config/StompChannelInterceptor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public Message<?> preSend(Message<?> message, MessageChannel channel) {
5353
}
5454
}
5555

56+
//TODO: StompCommand.SUBSCRIBE 분기 처리 로직 추가 (그룹 채팅 참여 검증 로직)
57+
5658
return message;
5759
}
5860

src/main/java/team/wego/wegobackend/chat/domain/entity/ChatRoom.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import lombok.NoArgsConstructor;
2323
import team.wego.wegobackend.common.entity.BaseTimeEntity;
2424
import team.wego.wegobackend.group.v2.domain.entity.GroupV2;
25+
import team.wego.wegobackend.user.domain.User;
2526

2627
@Entity
2728
@Table(name = "chat_room")
@@ -87,6 +88,20 @@ public boolean isDmChat() {
8788
return chatType == ChatType.DM;
8889
}
8990

91+
public boolean isHost(User user) {
92+
if (!isGroupChat() || group == null) {
93+
return false;
94+
}
95+
return group.getHost().getId().equals(user.getId());
96+
}
97+
98+
public Long getHostId() {
99+
if (!isGroupChat() || group == null) {
100+
return null;
101+
}
102+
return group.getHost().getId();
103+
}
104+
90105
public void addParticipant(ChatParticipant participant) {
91106
this.participants.add(participant);
92107
participant.assignToChatRoom(this);
@@ -96,4 +111,5 @@ public void addMessage(ChatMessage message) {
96111
this.messages.add(message);
97112
message.assignToChatRoom(this);
98113
}
114+
99115
}

src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GetGroupV2Response.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,16 @@ public record GetGroupV2Response(
3535
LocalDateTime createdAt,
3636
LocalDateTime updatedAt,
3737
MyMembership myMembership, // 로그인 아니면 null
38-
List<JoinedMember> joinedMembers // Host면 전체, 아니면 ATTEND만
38+
List<JoinedMember> joinedMembers, // Host면 전체, 아니면 ATTEND만
39+
Long chatRoomId // 채팅방 참여를 위한 ID
3940
) {
4041

4142
public static GetGroupV2Response of(
4243
GroupV2 group,
4344
List<GroupImageV2> images,
4445
List<GroupUserV2> users,
45-
Long userIdOrNull
46+
Long userIdOrNull,
47+
Long chatRoomId
4648
) {
4749
// 태그
4850
List<String> tagNames = group.getGroupTags().stream()
@@ -106,7 +108,8 @@ public static GetGroupV2Response of(
106108
group.getCreatedAt(),
107109
group.getUpdatedAt(),
108110
myMembership,
109-
joinedMembers
111+
joinedMembers,
112+
chatRoomId
110113
);
111114
}
112115

src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ public GetGroupV2Response getGroup(Long userId, Long groupId) {
287287
List<GroupImageV2> images = groupImageV2Repository.findAllByGroupIdWithVariants(groupId);
288288
List<GroupUserV2> users = groupUserV2Repository.findAllByGroupIdWithUser(groupId);
289289

290-
return GetGroupV2Response.of(group, images, users, userId);
290+
Long chatRoomId = group.getChatRoom() != null ? group.getChatRoom().getId() : null;
291+
292+
return GetGroupV2Response.of(group, images, users, userId, chatRoomId);
291293
}
292294
}

src/main/java/team/wego/wegobackend/user/domain/User.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,11 @@ public class User extends BaseTimeEntity {
8585
@Column(name = "provider")
8686
private ProviderType provider;
8787

88+
@Builder.Default
8889
@OneToMany(mappedBy = "follower")
8990
private List<Follow> followings = new ArrayList<>();
9091

92+
@Builder.Default
9193
@OneToMany(mappedBy = "followee")
9294
private List<Follow> followers = new ArrayList<>();
9395

@@ -168,7 +170,7 @@ public void updateNotificationEnabled(Boolean flag) {
168170

169171
public void updatedeleted(Boolean flag) {
170172
this.deleted = flag;
171-
}
173+
} //HARD DELETE로 변경
172174

173175
public void updateMbti(String mbti) {
174176
this.mbti = mbti;

0 commit comments

Comments
 (0)