diff --git a/be/src/main/java/yeonba/be/chatting/controller/ChattingController.java b/be/src/main/java/yeonba/be/chatting/controller/ChattingController.java index d2faa37b..cb15645e 100644 --- a/be/src/main/java/yeonba/be/chatting/controller/ChattingController.java +++ b/be/src/main/java/yeonba/be/chatting/controller/ChattingController.java @@ -5,96 +5,64 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; -import org.springframework.http.MediaType; +import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; -import yeonba.be.chatting.dto.ChattingRoomResponse; -import yeonba.be.chatting.dto.request.ChattingSendMessageRequest; +import yeonba.be.chatting.dto.response.ChatRoomResponse; +import yeonba.be.chatting.service.ChatService; import yeonba.be.util.CustomResponse; @Tag(name = "Chatting", description = "채팅 API") @RestController +@RequiredArgsConstructor public class ChattingController { - @Operation( - summary = "채팅 목록 조회", - description = "자신이 참여 중인 채팅 목록을 조회할 수 있습니다." - ) - @ApiResponse( - responseCode = "200", - description = "참여 중인 채팅 목록 조회 성공" - ) - @GetMapping("/chattings") - public ResponseEntity>> chattings() { + private final ChatService chatService; - return ResponseEntity - .ok() - .body(new CustomResponse<>()); - } + @Operation(summary = "채팅 목록 조회", description = "자신이 참여 중인 채팅 목록을 조회할 수 있습니다.") + @ApiResponse(responseCode = "200", description = "참여 중인 채팅 목록 조회 성공") + @GetMapping("/chattings") + public ResponseEntity>> getChatRooms( + @RequestAttribute("userId") long userId) { - @Operation( - summary = "채팅 요청", - description = "다른 사용자에게 채팅을 요청할 수 있습니다." - ) - @ApiResponse( - responseCode = "200", - description = "채팅 요청 정상 처리" - ) - @PostMapping("/users/{userId}/chat") - public ResponseEntity> requestChat( - @Parameter(description = "사용자 ID", example = "1") - @PathVariable long userId) { + List response = chatService.getChatRooms(userId); - return ResponseEntity - .ok() - .body(new CustomResponse<>()); - } + return ResponseEntity + .ok() + .body(new CustomResponse<>(response)); + } - @Operation( - summary = "채팅 메시지 전송", - description = "단순 텍스트 형식 채팅 메시지를 전송할 수 있습니다." - ) - @ApiResponse( - responseCode = "202", - description = "채팅 메시지 전송 정상 처리" - ) - @PostMapping("/chattings/{chattingId}/message") - public ResponseEntity> sendMessage( - @Parameter(description = "채팅방 ID", example = "1") - @PathVariable long chattingId, - @RequestBody ChattingSendMessageRequest request) { + @Operation(summary = "채팅 요청", description = "다른 사용자에게 채팅을 요청할 수 있습니다.") + @ApiResponse(responseCode = "200", description = "채팅 요청 정상 처리") + @PostMapping("/users/{partnerId}/chat") + public ResponseEntity> requestChat( + @RequestAttribute("userId") long userId, + @Parameter(description = "사용자 ID", example = "1") + @PathVariable long partnerId) { - return ResponseEntity - .accepted() - .body(new CustomResponse<>()); - } + chatService.requestChat(userId, partnerId); - @Operation( - summary = "사진 메시지 전송", - description = "사진을 전송할 수 있습니다." - ) - @ApiResponse( - responseCode = "202", - description = "사진 전송 정상 처리" - ) - @PostMapping( - path = "/chattings/{chattingId}/photo", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE - ) - public ResponseEntity> sendPhoto( - @Parameter(description = "채팅방 ID", example = "1") - @PathVariable long chattingId, - @Parameter(description = "전송 사진 파일") - @RequestPart(value = "photoFile") MultipartFile photoFile) { + return ResponseEntity + .ok() + .body(new CustomResponse<>()); + } - return ResponseEntity - .accepted() - .body(new CustomResponse<>()); - } + @Operation(summary = "채팅 요청 수락", description = "요청받은 채팅을 수락할 수 있습니다.") + @ApiResponse(responseCode = "200", description = "채팅 요청 수락 정상 처리") + @PostMapping("/notifications/{notificationId}/chat") + public ResponseEntity> acceptRequestedChat( + @RequestAttribute("userId") long userId, + @Parameter(description = "알림 ID", example = "1") + @PathVariable long notificationId) { + + chatService.acceptRequestedChat(userId, notificationId); + + return ResponseEntity + .ok() + .body(new CustomResponse<>()); + } } diff --git a/be/src/main/java/yeonba/be/chatting/dto/ChattingRoomResponse.java b/be/src/main/java/yeonba/be/chatting/dto/ChattingRoomResponse.java deleted file mode 100644 index 6aceb1bd..00000000 --- a/be/src/main/java/yeonba/be/chatting/dto/ChattingRoomResponse.java +++ /dev/null @@ -1,46 +0,0 @@ -package yeonba.be.chatting.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDateTime; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class ChattingRoomResponse { - - @Schema( - type = "number", - description = "채팅방 ID", - example = "1" - ) - private Long id; - - @Schema( - type = "string", - description = "채팅 상대 이름", - example = "김민재" - ) - private String partnerName; - - @Schema( - type = "number", - description = "읽지 않은 메시지 수", - example = "25" - ) - private Integer unreadMessageNumber; - - @Schema( - type = "string", - description = "마지막 메시지", - example = "잘 자!" - ) - private String lastMessage; - - @Schema( - type = "string", - description = "마지막 메시지 일시", - example = "2022-10-11 13:20:15" - ) - private LocalDateTime lastMessageAt; -} diff --git a/be/src/main/java/yeonba/be/chatting/dto/request/ChattingSendMessageRequest.java b/be/src/main/java/yeonba/be/chatting/dto/request/ChattingSendMessageRequest.java index 0e9d9cc1..56fc2c85 100644 --- a/be/src/main/java/yeonba/be/chatting/dto/request/ChattingSendMessageRequest.java +++ b/be/src/main/java/yeonba/be/chatting/dto/request/ChattingSendMessageRequest.java @@ -8,10 +8,10 @@ @AllArgsConstructor public class ChattingSendMessageRequest { - @Schema( - type = "string", - description = "메시지 내용", - example = "밥 먹었어?" - ) - private String content; + @Schema( + type = "string", + description = "메시지 내용", + example = "밥 먹었어?" + ) + private String content; } diff --git a/be/src/main/java/yeonba/be/chatting/dto/response/ChatRoomResponse.java b/be/src/main/java/yeonba/be/chatting/dto/response/ChatRoomResponse.java new file mode 100644 index 00000000..abad6e0b --- /dev/null +++ b/be/src/main/java/yeonba/be/chatting/dto/response/ChatRoomResponse.java @@ -0,0 +1,30 @@ +package yeonba.be.chatting.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; +import yeonba.be.chatting.entity.ChatRoom; + +@Getter +@AllArgsConstructor +public class ChatRoomResponse { + + @Schema(type = "number", description = "채팅방 ID", example = "1") + private long id; + + @Schema(type = "string", description = "채팅 상대 이름", example = "김민재") + private String partnerName; + + @Schema(type = "string", description = "채팅 상대 프로필 이미지 URL", example = "yeonba.com/profile") + private String partnerProfileImageUrl; + + @Schema(type = "number", description = "읽지 않은 메시지 수", example = "25") + private int unreadMessageNumber; + + @Schema(type = "string", description = "마지막 메시지", example = "잘 자!") + private String lastMessage; + + @Schema(type = "string", description = "마지막 메시지 일시", example = "2022-10-11 13:20:15") + private LocalDateTime lastMessageAt; +} diff --git a/be/src/main/java/yeonba/be/chatting/entity/ChatMessage.java b/be/src/main/java/yeonba/be/chatting/entity/ChatMessage.java new file mode 100644 index 00000000..33ba4dfb --- /dev/null +++ b/be/src/main/java/yeonba/be/chatting/entity/ChatMessage.java @@ -0,0 +1,64 @@ +package yeonba.be.chatting.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import yeonba.be.user.entity.User; + +@Table(name = "chat_messages") +@Getter +@Entity +@EntityListeners(value = AuditingEntityListener.class) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ChatMessage { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "chat_room_id") + private ChatRoom chatRoom; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "sent_user_id") + private User sender; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "received_user_id") + private User receiver; + + private String content; + + @Column(name = "is_read") + private boolean read; + + private LocalDateTime sentAt; + + @CreatedDate + private LocalDateTime createdAt; + + private LocalDateTime deletedAt; + + public ChatMessage(ChatRoom chatRoom, User sender, User receiver, String content) { + + this.chatRoom = chatRoom; + this.sender = sender; + this.receiver = receiver; + this.content = content; + this.read = false; + } +} diff --git a/be/src/main/java/yeonba/be/chatting/entity/ChatRoom.java b/be/src/main/java/yeonba/be/chatting/entity/ChatRoom.java new file mode 100644 index 00000000..0d5754e1 --- /dev/null +++ b/be/src/main/java/yeonba/be/chatting/entity/ChatRoom.java @@ -0,0 +1,64 @@ +package yeonba.be.chatting.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import java.util.List; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import yeonba.be.user.entity.User; + +@Table(name = "chat_rooms") +@Getter +@Entity +@EntityListeners(value = AuditingEntityListener.class) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = "id") +public class ChatRoom { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "sent_user_id") + private User sender; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "received_user_id") + private User receiver; + + private boolean active; + + @CreatedDate + private LocalDateTime createdAt; + + private LocalDateTime deletedAt; + + @OneToMany(mappedBy = "chatRoom") + private List chatMessages; + + public ChatRoom(User sender, User receiver) { + + this.sender = sender; + this.receiver = receiver; + this.active = false; + } + + public void activeRoom() { + + this.active = true; + } +} diff --git a/be/src/main/java/yeonba/be/chatting/repository/chatmessage/ChatMessageCommand.java b/be/src/main/java/yeonba/be/chatting/repository/chatmessage/ChatMessageCommand.java new file mode 100644 index 00000000..c7a9e5ae --- /dev/null +++ b/be/src/main/java/yeonba/be/chatting/repository/chatmessage/ChatMessageCommand.java @@ -0,0 +1,17 @@ +package yeonba.be.chatting.repository.chatmessage; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import yeonba.be.chatting.entity.ChatMessage; + +@Component +@RequiredArgsConstructor +public class ChatMessageCommand { + + private final ChatMessageRepository chatMessageRepository; + + public ChatMessage createChatMessage(ChatMessage message) { + + return chatMessageRepository.save(message); + } +} diff --git a/be/src/main/java/yeonba/be/chatting/repository/chatmessage/ChatMessageQuery.java b/be/src/main/java/yeonba/be/chatting/repository/chatmessage/ChatMessageQuery.java new file mode 100644 index 00000000..1ea9beb4 --- /dev/null +++ b/be/src/main/java/yeonba/be/chatting/repository/chatmessage/ChatMessageQuery.java @@ -0,0 +1,22 @@ +package yeonba.be.chatting.repository.chatmessage; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import yeonba.be.chatting.entity.ChatMessage; + +@Component +@RequiredArgsConstructor +public class ChatMessageQuery { + + private final ChatMessageRepository chatMessageRepository; + + public ChatMessage findLastMessageByChatRoomId(long chatRoomId) { + + return chatMessageRepository.findFirstByChatRoomIdOrderBySentAtDesc(chatRoomId); + } + + public int countUnreadMessagesByChatRoomId(long chatRoomId) { + + return chatMessageRepository.countByChatRoomIdAndReadIsFalse(chatRoomId); + } +} diff --git a/be/src/main/java/yeonba/be/chatting/repository/chatmessage/ChatMessageRepository.java b/be/src/main/java/yeonba/be/chatting/repository/chatmessage/ChatMessageRepository.java new file mode 100644 index 00000000..0205b255 --- /dev/null +++ b/be/src/main/java/yeonba/be/chatting/repository/chatmessage/ChatMessageRepository.java @@ -0,0 +1,13 @@ +package yeonba.be.chatting.repository.chatmessage; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import yeonba.be.chatting.entity.ChatMessage; + +@Repository +public interface ChatMessageRepository extends JpaRepository { + + ChatMessage findFirstByChatRoomIdOrderBySentAtDesc(long chatRoomId); + + int countByChatRoomIdAndReadIsFalse(long chatRoomId); +} diff --git a/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomCommand.java b/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomCommand.java new file mode 100644 index 00000000..9089594a --- /dev/null +++ b/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomCommand.java @@ -0,0 +1,17 @@ +package yeonba.be.chatting.repository.chatroom; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import yeonba.be.chatting.entity.ChatRoom; + +@Component +@RequiredArgsConstructor +public class ChatRoomCommand { + + private final ChatRoomRepository chatRoomRepository; + + public ChatRoom createChatRoom(ChatRoom chatRoom) { + + return chatRoomRepository.save(chatRoom); + } +} 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 new file mode 100644 index 00000000..01915f92 --- /dev/null +++ b/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomQuery.java @@ -0,0 +1,28 @@ +package yeonba.be.chatting.repository.chatroom; + +import static yeonba.be.exception.ChatException.NOT_FOUND_CHAT_ROOM; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import yeonba.be.chatting.entity.ChatRoom; +import yeonba.be.exception.GeneralException; +import yeonba.be.user.entity.User; + +@Component +@RequiredArgsConstructor +public class ChatRoomQuery { + + private final ChatRoomRepository chatRoomRepository; + + public List findAllBy(User user) { + + return chatRoomRepository.findAllByUserAndActiveIsTrue(user); + } + + public ChatRoom findBy(User sender, User receiver) { + + return chatRoomRepository.findBySenderAndReceiver(sender, receiver) + .orElseThrow(() -> new GeneralException(NOT_FOUND_CHAT_ROOM)); + } +} 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 new file mode 100644 index 00000000..449890fc --- /dev/null +++ b/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomRepository.java @@ -0,0 +1,13 @@ +package yeonba.be.chatting.repository.chatroom; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import yeonba.be.chatting.entity.ChatRoom; +import yeonba.be.user.entity.User; + +@Repository +public interface ChatRoomRepository extends JpaRepository, ChatRoomRepositoryCustom { + + Optional findBySenderAndReceiver(User sender, User receiver); +} diff --git a/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomRepositoryCustom.java b/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomRepositoryCustom.java new file mode 100644 index 00000000..96508a79 --- /dev/null +++ b/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomRepositoryCustom.java @@ -0,0 +1,10 @@ +package yeonba.be.chatting.repository.chatroom; + +import java.util.List; +import yeonba.be.chatting.entity.ChatRoom; +import yeonba.be.user.entity.User; + +public interface ChatRoomRepositoryCustom { + + List findAllByUserAndActiveIsTrue(User user); +} diff --git a/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomRepositoryImpl.java b/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomRepositoryImpl.java new file mode 100644 index 00000000..cfe23cbe --- /dev/null +++ b/be/src/main/java/yeonba/be/chatting/repository/chatroom/ChatRoomRepositoryImpl.java @@ -0,0 +1,27 @@ +package yeonba.be.chatting.repository.chatroom; + + +import static yeonba.be.chatting.entity.QChatRoom.chatRoom; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.RequiredArgsConstructor; +import yeonba.be.chatting.entity.ChatRoom; +import yeonba.be.user.entity.User; + +@RequiredArgsConstructor +public class ChatRoomRepositoryImpl implements ChatRoomRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List findAllByUserAndActiveIsTrue(User user) { + + return queryFactory.selectFrom(chatRoom) + .where((chatRoom.sender.eq(user) + .or(chatRoom.receiver.eq(user)) + .and(chatRoom.active.eq(true))) + ) + .fetch(); + } +} diff --git a/be/src/main/java/yeonba/be/chatting/service/ChatService.java b/be/src/main/java/yeonba/be/chatting/service/ChatService.java new file mode 100644 index 00000000..078d52fc --- /dev/null +++ b/be/src/main/java/yeonba/be/chatting/service/ChatService.java @@ -0,0 +1,124 @@ +package yeonba.be.chatting.service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import yeonba.be.chatting.dto.response.ChatRoomResponse; +import yeonba.be.chatting.entity.ChatMessage; +import yeonba.be.chatting.entity.ChatRoom; +import yeonba.be.chatting.repository.chatmessage.ChatMessageCommand; +import yeonba.be.chatting.repository.chatmessage.ChatMessageQuery; +import yeonba.be.chatting.repository.chatroom.ChatRoomCommand; +import yeonba.be.chatting.repository.chatroom.ChatRoomQuery; +import yeonba.be.exception.BlockException; +import yeonba.be.exception.GeneralException; +import yeonba.be.exception.NotificationException; +import yeonba.be.notification.entity.Notification; +import yeonba.be.notification.enums.NotificationType; +import yeonba.be.notification.event.NotificationSendEvent; +import yeonba.be.notification.repository.NotificationQuery; +import yeonba.be.user.entity.Block; +import yeonba.be.user.entity.User; +import yeonba.be.user.repository.block.BlockQuery; +import yeonba.be.user.repository.user.UserQuery; + +@Service +@RequiredArgsConstructor +public class ChatService { + + private final ChatRoomCommand chatRoomCommand; + private final ChatRoomQuery chatRoomQuery; + private final ChatMessageCommand chatMessageCommand; + private final ChatMessageQuery chatMessageQuery; + private final UserQuery userQuery; + private final BlockQuery blockQuery; + private final NotificationQuery notificationQuey; + + private final ApplicationEventPublisher eventPublisher; + + @Transactional(readOnly = true) + public List getChatRooms(long userId) { + + User user = userQuery.findById(userId); + + List chatRooms = chatRoomQuery.findAllBy(user); + + return chatRooms.stream() + .map(chatRoom -> toChatRoomResponseBy(chatRoom, user)) + .toList(); + } + + private ChatRoomResponse toChatRoomResponseBy(ChatRoom chatRoom, User user) { + + User partner = chatRoom.getSender().equals(user) ? chatRoom.getReceiver() + : chatRoom.getSender(); + + ChatMessage lastMessage = chatMessageQuery.findLastMessageByChatRoomId(chatRoom.getId()); + + return new ChatRoomResponse(chatRoom.getId(), partner.getNickname(), + partner.getRepresentativeProfilePhoto(), + chatMessageQuery.countUnreadMessagesByChatRoomId(chatRoom.getId()), + lastMessage.getContent(), + lastMessage.getSentAt()); + } + + @Transactional + public void requestChat(long senderId, long receiverId) { + + User sender = userQuery.findById(senderId); + User receiver = userQuery.findById(receiverId); + + // 차단한 사용자인지 검증 + Optional block = blockQuery.findByUser(sender, receiver); + + if (block.isPresent()) { + throw new GeneralException(BlockException.ALREADY_BLOCKED_USER); + } + + // 비활성화된 채팅방 생성 + chatRoomCommand.createChatRoom(new ChatRoom(sender, receiver)); + + NotificationSendEvent notificationSendEvent = new NotificationSendEvent( + NotificationType.CHATTING_REQUESTED, sender, receiver, + LocalDateTime.now()); + + eventPublisher.publishEvent(notificationSendEvent); + } + + public void acceptRequestedChat(long userId, long notificationId) { + + Notification notification = notificationQuey.findById(notificationId); + + // 채팅 요청 알림인지 검증 + if (!notification.getType().isChattingRequest()) { + + throw new GeneralException(NotificationException.IS_NOT_CHATTING_REQUEST_NOTIFICATION); + } + + User sender = userQuery.findById(notification.getSender().getId()); + User receiver = userQuery.findById(notification.getReceiver().getId()); + + // 본인에게 온 채팅 요청인지 검증 + if (receiver.equals(userQuery.findById(userId))) { + + throw new GeneralException(NotificationException.NOT_YOUR_CHATTING_REQUEST_NOTIFICATION); + } + + // 채팅방 활성화 + ChatRoom chatRoom = chatRoomQuery.findBy(sender, receiver); + chatRoom.activeRoom(); + + String activeRoom = "채팅방이 활성화되었습니다."; + chatMessageCommand.createChatMessage(new ChatMessage(chatRoom, sender, receiver, activeRoom)); + + NotificationSendEvent notificationSendEvent = new NotificationSendEvent( + NotificationType.CHATTING_REQUEST_ACCEPTED, receiver, sender, + LocalDateTime.now()); + + eventPublisher.publishEvent(notificationSendEvent); + } +} diff --git a/be/src/main/java/yeonba/be/exception/BlockException.java b/be/src/main/java/yeonba/be/exception/BlockException.java index a626c11c..71609542 100644 --- a/be/src/main/java/yeonba/be/exception/BlockException.java +++ b/be/src/main/java/yeonba/be/exception/BlockException.java @@ -4,31 +4,31 @@ public enum BlockException implements BaseException { - ALREADY_BLOCKED_USER( - HttpStatus.BAD_REQUEST, - "이미 차단한 사용자입니다."), + ALREADY_BLOCKED_USER( + HttpStatus.BAD_REQUEST, + "이미 차단한 사용자입니다."), - NOT_BLOCKED_USER( - HttpStatus.BAD_REQUEST, - "차단한 사용자가 아닙니다."); + NOT_BLOCKED_USER( + HttpStatus.BAD_REQUEST, + "차단한 사용자가 아닙니다."); - private final HttpStatus httpStatus; - private final String reason; + private final HttpStatus httpStatus; + private final String reason; - BlockException(HttpStatus httpStatus, String reason) { - this.httpStatus = httpStatus; - this.reason = reason; - } + BlockException(HttpStatus httpStatus, String reason) { + this.httpStatus = httpStatus; + this.reason = reason; + } - @Override - public HttpStatus getHttpStatus() { + @Override + public HttpStatus getHttpStatus() { - return httpStatus; - } + return httpStatus; + } - @Override - public String getReason() { + @Override + public String getReason() { - return reason; - } + return reason; + } } diff --git a/be/src/main/java/yeonba/be/exception/ChatException.java b/be/src/main/java/yeonba/be/exception/ChatException.java new file mode 100644 index 00000000..f1df029b --- /dev/null +++ b/be/src/main/java/yeonba/be/exception/ChatException.java @@ -0,0 +1,30 @@ +package yeonba.be.exception; + +import org.springframework.http.HttpStatus; + +public enum ChatException implements BaseException { + + NOT_FOUND_CHAT_ROOM( + HttpStatus.BAD_REQUEST, + "요청된 채팅방이 없습니다."); + + private final HttpStatus httpStatus; + private final String reason; + + ChatException(HttpStatus httpStatus, String reason) { + this.httpStatus = httpStatus; + this.reason = reason; + } + + @Override + public HttpStatus getHttpStatus() { + + return httpStatus; + } + + @Override + public String getReason() { + + return reason; + } +} diff --git a/be/src/main/java/yeonba/be/exception/NotificationException.java b/be/src/main/java/yeonba/be/exception/NotificationException.java index 69f342a8..49fb398d 100644 --- a/be/src/main/java/yeonba/be/exception/NotificationException.java +++ b/be/src/main/java/yeonba/be/exception/NotificationException.java @@ -8,6 +8,18 @@ @AllArgsConstructor public enum NotificationException implements BaseException { + NOTIFICATION_NOT_FOUND( + HttpStatus.BAD_REQUEST, + "해당 알림이 존재하지 않습니다."), + + IS_NOT_CHATTING_REQUEST_NOTIFICATION( + HttpStatus.BAD_REQUEST, + "채팅 요청 알림이 아닙니다."), + + NOT_YOUR_CHATTING_REQUEST_NOTIFICATION( + HttpStatus.BAD_REQUEST, + "해당 채팅 요청을 수락할 권한이 없습니다."), + NOTIFICATION_PERMISSION_NOT_FOUND( HttpStatus.BAD_REQUEST, "해당 알림 동의 내역이 존재하지 않습니다."), diff --git a/be/src/main/java/yeonba/be/mypage/service/MyPageService.java b/be/src/main/java/yeonba/be/mypage/service/MyPageService.java index 4b6291e6..395e41db 100644 --- a/be/src/main/java/yeonba/be/mypage/service/MyPageService.java +++ b/be/src/main/java/yeonba/be/mypage/service/MyPageService.java @@ -4,6 +4,7 @@ import java.time.Period; import java.util.List; import java.util.Objects; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -12,6 +13,7 @@ import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import yeonba.be.exception.BlockException; import yeonba.be.exception.GeneralException; import yeonba.be.exception.NotificationException; import yeonba.be.exception.UserException; @@ -33,8 +35,8 @@ import yeonba.be.user.entity.User; import yeonba.be.user.entity.UserPreference; import yeonba.be.user.entity.VocalRange; -import yeonba.be.user.repository.BlockCommand; -import yeonba.be.user.repository.BlockQuery; +import yeonba.be.user.repository.block.BlockCommand; +import yeonba.be.user.repository.block.BlockQuery; import yeonba.be.user.repository.animal.AnimalQuery; import yeonba.be.user.repository.area.AreaQuery; import yeonba.be.user.repository.user.UserQuery; @@ -213,8 +215,13 @@ public void unblockUser(long userId, long blockedUserId) { User user = userQuery.findById(userId); User blockedUser = userQuery.findById(blockedUserId); - Block block = blockQuery.findByUsers(user, blockedUser); - blockCommand.delete(block); + Optional block = blockQuery.findByUser(user, blockedUser); + + if (block.isEmpty()) { + throw new GeneralException(BlockException.NOT_BLOCKED_USER); + } + + blockCommand.delete(block.get()); } @Transactional diff --git a/be/src/main/java/yeonba/be/notification/controller/NotificationController.java b/be/src/main/java/yeonba/be/notification/controller/NotificationController.java index cbc99367..e52938c0 100644 --- a/be/src/main/java/yeonba/be/notification/controller/NotificationController.java +++ b/be/src/main/java/yeonba/be/notification/controller/NotificationController.java @@ -27,13 +27,12 @@ public class NotificationController { @Operation(summary = "최근 받은 알림 목록 조회", description = "최근 받은 알림 목록 조회하며 읽음 처리") @ApiResponse(responseCode = "200", description = "받은 알림 목록 조회 성공") @PatchMapping("/users/notifications") - public ResponseEntity> - getRecentlyReceivedNotifications( + public ResponseEntity> getRecentlyReceivedNotifications( @RequestAttribute("userId") long userId, @Valid @RequestBody(required = false) NotificationPageRequest request) { - NotificationPageResponse response = - notificationService.getRecentlyReceivedNotificationsBy(userId, request); + NotificationPageResponse response = notificationService.getRecentlyReceivedNotificationsBy( + userId, request); return ResponseEntity .ok() @@ -43,11 +42,11 @@ public class NotificationController { @Operation(summary = "읽지 않은 알림 존재 여부 조회", description = "읽지 않은 알림 존재 여부 확인 가능") @ApiResponse(responseCode = "200", description = "읽지 않은 알림 존재 여부 확인 성공") @GetMapping("/users/notifications/unread/exists") - public ResponseEntity> - getUnreadNotificationExistence(@RequestAttribute("userId") long userId) { + public ResponseEntity> getUnreadNotificationExistence( + @RequestAttribute("userId") long userId) { - NotificationUnreadExistResponse response = - notificationService.isUnreadNotificationExist(userId); + NotificationUnreadExistResponse response = notificationService.isUnreadNotificationExist( + userId); return ResponseEntity .ok() 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 d7560d04..150ea41a 100644 --- a/be/src/main/java/yeonba/be/notification/enums/NotificationType.java +++ b/be/src/main/java/yeonba/be/notification/enums/NotificationType.java @@ -18,4 +18,9 @@ public String getFormattedMessage(String username) { return String.format(this.message, username); } + + public boolean isChattingRequest() { + + return this == CHATTING_REQUESTED; + } } diff --git a/be/src/main/java/yeonba/be/notification/repository/NotificationQuery.java b/be/src/main/java/yeonba/be/notification/repository/NotificationQuery.java index 37d33822..3855da13 100644 --- a/be/src/main/java/yeonba/be/notification/repository/NotificationQuery.java +++ b/be/src/main/java/yeonba/be/notification/repository/NotificationQuery.java @@ -1,9 +1,12 @@ package yeonba.be.notification.repository; +import static yeonba.be.exception.NotificationException.NOTIFICATION_NOT_FOUND; + import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; +import yeonba.be.exception.GeneralException; import yeonba.be.notification.entity.Notification; import yeonba.be.user.entity.User; @@ -28,4 +31,10 @@ public Page findRecentlyReceivedNotificationsBy( return notificationRepository.findAllByReceiverOrderByCreatedAtDesc(receiver, pageRequest); } + + public Notification findById(long id) { + + return notificationRepository.findById(id) + .orElseThrow(() -> new GeneralException(NOTIFICATION_NOT_FOUND)); + } } diff --git a/be/src/main/java/yeonba/be/notification/repository/NotificationRepository.java b/be/src/main/java/yeonba/be/notification/repository/NotificationRepository.java index 9222e320..21144959 100644 --- a/be/src/main/java/yeonba/be/notification/repository/NotificationRepository.java +++ b/be/src/main/java/yeonba/be/notification/repository/NotificationRepository.java @@ -1,5 +1,6 @@ package yeonba.be.notification.repository; +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; @@ -17,4 +18,6 @@ public interface NotificationRepository Page findAllByReceiverOrderByCreatedAtDesc( User receiver, PageRequest pageRequest); + + Optional findById(long id); } diff --git a/be/src/main/java/yeonba/be/user/repository/BlockCommand.java b/be/src/main/java/yeonba/be/user/repository/block/BlockCommand.java similarity index 91% rename from be/src/main/java/yeonba/be/user/repository/BlockCommand.java rename to be/src/main/java/yeonba/be/user/repository/block/BlockCommand.java index 60c9c3cf..36568686 100644 --- a/be/src/main/java/yeonba/be/user/repository/BlockCommand.java +++ b/be/src/main/java/yeonba/be/user/repository/block/BlockCommand.java @@ -1,4 +1,4 @@ -package yeonba.be.user.repository; +package yeonba.be.user.repository.block; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/be/src/main/java/yeonba/be/user/repository/BlockQuery.java b/be/src/main/java/yeonba/be/user/repository/block/BlockQuery.java similarity index 66% rename from be/src/main/java/yeonba/be/user/repository/BlockQuery.java rename to be/src/main/java/yeonba/be/user/repository/block/BlockQuery.java index 2687659b..bb959f18 100644 --- a/be/src/main/java/yeonba/be/user/repository/BlockQuery.java +++ b/be/src/main/java/yeonba/be/user/repository/block/BlockQuery.java @@ -1,10 +1,9 @@ -package yeonba.be.user.repository; +package yeonba.be.user.repository.block; import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import yeonba.be.exception.BlockException; -import yeonba.be.exception.GeneralException; import yeonba.be.user.entity.Block; import yeonba.be.user.entity.User; @@ -14,12 +13,9 @@ public class BlockQuery { private final BlockRepository blockRepository; - public Block findByUsers(User user, User blockedUser) { + public Optional findByUser(User user, User blockedUser) { - return blockRepository.findByUserAndBlockedUser(user, blockedUser) - .orElseThrow( - () -> new GeneralException(BlockException.NOT_BLOCKED_USER) - ); + return blockRepository.findByUserAndBlockedUser(user, blockedUser); } public boolean isBlockExist(User user, User blockedUser) { diff --git a/be/src/main/java/yeonba/be/user/repository/BlockRepository.java b/be/src/main/java/yeonba/be/user/repository/block/BlockRepository.java similarity index 92% rename from be/src/main/java/yeonba/be/user/repository/BlockRepository.java rename to be/src/main/java/yeonba/be/user/repository/block/BlockRepository.java index c7e36718..676eb953 100644 --- a/be/src/main/java/yeonba/be/user/repository/BlockRepository.java +++ b/be/src/main/java/yeonba/be/user/repository/block/BlockRepository.java @@ -1,4 +1,4 @@ -package yeonba.be.user.repository; +package yeonba.be.user.repository.block; import java.util.List; import java.util.Optional; diff --git a/be/src/main/java/yeonba/be/user/service/BlockService.java b/be/src/main/java/yeonba/be/user/service/BlockService.java index 576fbc6b..800fe90b 100644 --- a/be/src/main/java/yeonba/be/user/service/BlockService.java +++ b/be/src/main/java/yeonba/be/user/service/BlockService.java @@ -7,8 +7,8 @@ import yeonba.be.exception.GeneralException; import yeonba.be.user.entity.Block; import yeonba.be.user.entity.User; -import yeonba.be.user.repository.BlockCommand; -import yeonba.be.user.repository.BlockQuery; +import yeonba.be.user.repository.block.BlockCommand; +import yeonba.be.user.repository.block.BlockQuery; import yeonba.be.user.repository.user.UserQuery; @Service