diff --git a/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomQuery.java b/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomQuery.java index 681b0339..564937e7 100644 --- a/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomQuery.java +++ b/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomQuery.java @@ -31,4 +31,9 @@ public ChatRoom findBy(User sender, User receiver) { return chatRoomRepository.findBySenderAndReceiver(sender, receiver) .orElseThrow(() -> new GeneralException(NOT_FOUND_CHAT_ROOM)); } + + public boolean existsBy(User sender, User receiver) { + + return chatRoomRepository.existsBySenderAndReceiverAndActiveIsTrue(sender, receiver); + } } diff --git a/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomRepository.java b/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomRepository.java index 449890fc..fbd8aa43 100644 --- a/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomRepository.java +++ b/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomRepository.java @@ -7,7 +7,10 @@ import yeonba.be.user.entity.User; @Repository -public interface ChatRoomRepository extends JpaRepository, ChatRoomRepositoryCustom { +public interface ChatRoomRepository extends JpaRepository, + ChatRoomRepositoryCustom { Optional findBySenderAndReceiver(User sender, User receiver); + + boolean existsBySenderAndReceiverAndActiveIsTrue(User sender, User receiver); } diff --git a/be/src/main/java/yeonba/be/chatting/service/ChatService.java b/be/src/main/java/yeonba/be/chatting/service/ChatService.java index f56ad3f9..028798f8 100644 --- a/be/src/main/java/yeonba/be/chatting/service/ChatService.java +++ b/be/src/main/java/yeonba/be/chatting/service/ChatService.java @@ -19,7 +19,6 @@ import yeonba.be.chatting.repository.chatmessage.ChatMessageQuery; import yeonba.be.chatting.repository.chatroom.ChatRoomCommand; import yeonba.be.chatting.repository.chatroom.ChatRoomQuery; -import yeonba.be.chatting.repository.chatroom.ChatRoomRepository; import yeonba.be.exception.BlockException; import yeonba.be.exception.ChatException; import yeonba.be.exception.GeneralException; @@ -125,6 +124,13 @@ public void requestChat(long senderId, long receiverId) { throw new GeneralException(BlockException.ALREADY_BLOCKED_USER); } + // 이미 채팅 중인 사용자인 지 검증 + boolean chatRoomExist = + chatRoomQuery.existsBy(sender, receiver) || chatRoomQuery.existsBy(receiver, sender); + if (chatRoomExist) { + throw new GeneralException(ChatException.ALREADY_CHAT_USER); + } + // 비활성화된 채팅방 생성 chatRoomCommand.createChatRoom(new ChatRoom(sender, receiver)); diff --git a/be/src/main/java/yeonba/be/exception/ChatException.java b/be/src/main/java/yeonba/be/exception/ChatException.java index 3bb5783b..cc7a5b4c 100644 --- a/be/src/main/java/yeonba/be/exception/ChatException.java +++ b/be/src/main/java/yeonba/be/exception/ChatException.java @@ -10,7 +10,11 @@ public enum ChatException implements BaseException { NOT_FOUND_CHAT_ROOM( HttpStatus.BAD_REQUEST, - "요청된 채팅방이 없습니다."); + "요청된 채팅방이 없습니다."), + + ALREADY_CHAT_USER( + HttpStatus.BAD_REQUEST, + "이미 채팅 중인 사용자입니다."); private final HttpStatus httpStatus; private final String reason; diff --git a/be/src/main/java/yeonba/be/notification/dto/response/ChattingAcceptedNotificationResponse.java b/be/src/main/java/yeonba/be/notification/dto/response/ChattingAcceptedNotificationResponse.java new file mode 100644 index 00000000..883f78bc --- /dev/null +++ b/be/src/main/java/yeonba/be/notification/dto/response/ChattingAcceptedNotificationResponse.java @@ -0,0 +1,44 @@ +package yeonba.be.notification.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; +import lombok.Getter; +import yeonba.be.notification.entity.Notification; +import yeonba.be.user.entity.User; + +@Getter +public class ChattingAcceptedNotificationResponse extends NotificationResponse { + + @Schema( + type = "number", + description = "채팅방 ID", + example = "1") + private long chatRoomId; + + public ChattingAcceptedNotificationResponse( + String notificationType, + String content, + long senderId, + String senderProfilePhotoUrl, + LocalDateTime createdAt, + long chatRoomId) { + + super(notificationType, content, senderId, senderProfilePhotoUrl, createdAt); + this.chatRoomId = chatRoomId; + } + + public static ChattingAcceptedNotificationResponse from(Notification notification, + long chatRoomId) { + + User sender = notification.getSender(); + + return new ChattingAcceptedNotificationResponse( + notification.getType().name(), + notification.getContent(), + sender.getId(), + sender.getRepresentativeProfilePhoto(), + notification.getCreatedAt(), + chatRoomId + ); + } +} diff --git a/be/src/main/java/yeonba/be/notification/dto/response/ChattingRequestedNotificationResponse.java b/be/src/main/java/yeonba/be/notification/dto/response/ChattingRequestedNotificationResponse.java new file mode 100644 index 00000000..d4dfc8a1 --- /dev/null +++ b/be/src/main/java/yeonba/be/notification/dto/response/ChattingRequestedNotificationResponse.java @@ -0,0 +1,43 @@ +package yeonba.be.notification.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; +import lombok.Getter; +import yeonba.be.notification.entity.Notification; +import yeonba.be.user.entity.User; + +@Getter +public class ChattingRequestedNotificationResponse extends NotificationResponse { + + @Schema( + type = "number", + description = "알림 ID", + example = "1") + private long notificationId; + + public ChattingRequestedNotificationResponse( + String notificationType, + String content, + long senderId, + String senderProfilePhotoUrl, + LocalDateTime createdAt, + long notificationId) { + + super(notificationType, content, senderId, senderProfilePhotoUrl, createdAt); + this.notificationId = notificationId; + } + + public static ChattingRequestedNotificationResponse from(Notification notification) { + + User sender = notification.getSender(); + + return new ChattingRequestedNotificationResponse( + notification.getType().name(), + notification.getContent(), + sender.getId(), + sender.getRepresentativeProfilePhoto(), + notification.getCreatedAt(), + notification.getId() + ); + } +} diff --git a/be/src/main/java/yeonba/be/notification/dto/response/NotificationPageResponse.java b/be/src/main/java/yeonba/be/notification/dto/response/NotificationPageResponse.java index 222b1767..b07a1e0d 100644 --- a/be/src/main/java/yeonba/be/notification/dto/response/NotificationPageResponse.java +++ b/be/src/main/java/yeonba/be/notification/dto/response/NotificationPageResponse.java @@ -43,14 +43,11 @@ public class NotificationPageResponse { @JsonProperty("isLast") private boolean last; - public static NotificationPageResponse from(Page page) { - - List content = page.getContent().stream() - .map(NotificationResponse::from) - .toList(); + public static NotificationPageResponse from(Page page, + List notifications) { return new NotificationPageResponse( - content, + notifications, page.getTotalPages(), page.getTotalElements(), page.isFirst(), diff --git a/be/src/main/java/yeonba/be/notification/dto/response/NotificationResponse.java b/be/src/main/java/yeonba/be/notification/dto/response/NotificationResponse.java index a67469b4..904f2907 100644 --- a/be/src/main/java/yeonba/be/notification/dto/response/NotificationResponse.java +++ b/be/src/main/java/yeonba/be/notification/dto/response/NotificationResponse.java @@ -4,20 +4,18 @@ import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Getter; -import yeonba.be.notification.entity.Notification; -import yeonba.be.user.entity.User; @Getter @AllArgsConstructor -public class NotificationResponse { +public abstract class NotificationResponse { @Schema( type = "string", description = """ 알림 타입, 다음 종류 존재 - ARROW_RECEIVED (화살 수신) - - CHAT_REQUESTED (채팅 요청 수신) - - CHAT_REQUEST_ACCEPTED (요청한 채팅 수락됨)""", + - CHATTING_REQUESTED (채팅 요청 수신) + - CHATTING_REQUEST_ACCEPTED (요청한 채팅 수락됨)""", example = "ARROW_RECEIVED") private String notificationType; @@ -39,29 +37,9 @@ public class NotificationResponse { example = "profile-photo/1-0") private String senderProfilePhotoUrl; - @Schema( - type = "string", - description = "알림 보낸 사용자 별명", - example = "안민재") - private String senderNickname; - @Schema( type = "string", description = "알림 생성 일시", example = "2024-04-10 10:12:00.112233") private LocalDateTime createdAt; - - public static NotificationResponse from(Notification notification) { - - User sender = notification.getSender(); - - return new NotificationResponse( - notification.getType().name(), - notification.getContent(), - sender.getId(), - sender.getRepresentativeProfilePhoto(), - sender.getNickname(), - notification.getCreatedAt() - ); - } } diff --git a/be/src/main/java/yeonba/be/notification/dto/response/SimpleNotificationResponse.java b/be/src/main/java/yeonba/be/notification/dto/response/SimpleNotificationResponse.java new file mode 100644 index 00000000..fea9b1bc --- /dev/null +++ b/be/src/main/java/yeonba/be/notification/dto/response/SimpleNotificationResponse.java @@ -0,0 +1,31 @@ +package yeonba.be.notification.dto.response; + +import java.time.LocalDateTime; +import yeonba.be.notification.entity.Notification; +import yeonba.be.user.entity.User; + +public class SimpleNotificationResponse extends NotificationResponse { + + public SimpleNotificationResponse( + String notificationType, + String content, + long senderId, + String senderProfilePhotoUrl, + LocalDateTime createdAt) { + + super(notificationType, content, senderId, senderProfilePhotoUrl, createdAt); + } + + public static SimpleNotificationResponse from(Notification notification) { + + User sender = notification.getSender(); + + return new SimpleNotificationResponse( + notification.getType().name(), + notification.getContent(), + sender.getId(), + sender.getRepresentativeProfilePhoto(), + notification.getCreatedAt() + ); + } +} diff --git a/be/src/main/java/yeonba/be/notification/enums/NotificationType.java b/be/src/main/java/yeonba/be/notification/enums/NotificationType.java index 150ea41a..cad627a9 100644 --- a/be/src/main/java/yeonba/be/notification/enums/NotificationType.java +++ b/be/src/main/java/yeonba/be/notification/enums/NotificationType.java @@ -23,4 +23,9 @@ public boolean isChattingRequest() { return this == CHATTING_REQUESTED; } + + public boolean isChattingAccept() { + + return this == CHATTING_REQUEST_ACCEPTED; + } } diff --git a/be/src/main/java/yeonba/be/notification/service/NotificationService.java b/be/src/main/java/yeonba/be/notification/service/NotificationService.java index 8be4023e..4bcca3f0 100644 --- a/be/src/main/java/yeonba/be/notification/service/NotificationService.java +++ b/be/src/main/java/yeonba/be/notification/service/NotificationService.java @@ -9,11 +9,17 @@ import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import yeonba.be.chatting.entity.ChatRoom; +import yeonba.be.chatting.repository.chatroom.ChatRoomQuery; import yeonba.be.exception.GeneralException; import yeonba.be.exception.NotificationException; import yeonba.be.notification.dto.request.NotificationPageRequest; +import yeonba.be.notification.dto.response.ChattingAcceptedNotificationResponse; +import yeonba.be.notification.dto.response.ChattingRequestedNotificationResponse; import yeonba.be.notification.dto.response.NotificationPageResponse; +import yeonba.be.notification.dto.response.NotificationResponse; import yeonba.be.notification.dto.response.NotificationUnreadExistResponse; +import yeonba.be.notification.dto.response.SimpleNotificationResponse; import yeonba.be.notification.entity.Notification; import yeonba.be.notification.entity.NotificationPermission; import yeonba.be.notification.enums.NotificationType; @@ -32,6 +38,7 @@ public class NotificationService { private final UserQuery userQuery; private final NotificationQuery notificationQuery; private final NotificationPermissionQuery notificationPermissionQuery; + private final ChatRoomQuery chatRoomQuery; private final NotificationCommand notificationCommand; private final NotificationPermissionCommand notificationPermissionCommand; @@ -52,9 +59,10 @@ public NotificationPageResponse getRecentlyReceivedNotificationsBy( User receiver = userQuery.findById(receiverId); Page page = notificationQuery.findRecentlyReceivedNotificationsBy(receiver, pageRequest); + List notifications = page.getContent(); // 가장 최근에 받은 알림 ID 도출 - long mostRecentNotificationId = page.getContent().stream() + long mostRecentNotificationId = notifications.stream() .mapToLong(Notification::getId) .max() .orElse(Long.MAX_VALUE); @@ -62,7 +70,31 @@ public NotificationPageResponse getRecentlyReceivedNotificationsBy( // 가장 최근에 받은 알림 포함 이전 알림 전부 읽음 처리 notificationCommand.readNotificationsUpToIdBy(receiverId, mostRecentNotificationId); - return NotificationPageResponse.from(page); + List response = notifications.stream() + .map(this::toNotificationResponse) + .toList(); + + return NotificationPageResponse.from(page, response); + } + + private NotificationResponse toNotificationResponse(Notification notification) { + + NotificationType type = notification.getType(); + + if (type.isChattingAccept()) { + + ChatRoom chatRoom = chatRoomQuery.findBy(notification.getReceiver(), + notification.getSender()); + + return ChattingAcceptedNotificationResponse.from(notification, chatRoom.getId()); + } + + if (type.isChattingRequest()) { + + return ChattingRequestedNotificationResponse.from(notification); + } + + return SimpleNotificationResponse.from(notification); } @Transactional diff --git a/be/src/main/java/yeonba/be/user/dto/response/UserProfileResponse.java b/be/src/main/java/yeonba/be/user/dto/response/UserProfileResponse.java index b5cb8a3a..3125bb06 100644 --- a/be/src/main/java/yeonba/be/user/dto/response/UserProfileResponse.java +++ b/be/src/main/java/yeonba/be/user/dto/response/UserProfileResponse.java @@ -1,5 +1,6 @@ package yeonba.be.user.dto.response; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; import lombok.AllArgsConstructor; @@ -112,8 +113,15 @@ public class UserProfileResponse { example = "false") private boolean isAlreadySentArrow; + @Schema( + type = "boolean", + description = "채팅 요청 가능 여부", + example = "false") + @JsonProperty("canChat") + private boolean canChat; + public static UserProfileResponse from( - User user, UserPreference userPreference, boolean isAlreadySentArrow) { + User user, UserPreference userPreference, boolean isAlreadySentArrow, boolean canChat) { return new UserProfileResponse( user.getProfilePhotoUrls(), @@ -132,7 +140,8 @@ public static UserProfileResponse from( userPreference.getHeightUpperBound(), userPreference.getMbti(), userPreference.getBodyType(), - isAlreadySentArrow + isAlreadySentArrow, + canChat ); } } diff --git a/be/src/main/java/yeonba/be/user/service/UserService.java b/be/src/main/java/yeonba/be/user/service/UserService.java index 7c28d3b8..961c4fae 100644 --- a/be/src/main/java/yeonba/be/user/service/UserService.java +++ b/be/src/main/java/yeonba/be/user/service/UserService.java @@ -14,6 +14,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import yeonba.be.arrow.repository.ArrowQuery; +import yeonba.be.chatting.repository.chatroom.ChatRoomQuery; import yeonba.be.exception.GeneralException; import yeonba.be.exception.JoinException; import yeonba.be.exception.UserException; @@ -61,6 +62,7 @@ public class UserService { private final AnimalQuery animalQuery; private final AreaQuery areaQuery; private final ArrowQuery arrowQuery; + private final ChatRoomQuery chatRoomQuery; private final UserQuery userQuery; private final UserPreferenceQuery userPreferenceQuery; private final UserRecommendationQuery userRecommendationQuery; @@ -77,8 +79,11 @@ public UserProfileResponse getTargetUserProfile(long userId, long targetUserId) User targetUser = userQuery.findById(targetUserId); UserPreference targetUserPreference = userPreferenceQuery.findByUser(targetUser); boolean isAlreadySentArrow = arrowQuery.isArrowTransactionExist(user, targetUser); + boolean chatRoomExist = + chatRoomQuery.existsBy(user, targetUser) || chatRoomQuery.existsBy(targetUser, user); - return UserProfileResponse.from(targetUser, targetUserPreference, isAlreadySentArrow); + return UserProfileResponse.from(targetUser, targetUserPreference, isAlreadySentArrow, + !chatRoomExist); } public User saveUser(UserJoinRequest request) { @@ -242,7 +247,7 @@ public UserQueryPageResponse findUsersBySearchCondition(long userId, UserSearchRequest request) { int page = 0; - if(Objects.nonNull(request) && Objects.nonNull(request.getPage())) { + if (Objects.nonNull(request) && Objects.nonNull(request.getPage())) { page = request.getPage(); // 검색 나이/키 하한 <= 상한 여부 검증