diff --git a/src/main/java/com/back/catchmate/domain/chat/controller/ChatRoomController.java b/src/main/java/com/back/catchmate/domain/chat/controller/ChatRoomController.java index 3075423..5e07227 100644 --- a/src/main/java/com/back/catchmate/domain/chat/controller/ChatRoomController.java +++ b/src/main/java/com/back/catchmate/domain/chat/controller/ChatRoomController.java @@ -13,11 +13,10 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; @Tag(name = "채팅방 관련 API") @RestController @@ -47,4 +46,20 @@ public StateResponse leaveChatRoom(@JwtValidation Long userId, public UserInfoList getUsersInChatRoom(@PathVariable Long chatRoomId) { return userChatRoomService.getUserInfoList(chatRoomId); } + + @PatchMapping("/{chatRoomId}/image") + @Operation(summary = "채팅방 이미지 변경 API", description = "채팅방 대표 이미지를 변경하는 API 입니다.") + public StateResponse updateChatRoomImage(@JwtValidation Long userId, + @PathVariable Long chatRoomId, + @RequestParam("chatRoomImage") MultipartFile chatRoomImage) throws IOException { + return chatRoomService.updateChatRoomImage(userId, chatRoomId, chatRoomImage); + } + + @DeleteMapping("/{chatRoomId}/users/{userId}") + @Operation(summary = "채팅방 유저 강제 퇴장 API", description = "채팅방에 참여한 유저를 강제 퇴장하는 API 입니다.") + public StateResponse kickUserFromChatRoom(@JwtValidation Long loginUserId, + @PathVariable Long chatRoomId, + @PathVariable Long userId) { + return chatRoomService.kickUserFromChatRoom(loginUserId, chatRoomId, userId); + } } diff --git a/src/main/java/com/back/catchmate/domain/chat/converter/ChatRoomConverter.java b/src/main/java/com/back/catchmate/domain/chat/converter/ChatRoomConverter.java index f4335ac..c299394 100644 --- a/src/main/java/com/back/catchmate/domain/chat/converter/ChatRoomConverter.java +++ b/src/main/java/com/back/catchmate/domain/chat/converter/ChatRoomConverter.java @@ -11,7 +11,6 @@ import org.springframework.data.domain.Page; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -24,7 +23,9 @@ public ChatRoom toEntity(Board board) { return ChatRoom.builder() .board(board) .participantCount(1) - .lastMessageAt(LocalDateTime.now()) + .lastMessageAt(null) + .lastMessageContent(null) + .chatRoomImage(String.valueOf(board.getClub().getId())) .build(); } @@ -50,6 +51,8 @@ public ChatRoomInfo toChatRoomInfo(ChatRoom chatRoom, Board board) { .boardInfo(boardInfo) .participantCount(chatRoom.getParticipantCount()) .lastMessageAt(chatRoom.getLastMessageAt()) + .lastMessageContent(chatRoom.getLastMessageContent()) + .chatRoomImage(chatRoom.getChatRoomImage()) .build(); } } diff --git a/src/main/java/com/back/catchmate/domain/chat/dto/ChatResponse.java b/src/main/java/com/back/catchmate/domain/chat/dto/ChatResponse.java index d1dd697..b40e27c 100644 --- a/src/main/java/com/back/catchmate/domain/chat/dto/ChatResponse.java +++ b/src/main/java/com/back/catchmate/domain/chat/dto/ChatResponse.java @@ -38,22 +38,24 @@ public static class PagedChatMessageInfo { @Builder @AllArgsConstructor @NoArgsConstructor - public static class PagedChatRoomInfo { - private List chatRoomInfoList; - private Integer totalPages; - private Long totalElements; - private Boolean isFirst; - private Boolean isLast; + public static class ChatRoomInfo { + private Long chatRoomId; + private BoardInfo boardInfo; + private Integer participantCount; + private LocalDateTime lastMessageAt; + private String lastMessageContent; + private String chatRoomImage; } @Getter @Builder @AllArgsConstructor @NoArgsConstructor - public static class ChatRoomInfo { - private Long chatRoomId; - private BoardInfo boardInfo; - private Integer participantCount; - private LocalDateTime lastMessageAt; + public static class PagedChatRoomInfo { + private List chatRoomInfoList; + private Integer totalPages; + private Long totalElements; + private Boolean isFirst; + private Boolean isLast; } } diff --git a/src/main/java/com/back/catchmate/domain/chat/entity/ChatRoom.java b/src/main/java/com/back/catchmate/domain/chat/entity/ChatRoom.java index f6fe572..0bbd709 100644 --- a/src/main/java/com/back/catchmate/domain/chat/entity/ChatRoom.java +++ b/src/main/java/com/back/catchmate/domain/chat/entity/ChatRoom.java @@ -39,10 +39,12 @@ public class ChatRoom extends BaseTimeEntity { @Column(nullable = false) private int participantCount; - // 마지막 메시지 시간 - @Column(nullable = false) private LocalDateTime lastMessageAt; + private String lastMessageContent; + + private String chatRoomImage; + // 채팅방에 메시지가 있을 때 마지막 메시지 시간 업데이트 public void updateLastMessageTime() { this.lastMessageAt = LocalDateTime.now(); @@ -57,4 +59,16 @@ public void incrementParticipantCount() { public void decrementParticipantCount() { this.participantCount--; } + + public boolean isOwner(Long userId) { + return this.board.getUser().getId().equals(userId); + } + + public void updateChatRoomImage(String chatRoomImage) { + this.chatRoomImage = chatRoomImage; + } + + public void updateLastMessageContent(String content) { + this.lastMessageContent = content; + } } diff --git a/src/main/java/com/back/catchmate/domain/chat/repository/UserChatRoomRepository.java b/src/main/java/com/back/catchmate/domain/chat/repository/UserChatRoomRepository.java index 60950b9..8a966a1 100644 --- a/src/main/java/com/back/catchmate/domain/chat/repository/UserChatRoomRepository.java +++ b/src/main/java/com/back/catchmate/domain/chat/repository/UserChatRoomRepository.java @@ -12,7 +12,7 @@ public interface UserChatRoomRepository extends JpaRepository { Optional findByUserIdAndChatRoomId(Long userId, Long chatRoomId); - boolean existsByUserIdAndChatRoomId(Long userId, Long chatRoomId); + boolean existsByUserIdAndChatRoomIdAndDeletedAtIsNull(Long userId, Long chatRoomId); @Query("SELECT ucr FROM UserChatRoom ucr " + "JOIN FETCH ucr.chatRoom cr " + diff --git a/src/main/java/com/back/catchmate/domain/chat/service/ChatRoomService.java b/src/main/java/com/back/catchmate/domain/chat/service/ChatRoomService.java index 447ce16..a7bfa44 100644 --- a/src/main/java/com/back/catchmate/domain/chat/service/ChatRoomService.java +++ b/src/main/java/com/back/catchmate/domain/chat/service/ChatRoomService.java @@ -3,9 +3,16 @@ import com.back.catchmate.domain.chat.dto.ChatResponse.PagedChatRoomInfo; import com.back.catchmate.global.dto.StateResponse; import org.springframework.data.domain.Pageable; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; public interface ChatRoomService { StateResponse leaveChatRoom(Long userId, Long chatRoomId); PagedChatRoomInfo getChatRoomList(Long userId, Pageable pageable); + + StateResponse updateChatRoomImage(Long userId, Long chatRoomId, MultipartFile image) throws IOException; + + StateResponse kickUserFromChatRoom(Long adminId, Long chatRoomId, Long userId); } diff --git a/src/main/java/com/back/catchmate/domain/chat/service/ChatRoomServiceImpl.java b/src/main/java/com/back/catchmate/domain/chat/service/ChatRoomServiceImpl.java index d8ee06a..bb13e72 100644 --- a/src/main/java/com/back/catchmate/domain/chat/service/ChatRoomServiceImpl.java +++ b/src/main/java/com/back/catchmate/domain/chat/service/ChatRoomServiceImpl.java @@ -11,11 +11,15 @@ import com.back.catchmate.global.dto.StateResponse; import com.back.catchmate.global.error.ErrorCode; import com.back.catchmate.global.error.exception.BaseException; +import com.back.catchmate.global.s3.S3Service; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; import static com.back.catchmate.domain.chat.dto.ChatRequest.ChatMessageRequest.MessageType; @@ -23,6 +27,7 @@ @RequiredArgsConstructor public class ChatRoomServiceImpl implements ChatRoomService { private final ChatService chatService; + private final S3Service s3Service; private final UserRepository userRepository; private final ChatRoomRepository chatRoomRepository; private final UserChatRoomRepository userChatRoomRepository; @@ -63,4 +68,42 @@ public StateResponse leaveChatRoom(Long userId, Long chatRoomId) { return new StateResponse(true); } + + @Override + @Transactional + public StateResponse updateChatRoomImage(Long userId, Long chatRoomId, MultipartFile image) throws IOException { + ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) + .orElseThrow(() -> new BaseException(ErrorCode.CHATROOM_NOT_FOUND)); + + if (!chatRoom.isOwner(userId)) { + throw new BaseException(ErrorCode.IMAGE_UPDATE_UNAUTHORIZED_ACCESS); + } + + String imageUrl = s3Service.uploadFile(image); + chatRoom.updateChatRoomImage(imageUrl); + return new StateResponse(true); + } + + @Override + @Transactional + public StateResponse kickUserFromChatRoom(Long loginUserId, Long chatRoomId, Long userId) { + ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) + .orElseThrow(() -> new BaseException(ErrorCode.CHATROOM_NOT_FOUND)); + + if (!chatRoom.isOwner(loginUserId)) { + throw new BaseException(ErrorCode.KICK_CHATROOM_UNAUTHORIZED_ACCESS); + } + + UserChatRoom userChatRoom = userChatRoomRepository.findByUserIdAndChatRoomId(userId, chatRoom.getId()) + .orElseThrow(() -> new BaseException(ErrorCode.USER_CHATROOM_NOT_FOUND)); + + userChatRoom.delete(); + + User user = userRepository.findById(userId) + .orElseThrow(() -> new BaseException(ErrorCode.USER_NOT_FOUND)); + + String content = "방장의 결정으로 " + user.getNickName() + " 님이 채팅방에서 나갔습니다."; + chatService.sendEnterLeaveMessage(chatRoomId, content, userId, MessageType.LEAVE); + return new StateResponse(true); + } } diff --git a/src/main/java/com/back/catchmate/domain/chat/service/ChatServiceImpl.java b/src/main/java/com/back/catchmate/domain/chat/service/ChatServiceImpl.java index 6da6d91..dd9ff09 100644 --- a/src/main/java/com/back/catchmate/domain/chat/service/ChatServiceImpl.java +++ b/src/main/java/com/back/catchmate/domain/chat/service/ChatServiceImpl.java @@ -53,6 +53,7 @@ public void sendChatMessage(Long chatRoomId, ChatMessageRequest request) { ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) .orElseThrow(() -> new BaseException(ErrorCode.CHATROOM_NOT_FOUND)); + chatRoom.updateLastMessageContent(request.getContent()); chatRoom.updateLastMessageTime(); } @@ -86,7 +87,7 @@ public void sendEnterLeaveMessage(Long chatRoomId, String content, Long senderId @Override @Transactional(readOnly = true) public PagedChatMessageInfo getChatMessageList(Long userId, Long chatRoomId, Pageable pageable) { - if (!userChatRoomRepository.existsByUserIdAndChatRoomId(userId, chatRoomId)) { + if (!userChatRoomRepository.existsByUserIdAndChatRoomIdAndDeletedAtIsNull(userId, chatRoomId)) { throw new BaseException(ErrorCode.USER_CHATROOM_NOT_FOUND); } diff --git a/src/main/java/com/back/catchmate/global/error/ErrorCode.java b/src/main/java/com/back/catchmate/global/error/ErrorCode.java index ccb182a..625e234 100644 --- a/src/main/java/com/back/catchmate/global/error/ErrorCode.java +++ b/src/main/java/com/back/catchmate/global/error/ErrorCode.java @@ -51,6 +51,8 @@ public enum ErrorCode { // 파일 FILE_UPLOAD_FAILED(HttpStatus.BAD_REQUEST, "파일 업로드를 실패했습니다."), + IMAGE_UPDATE_UNAUTHORIZED_ACCESS(HttpStatus.UNAUTHORIZED, "채팅방 이미지를 수정할 권한이 없습니다."), + KICK_CHATROOM_UNAUTHORIZED_ACCESS(HttpStatus.UNAUTHORIZED, "채팅방에서 내보낼 권한이 없습니다."), INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다."), BAD_REQUEST(HttpStatus.BAD_REQUEST, "클라이언트 에러입니다"),