Skip to content

Commit

Permalink
feat: ✨ Chatroom Join Event Relay (#185)
Browse files Browse the repository at this point in the history
* feat: add message_content_type enum class

* feat: add message_category_type enum class

* feat: two types of converter impl

* feat: chat_message entity with step builder

* rename: type package path transfer

* feat: chat_message repository & domain service

* fix: chat_message redis entity @id package spring to jakarta

* test: dao layer test

* chore: configuring the rabbit listener container factory & socket application fix

* chore: add rabbitmq dependency into the socket module

* feat: impl chat_join_event_listener

* refactor: seperate business logic from listener

* rename: contants -> constants

* feat: add system message enum classes

* refactor: using send_message_command for dto

* chore: when integration tests run in the external-api, amqp connect is blocked
  • Loading branch information
psychology50 authored Oct 30, 2024

Unverified

The key that signed this doesn't have usage flags that allow signing.
1 parent afb5d86 commit 444ad64
Showing 20 changed files with 802 additions and 3 deletions.
6 changes: 5 additions & 1 deletion pennyway-app-external-api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -57,4 +57,8 @@ springdoc:
spring:
config:
activate:
on-profile: test
on-profile: test

pennyway:
rabbitmq:
validate-connection: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kr.co.pennyway.domain.common.converter;

import jakarta.persistence.Converter;
import kr.co.pennyway.domain.common.redis.message.type.MessageCategoryType;

@Converter
public class MessageCategoryTypeConverter extends AbstractLegacyEnumAttributeConverter<MessageCategoryType> {
private static final String ENUM_NAME = "메시지 카테고리 타입";

public MessageCategoryTypeConverter() {
super(MessageCategoryType.class, false, ENUM_NAME);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kr.co.pennyway.domain.common.converter;

import jakarta.persistence.Converter;
import kr.co.pennyway.domain.common.redis.message.type.MessageContentType;

@Converter
public class MessageContentTypeConverter extends AbstractLegacyEnumAttributeConverter<MessageContentType> {
private static final String ENUM_NAME = "메시지 컨텐츠 타입";

public MessageContentTypeConverter() {
super(MessageContentType.class, false, ENUM_NAME);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package kr.co.pennyway.domain.common.redis.message.domain;

import jakarta.persistence.Convert;
import jakarta.persistence.Id;
import kr.co.pennyway.domain.common.converter.MessageCategoryTypeConverter;
import kr.co.pennyway.domain.common.converter.MessageContentTypeConverter;
import kr.co.pennyway.domain.common.redis.message.type.MessageCategoryType;
import kr.co.pennyway.domain.common.redis.message.type.MessageContentType;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.redis.core.RedisHash;

import java.time.LocalDateTime;

/**
* 채팅 메시지를 표현하는 클래스입니다.
* Redis에 저장되는 채팅 메시지의 기본 단위입니다.
*/
@Getter
@RedisHash
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ChatMessage {
/**
* 채팅 메시지 ID는 "chatroom:{roomId}:message:{messageId}" 형태로 생성한다.
*/
@Id
private String id;
private String content;
@Convert(converter = MessageContentTypeConverter.class)
private MessageContentType contentType;
@Convert(converter = MessageCategoryTypeConverter.class)
private MessageCategoryType categoryType;
private LocalDateTime createdAt;
private LocalDateTime deletedAt;
private Long sender;

protected ChatMessage(ChatMessageBuilder builder) {
this.id = "chatroom:" + builder.getChatRoomId() + ":message:" + builder.getChatId();
this.content = builder.getContent();
this.contentType = builder.getContentType();
this.categoryType = builder.getCategoryType();
this.createdAt = LocalDateTime.now();
this.deletedAt = null;
this.sender = builder.getSender();
}

public Long getChatRoomId() {
return Long.parseLong(id.split(":")[1]);
}

public Long getChatId() {
return Long.parseLong(id.split(":")[3]);
}

@Override
public String toString() {
return "ChatMessage{" +
"id='" + id + '\'' +
", content='" + content + '\'' +
", contentType=" + contentType +
", categoryType=" + categoryType +
", createdAt=" + createdAt +
", deletedAt=" + deletedAt +
", sender=" + sender +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package kr.co.pennyway.domain.common.redis.message.domain;

import kr.co.pennyway.domain.common.redis.message.type.MessageCategoryType;
import kr.co.pennyway.domain.common.redis.message.type.MessageContentType;
import org.springframework.lang.NonNull;

import java.util.Objects;

/**
* 채팅 메시지 생성을 위한 Step Builder입니다.
* 필수 필드들을 순차적으로 설정하도록 강제하여 객체 생성의 안정성을 보장합니다.
*
* <p>사용 예시:
* <pre>
* ChatMessage message = ChatMessage.builder()
* .chatRoomId(123L)
* .chatId(456L)
* .content("Hello")
* .contentType(MessageContentType.TEXT)
* .categoryType(MessageCategoryType.NORMAL)
* .sender(789L)
* .build();
* </pre>
*/
public final class ChatMessageBuilder {
private Long chatRoomId;
private Long chatId;
private String content;
private MessageContentType contentType;
private MessageCategoryType categoryType;
private long sender;

private ChatMessageBuilder() {
}

/**
* ChatMessage 빌더의 시작점입니다.
*
* @return 채팅방 ID 설정 단계
*/
public static ChatRoomIdStep builder() {
return new Steps();
}

Long getChatRoomId() {
return chatRoomId;
}

Long getChatId() {
return chatId;
}

String getContent() {
return content;
}

MessageContentType getContentType() {
return contentType;
}

MessageCategoryType getCategoryType() {
return categoryType;
}

long getSender() {
return sender;
}

/**
* 채팅방 ID 설정 단계입니다.
* 채팅 메시지가 속한 채팅방의 ID를 지정합니다.
*/
public interface ChatRoomIdStep {
/**
* 채팅방 ID를 설정합니다.
*
* @param chatRoomId 채팅방 ID
* @return 채팅 메시지 ID 설정 단계
* @throws NullPointerException chatRoomId가 null인 경우
*/
ChatIdStep chatRoomId(Long chatRoomId);
}

/**
* 채팅 메시지 ID 설정 단계입니다.
* 개별 채팅 메시지를 식별하기 위한 ID를 지정합니다.
*/
public interface ChatIdStep {
/**
* 채팅 메시지 ID를 설정합니다.
*
* @param chatId 채팅 메시지 ID
* @return 메시지 내용 설정 단계
* @throws NullPointerException chatId가 null인 경우
*/
ContentStep chatId(Long chatId);
}

/**
* 메시지 내용 설정 단계입니다.
* 채팅 메시지의 실제 내용을 지정합니다.
*/
public interface ContentStep {
/**
* 메시지 내용을 설정합니다.
*
* @param content 메시지 내용
* @return 메시지 타입 설정 단계
* @throws NullPointerException content가 null인 경우
* @throws IllegalArgumentException content가 5000자를 초과하는 경우
*/
ContentTypeStep content(String content);
}

/**
* 메시지 타입 설정 단계입니다.
* 메시지의 형식(텍스트, 이미지, 파일 등)을 지정합니다.
*/
public interface ContentTypeStep {
/**
* 메시지 타입을 설정합니다.
*
* @param contentType 메시지 타입
* @return 메시지 카테고리 설정 단계
* @throws NullPointerException contentType이 null인 경우
*/
CategoryTypeStep contentType(MessageContentType contentType);
}

/**
* 메시지 카테고리 설정 단계입니다.
* 메시지의 종류(일반, 시스템 등)를 지정합니다.
*/
public interface CategoryTypeStep {
/**
* 메시지 카테고리를 설정합니다.
*
* @param categoryType 메시지 카테고리
* @return 발신자 설정 단계
* @throws NullPointerException categoryType이 null인 경우
*/
SenderStep categoryType(MessageCategoryType categoryType);
}

/**
* 발신자 설정 단계입니다.
* 메시지를 보낸 사용자의 ID를 지정합니다.
*/
public interface SenderStep {
/**
* 발신자 ID를 설정합니다.
*
* @param sender 발신자 ID
* @return 빌드 단계
*/
BuildStep sender(Long sender);
}

/**
* 최종 빌드 단계입니다.
* 모든 필수 필드가 설정된 후 ChatMessage 객체를 생성합니다.
*/
public interface BuildStep {
/**
* 설정된 값들을 사용하여 ChatMessage 객체를 생성합니다.
*
* @return 생성된 ChatMessage 객체
*/
ChatMessage build();
}

private static class Steps implements
ChatRoomIdStep,
ChatIdStep,
ContentStep,
ContentTypeStep,
CategoryTypeStep,
SenderStep,
BuildStep {

private final ChatMessageBuilder builder = new ChatMessageBuilder();

@Override
public ChatIdStep chatRoomId(@NonNull final Long chatRoomId) {
builder.chatRoomId = Objects.requireNonNull(chatRoomId, "chatRoomId must not be null");
return this;
}

@Override
public ContentStep chatId(@NonNull final Long chatId) {
builder.chatId = Objects.requireNonNull(chatId, "chatId must not be null");
return this;
}

@Override
public ContentTypeStep content(@NonNull final String content) {
builder.content = Objects.requireNonNull(content, "content must not be null");

if (content.length() > 5000) {
throw new IllegalArgumentException("content length must be less than or equal to 5000");
}

return this;
}

@Override
public CategoryTypeStep contentType(@NonNull final MessageContentType contentType) {
builder.contentType = Objects.requireNonNull(contentType, "contentType must not be null");
return this;
}

@Override
public SenderStep categoryType(@NonNull final MessageCategoryType categoryType) {
builder.categoryType = Objects.requireNonNull(categoryType, "categoryType must not be null");
return this;
}

@Override
public BuildStep sender(@NonNull final Long sender) {
builder.sender = sender;
return this;
}

@Override
public ChatMessage build() {
return new ChatMessage(builder);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kr.co.pennyway.domain.common.redis.message.repository;

import kr.co.pennyway.domain.common.redis.message.domain.ChatMessage;
import org.springframework.data.repository.CrudRepository;

public interface ChatMessageRepository extends CrudRepository<ChatMessage, String> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package kr.co.pennyway.domain.common.redis.message.service;

import kr.co.pennyway.common.annotation.DomainService;
import kr.co.pennyway.domain.common.redis.message.domain.ChatMessage;
import kr.co.pennyway.domain.common.redis.message.repository.ChatMessageRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@DomainService
@RequiredArgsConstructor
public class ChatMessageService {
private final ChatMessageRepository chatMessageRepository;

public ChatMessage save(ChatMessage chatMessage) {
return chatMessageRepository.save(chatMessage);
}

public void delete(final long chatRoomId, final long chatId) {
chatMessageRepository.deleteById("chatroom:" + chatRoomId + ":message:" + chatId);
}
}
Loading

0 comments on commit 444ad64

Please sign in to comment.