Skip to content

Commit

Permalink
Merge pull request #70 from uju-in/LIME-141-chatroom-feat
Browse files Browse the repository at this point in the history
[LIME-141] 채팅방 목록 조회, 채팅방 참여, 채팅방 인원수 조회, 채팅방 나가기 기능 구현
  • Loading branch information
Curry4182 authored Mar 23, 2024
2 parents 20723b1 + 2bcd9c6 commit ab7bcc1
Show file tree
Hide file tree
Showing 18 changed files with 549 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.programmers.lime.domains.chatroom.api;

import org.springframework.http.ResponseEntity;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.programmers.lime.domains.chatroom.api.dto.response.ChatRoomGetListResponse;
import com.programmers.lime.domains.chatroom.application.ChatRoomService;
import com.programmers.lime.domains.chatroom.application.dto.response.ChatRoomGetServiceListResponse;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

@Tag(name = "chat-room", description = "채팅방 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/chatrooms")
public class ChatRoomController {

private final ChatRoomService chatRoomService;

//private final ChatService chatService;

@Operation(summary = "채팅방 전체 조회", description = "채팅방을 조회합니다.")
@GetMapping
public ResponseEntity<ChatRoomGetListResponse> getChatRooms() {
ChatRoomGetServiceListResponse serviceResponse = chatRoomService.getAvailableChatRooms();
ChatRoomGetListResponse response = ChatRoomGetListResponse.from(serviceResponse);

return ResponseEntity.ok(
response
);
}

@Operation(summary = "채팅방 참여", description = "채팅방에 참여합니다.")
@PostMapping("/{chatRoomId}/join")
public ResponseEntity<Void> joinChatRoom(@PathVariable final Long chatRoomId) {
chatRoomService.joinChatRoom(chatRoomId);
//chatService.joinChatRoom(chatRoomId);
return ResponseEntity.ok().build();
}

@Operation(summary = "채팅방 인원수 조회", description = "채팅방의 인원수를 조회합니다.")
@GetMapping("/{chatRoomId}/members/count")
public ResponseEntity<Integer> countChatRoomMembers(@PathVariable final Long chatRoomId) {
return ResponseEntity.ok(
chatRoomService.countChatRoomMembersByChatRoomId(chatRoomId)
);
}

@Operation(summary = "채팅방 나가기", description = "채팅방에서 나갑니다.")
@DeleteMapping("/{chatRoomId}/exit")
public ResponseEntity<Void> exitChatRoom(@PathVariable final Long chatRoomId) {
chatRoomService.exitChatRoom(chatRoomId);
//chatService.sendExitMessageToChatRoom(chatRoomId);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.programmers.lime.domains.chatroom.api.dto.response;

import java.util.List;

import com.programmers.lime.domains.chatroom.application.dto.response.ChatRoomGetServiceListResponse;
import com.programmers.lime.domains.chatroom.model.ChatRoomInfo;

public record ChatRoomGetListResponse(
List<ChatRoomInfo> chatRoomInfos
) {
public static ChatRoomGetListResponse from(final ChatRoomGetServiceListResponse chatRoomGetServiceListResponse) {
return new ChatRoomGetListResponse(
chatRoomGetServiceListResponse.chatRoomInfos()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.programmers.lime.domains.chatroom.application;

import java.util.List;
import java.util.Objects;
import java.util.Set;

import org.springframework.stereotype.Service;

import com.programmers.lime.domains.chatroom.application.dto.response.ChatRoomGetServiceListResponse;
import com.programmers.lime.domains.chatroom.implementation.ChatRoomMemberAppender;
import com.programmers.lime.domains.chatroom.implementation.ChatRoomMemberReader;
import com.programmers.lime.domains.chatroom.implementation.ChatRoomMemberRemover;
import com.programmers.lime.domains.chatroom.implementation.ChatRoomReader;
import com.programmers.lime.domains.chatroom.model.ChatRoomInfo;
import com.programmers.lime.error.BusinessException;
import com.programmers.lime.error.ErrorCode;
import com.programmers.lime.global.config.chat.WebSocketSessionManager;
import com.programmers.lime.global.config.security.SecurityUtils;
import com.programmers.lime.redis.chat.ChatSessionRedisManager;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class ChatRoomService {

private final ChatRoomMemberReader chatRoomMemberReader;

private final ChatRoomMemberAppender chatRoomMemberAppender;

private final ChatRoomMemberRemover chatRoomMemberRemover;

private final ChatRoomReader chatRoomReader;

private final WebSocketSessionManager webSocketSessionManager;

private final ChatSessionRedisManager chatSessionRedisManager;

public ChatRoomGetServiceListResponse getAvailableChatRooms() {
Long memberId = SecurityUtils.getCurrentMemberId();

List<ChatRoomInfo> chatRoomInfos = chatRoomReader.readOpenChatRoomsByMemberId(memberId);
return new ChatRoomGetServiceListResponse(
chatRoomInfos
);
}

public void joinChatRoom(final Long chatRoomId) {

Long memberId = SecurityUtils.getCurrentMemberId();

if(Objects.isNull(memberId)) {
throw new BusinessException(ErrorCode.MEMBER_ANONYMOUS);
}

if(chatRoomMemberReader.existMemberByMemberIdAndRoomId(chatRoomId, memberId)) {
throw new BusinessException(ErrorCode.CHATROOM_ALREADY_JOIN);
}

chatRoomMemberAppender.appendChatRoomMember(chatRoomId, memberId);
}

public int countChatRoomMembersByChatRoomId(Long chatRoomId) {
return chatRoomMemberReader.countChatRoomMembersByChatRoomId(chatRoomId);
}

public void exitChatRoom(final Long chatRoomId) {
Long memberId = SecurityUtils.getCurrentMemberId();

Set<String> sessionIdsByMemberAndRoom = chatSessionRedisManager.getSessionIdsByMemberAndRoom(
memberId,
chatRoomId
);

for(String memberSessionId : sessionIdsByMemberAndRoom) {
try {
webSocketSessionManager.closeSession(memberSessionId);
} catch (Exception e) {
throw new BusinessException(ErrorCode.CHAT_SESSION_NOT_FOUND);
}
}

chatRoomMemberRemover.removeChatRoomMember(chatRoomId, memberId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.programmers.lime.domains.chatroom.application.dto.response;

import java.util.List;

import com.programmers.lime.domains.chatroom.model.ChatRoomInfo;

public record ChatRoomGetServiceListResponse(
List<ChatRoomInfo> chatRoomInfos
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public enum ErrorCode {
S3_UPLOAD_FAIL("COMMON_014", "S3 업로드에 실패했습니다."),
S3_DELETE_FAIL("COMMON_015", "S3 삭제에 실패했습니다."),
BAD_REVIEW_IMAGE_URL("COMMON_016", "잘못된 리뷰 이미지 URL 입니다."),
INVALID_SUBSCRIPTION_DESTINATION("COMMON_017", "유효하지 않은 구독 대상입니다."),
MESSAGE_DOMAIN_TYPE_NOT_FOUND("COMMON_018", "메시지 도메인 타입을 찾을 수 없습니다."),
SUBSCRIPTION_DESTINATION_NOT_FOUND("COMMON_019", "구독 대상을 찾을 수 없습니다."),


// Member
Expand Down Expand Up @@ -134,7 +137,15 @@ public enum ErrorCode {

// Favorite
FAVORITE_TYPE_BAD_REQUEST("FAVORITE_001", "잘못된 favoriteType 파라미터 값입니다."),
;

// ChatRoom
CHATROOM_MAX_MEMBER_COUNT_ERROR("CHATROOM_001","최소 2명 이상의 사용자가 필요합니다." ),
CHATROOM_ALREADY_JOIN("CHATROOM_002","이미 참여한 채팅방 입니다." ),
CHATROOM_NOT_PERMISSION("CHATROOM_003","채팅방에 참여할 권한이 없습니다." ),

// Chat
CHAT_NOT_PERMISSION("CHAT_001","채팅 권한이 없습니다." ),
CHAT_SESSION_NOT_FOUND("CHAT_002","채팅 세션을 찾을 수 없습니다." );

private static final Map<String, ErrorCode> ERROR_CODE_MAP;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.programmers.lime.domains.chatroom.domain;

import java.util.Objects;

import com.programmers.lime.domains.BaseEntity;
import com.programmers.lime.domains.chatroom.model.ChatRoomStatus;
import com.programmers.lime.domains.chatroom.model.ChatRoomType;
import com.programmers.lime.error.BusinessException;
import com.programmers.lime.error.ErrorCode;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@Table(name = "chat_rooms")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ChatRoom extends BaseEntity {

private static final int MIN_ROOM_MAX_MEMBER_COUNT = 2;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;

@Column(name = "room_name", nullable = false)
private String roomName;

@Enumerated(EnumType.STRING)
@Column(name = "room_type", nullable = false)
private ChatRoomType chatRoomType;

@Enumerated(EnumType.STRING)
@Column(name = "room_status", nullable = false)
private ChatRoomStatus chatRoomStatus;

@Column(name = "room_max_member_count", nullable = false)
private int roomMaxMemberCount;

@Builder
public ChatRoom(
final String roomName,
final ChatRoomType chatRoomType,
final ChatRoomStatus chatRoomStatus,
final int roomMaxMemberCount
) {
validRoomMaxMemberCount(roomMaxMemberCount);
this.roomName = Objects.requireNonNull(roomName);
this.chatRoomType = Objects.requireNonNull(chatRoomType);
this.chatRoomStatus = Objects.requireNonNull(chatRoomStatus);
this.roomMaxMemberCount = roomMaxMemberCount;
}

private void validRoomMaxMemberCount(int roomMaxMemberCount) {
if (roomMaxMemberCount < MIN_ROOM_MAX_MEMBER_COUNT) {
throw new BusinessException(ErrorCode.CHATROOM_MAX_MEMBER_COUNT_ERROR);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.programmers.lime.domains.chatroom.domain;

import java.util.Objects;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@Table(name = "chat_room_members")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ChatRoomMember {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;

@Column(name = "chat_room_id", nullable = false)
private Long chatRoomId;

@Column(name = "member_id", nullable = false)
private Long memberId;

public ChatRoomMember(final Long chatRoomId, final Long memberId) {
this.chatRoomId = Objects.requireNonNull(chatRoomId);
this.memberId = Objects.requireNonNull(memberId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.programmers.lime.domains.chatroom.implementation;

import org.springframework.stereotype.Component;

import com.programmers.lime.domains.chatroom.domain.ChatRoomMember;
import com.programmers.lime.domains.chatroom.repository.ChatRoomMemberRepository;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class ChatRoomMemberAppender {

private final ChatRoomMemberRepository chatRoomMemberRepository;

public void appendChatRoomMember(
final Long chatRoomId,
final Long memberId
) {
ChatRoomMember chatRoomMember = new ChatRoomMember(chatRoomId, memberId);
chatRoomMemberRepository.save(chatRoomMember);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.programmers.lime.domains.chatroom.implementation;


import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.programmers.lime.domains.chatroom.repository.ChatRoomMemberRepository;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ChatRoomMemberReader {

private final ChatRoomMemberRepository chatRoomMemberRepository;

public int countChatRoomMembersByChatRoomId(final Long chatRoomId) {
return chatRoomMemberRepository.countChatRoomMembersByChatRoomId(chatRoomId);
}

public boolean existMemberByMemberIdAndRoomId(
final Long chatRoomId,
final Long memberId
) {
return chatRoomMemberRepository.existsAllByChatRoomIdAndMemberId(chatRoomId, memberId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.programmers.lime.domains.chatroom.implementation;

import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.programmers.lime.domains.chatroom.repository.ChatRoomMemberRepository;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class ChatRoomMemberRemover {

private final ChatRoomMemberRepository chatRoomMemberRepository;

@Transactional
public void removeChatRoomMember(
final Long chatRoomId,
final Long memberId
) {
chatRoomMemberRepository.deleteByChatRoomIdAndMemberId(chatRoomId, memberId);
}
}
Loading

0 comments on commit ab7bcc1

Please sign in to comment.