Skip to content

Commit

Permalink
채팅 기능 추가 및 CI/CD 관련 코드 작성 (#9)
Browse files Browse the repository at this point in the history
* Feature: 채팅 기능 구현 (#2)

* Feat: 웹 소켓 의존성 추가

* Feat: 웹 소켓에 대한 설정 클래스 작성

* Feat: Redis에 필요한 클래스 작성 및 DTO 추가
Redis의 Pub/Sub에 필요한 클래스와 설정을 작성하고 필요한 DTO를 추가함.

* Test: Redis Sub/Pub 테스트 코드 작성

* Feat: 채팅에 필요한 Entity 클래스 작성

* Refactor: DTO 클래스 필드 수정
채팅 메시지 DTO 클래스의 필드를 수정하였고 그에 따른 다른 코드들도 수정함

* Refactor: 채팅 메시지 DTO 클래스명 수정
채팅 메시지 DTO 클래스명을 좀 더 명확하게 하기 위해 ChatReqeust -> ChatMessageRequest로 변경
또한, 변경에 따른 여러 코드 수정

* Refactor: Entity들의 Builder에 외부접근이 불가능하도록 PRIVATE 으로 설정

* Feat: 채팅에 필요한 여러 Repository 추가

* Test: Repository 테스트 코드 작성

* Feat: 채팅 서비스에 필요한 DTO 클래스 추가

* Feat: 채팅 서비스 로직 작성

* Test: 채팅 서비스 로직 테스트 코드 작성

* Test: 테스트 코드 수정
테스트가 일관성을 유지하도록 저장시간을 지정

* Feat: API 응답 객체 생성

* Feat: REST Docs 의존성 추가 및 설정 작성

* Feat: 채팅 컨트롤러 작성 및 예외처리 로직 추가

* Test: 채팅 컨트롤러단 테스트 코드 작성 및 문서화 코드 추가

* Docs: 채팅 API 문서 생성

* CI/CD 구축을 위한 세팅 (#4)

* Refactor: 테스트환경과 서비스 환경 분리를 위해 Redis 설정 파일 수정

* Feat: 도커파일 작성

* Feat: 테스트 코드 환경 DB 세팅용 도커컴포즈 파일 작성

* Chore: 빌드 시 1개의 jar 파일만 생성하도록 그래들 수정

* Test: 테스트 코드용 yml파일 작성

* Test: 테스트 yml을 사용하도록 코드 추가

* CI/CD 구축을 위한 수정 (#6)

* Refactor: 테스트환경과 서비스 환경 분리를 위해 Redis 설정 파일 수정

* Feat: 도커파일 작성

* Feat: 테스트 코드 환경 DB 세팅용 도커컴포즈 파일 작성

* Chore: 빌드 시 1개의 jar 파일만 생성하도록 그래들 수정

* Test: 테스트 코드용 yml파일 작성

* Test: 테스트 yml을 사용하도록 코드 추가

* Feat: .dockerignore 파일 추가

* Refactor: .properties -> .yml 로 파일 확장자 변경
가독성 증가를 위해 파일 확장자 변경

* Feature: 채팅 기능 추가 및 기타 코드 수정 (#8)

* Refactor: Redis 설정 클래스 수정
`@Value` 로 값을 불러들여 빈을 만드는 대신 설정파일을 자동으로 읽어 빈으로 만드는 방법으로 수정

* Feat: 채팅방 UUID로 채팅방 상세 정보를 검색하는 API 추가

* Test: 새로운 API 테스트 코드 작성

* Docs: API 문서 최신화

* Refactor: 테스트용 설정 파일 수정
레디스 설정 수정에 따라 설정 파일 수정
  • Loading branch information
pjh5365 authored Sep 1, 2024
1 parent dc5a5df commit 6700619
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 44 deletions.
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/docker-compose-chat-test-db.yml
/Dockerfile
/.gradle/
/build/
/.idea/
5 changes: 5 additions & 0 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ operation::chat-controller-test/create-room-fail-test[snippets="http-response,re
operation::chat-controller-test/find-by-username-test[snippets="http-request,query-parameters"]
==== 응답
operation::chat-controller-test/find-by-username-test[snippets="http-response,response-fields"]
=== 채팅방 상세 정보 조회 API
==== 요청
operation::chat-controller-test/find-chat-room-details-test[snippets="http-request,query-parameters"]
==== 응답
operation::chat-controller-test/find-chat-room-details-test[snippets="http-response,response-fields"]
=== 채팅방 채팅내역 조회 API
==== 요청
operation::chat-controller-test/find-chat-message-test[snippets="http-request,query-parameters"]
Expand Down
28 changes: 4 additions & 24 deletions src/main/java/kaboo/kaboochat/chat/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package kaboo.kaboochat.chat.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
Expand All @@ -13,6 +11,7 @@
import org.springframework.data.redis.serializer.StringRedisSerializer;

import kaboo.kaboochat.chat.domain.redis.RedisSubscriber;
import lombok.RequiredArgsConstructor;

/**
* Redis 설정 클래스
Expand All @@ -26,29 +25,10 @@
* @since : 2024/08/17
*/
@Configuration
@RequiredArgsConstructor
public class RedisConfig {

private final String redisHost;
private final int redisPort;

public RedisConfig(@Value("${REDIS_HOST}") String redisHost, @Value("${REDIS_PORT}") int redisPort) {
this.redisHost = redisHost;
this.redisPort = redisPort;
}

/**
* RedisConnectionFactory 빈 생성
* <p>
* Lettuce 클라이언트를 사용하여 Redis 서버와의 연결을 생성하고 관리합니다.
* <br>
* 이 빈은 Redis 서버와의 연결을 생성하고, 다른 Redis 관련 빈들이 이 연결을 사용하여 Redis와 통신하게 됩니다.
* </p>
* @return RedisConnectionFactory LettuceConnectionFactory 인스턴스
*/
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}
private final RedisConnectionFactory redisConnectionFactory;

/**
* RedisTemplate 빈 생성
Expand Down Expand Up @@ -106,7 +86,7 @@ public RedisMessageListenerContainer redisMessageListener(
ChannelTopic channelTopic
) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory()); // Redis 연결 팩토리 설정
container.setConnectionFactory(redisConnectionFactory); // Redis 연결 팩토리 설정
container.addMessageListener(listenerAdapterChatMessage, channelTopic); // 리스너와 채널을 컨테이너에 등록
return container;
}
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/kaboo/kaboochat/chat/controller/ChatController.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ public ResponseEntity<ApiResponse<List<ChatRoomResponse>>> findChatRoomListByUse
.body(ApiResponse.success(responses));
}

/**
* 채팅방의 상세 정보를 불러옵니다.
*
* @param roomUUID 채팅방 UUID
* @return 채팅방 상세 정보
*/
@GetMapping("/rooms/details")
public ResponseEntity<ApiResponse<ChatRoomResponse>> findChatRoomDetails(String roomUUID) {
ChatRoomResponse response = chatService.findByChatUUID(roomUUID);

return ResponseEntity.status(HttpStatus.OK)
.body(ApiResponse.success(response));
}

/**
* 채팅방을 생성합니다.
*
Expand Down
1 change: 0 additions & 1 deletion src/main/resources/application.properties

This file was deleted.

3 changes: 3 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
spring:
application:
name: Kaboo-Chat
137 changes: 123 additions & 14 deletions src/main/resources/static/docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ <h1>카부 커뮤니티 채팅 API</h1>
<li><a href="#_채팅방_생성_성공_api">채팅방 생성 성공 API</a></li>
<li><a href="#_채팅방_생성_실패_api">채팅방 생성 실패 API</a></li>
<li><a href="#_채팅방_리스트_조회_api">채팅방 리스트 조회 API</a></li>
<li><a href="#_채팅방_상세_정보_조회_api">채팅방 상세 정보 조회 API</a></li>
<li><a href="#_채팅방_채팅내역_조회_api">채팅방 채팅내역 조회 API</a></li>
</ul>
</li>
Expand Down Expand Up @@ -719,15 +720,15 @@ <h5 id="_응답_3_http_response">HTTP response</h5>
"message" : "요청이 성공적으로 처리되었습니다.",
"data" : [ {
"usernames" : null,
"chatRoomUUID" : "753f04ee-28e6-44c9-be80-1a7620602164",
"chatRoomUUID" : "704569d6-4f67-4113-b240-262d3b478124",
"chatRoomName" : "채팅방1"
}, {
"usernames" : null,
"chatRoomUUID" : "ac7a6331-dbf9-4416-803e-2740cfcb8e35",
"chatRoomUUID" : "aa72340d-9be0-4570-abb6-fa34dbfdd6a8",
"chatRoomName" : "채팅방2"
}, {
"usernames" : null,
"chatRoomUUID" : "e8ef047b-5f7e-468b-8a80-20df87c3590a",
"chatRoomUUID" : "8c203b97-a839-41f2-aacb-fc1c04990b2c",
"chatRoomName" : "채팅방3"
} ]
}</code></pre>
Expand Down Expand Up @@ -786,14 +787,14 @@ <h5 id="_응답_3_response_fields">Response fields</h5>
</div>
</div>
<div class="sect2">
<h3 id="_채팅방_채팅내역_조회_api">채팅방 채팅내역 조회 API</h3>
<h3 id="_채팅방_상세_정보_조회_api">채팅방 상세 정보 조회 API</h3>
<div class="sect3">
<h4 id="_요청_4">요청</h4>
<div class="sect4">
<h5 id="_요청_4_http_request">HTTP request</h5>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-http hljs" data-lang="http">GET /chat/messages?roomUUID=A1A1-B2B2&amp;page=0&amp;size=10 HTTP/1.1
<pre class="highlightjs highlight nowrap"><code class="language-http hljs" data-lang="http">GET /chat/rooms/details?roomUUID=AAAA-BBBB-CCCC-DDDD HTTP/1.1
Host: localhost:8080</code></pre>
</div>
</div>
Expand All @@ -814,6 +815,114 @@ <h5 id="_요청_4_query_parameters">Query parameters</h5>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>roomUUID</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">검색할 채팅방 UUID</p></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="sect3">
<h4 id="_응답_4">응답</h4>
<div class="sect4">
<h5 id="_응답_4_http_response">HTTP response</h5>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-http hljs" data-lang="http">HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 253

{
"success" : true,
"message" : "요청이 성공적으로 처리되었습니다.",
"data" : {
"usernames" : [ "pjh5365", "Justin", "Apple" ],
"chatRoomUUID" : "7555f825-a342-4d77-b6db-4439cebe8e7c",
"chatRoomName" : "채팅방1"
}
}</code></pre>
</div>
</div>
</div>
<div class="sect4">
<h5 id="_응답_4_response_fields">Response fields</h5>
<table class="tableblock frame-all grid-all stretch">
<colgroup>
<col style="width: 33.3333%;">
<col style="width: 33.3333%;">
<col style="width: 33.3334%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Path</th>
<th class="tableblock halign-left valign-top">Type</th>
<th class="tableblock halign-left valign-top">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>success</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Boolean</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">성공여부</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>message</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">응답 메시지</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>data</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Object</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">채팅방 리스트</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>data.usernames</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Array</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">참여자 이름 리스트</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>data.chatRoomUUID</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">채팅방 UUID</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>data.chatRoomName</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">채팅방 이름</p></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_채팅방_채팅내역_조회_api">채팅방 채팅내역 조회 API</h3>
<div class="sect3">
<h4 id="_요청_5">요청</h4>
<div class="sect4">
<h5 id="_요청_5_http_request">HTTP request</h5>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-http hljs" data-lang="http">GET /chat/messages?roomUUID=A1A1-B2B2&amp;page=0&amp;size=10 HTTP/1.1
Host: localhost:8080</code></pre>
</div>
</div>
</div>
<div class="sect4">
<h5 id="_요청_5_query_parameters">Query parameters</h5>
<table class="tableblock frame-all grid-all stretch">
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Parameter</th>
<th class="tableblock halign-left valign-top">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>roomUUID</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">채팅방 UUID</p></td>
</tr>
<tr>
Expand All @@ -829,9 +938,9 @@ <h5 id="_요청_4_query_parameters">Query parameters</h5>
</div>
</div>
<div class="sect3">
<h4 id="_응답_4">응답</h4>
<h4 id="_응답_5">응답</h4>
<div class="sect4">
<h5 id="_응답_4_http_response">HTTP response</h5>
<h5 id="_응답_5_http_response">HTTP response</h5>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-http hljs" data-lang="http">HTTP/1.1 200 OK
Expand All @@ -845,34 +954,34 @@ <h5 id="_응답_4_http_response">HTTP response</h5>
"username" : "pjh5365",
"nickname" : "justin",
"message" : "거기는 어때?",
"sendAt" : "2024-08-18T21:01:13.382868"
"sendAt" : "2024-09-01T13:51:00.983825"
}, {
"username" : "pjh5365",
"nickname" : "justin",
"message" : "엄청 더워...",
"sendAt" : "2024-08-18T21:01:13.382866"
"sendAt" : "2024-09-01T13:51:00.983822"
}, {
"username" : "pibber",
"nickname" : "park",
"message" : "거기 날씨 어때?",
"sendAt" : "2024-08-18T21:01:13.382864"
"sendAt" : "2024-09-01T13:51:00.98382"
}, {
"username" : "pibber",
"nickname" : "park",
"message" : "반가워",
"sendAt" : "2024-08-18T21:01:13.38286"
"sendAt" : "2024-09-01T13:51:00.983817"
}, {
"username" : "pjh5365",
"nickname" : "justin",
"message" : "안녕",
"sendAt" : "2024-08-18T21:01:13.382837"
"sendAt" : "2024-09-01T13:51:00.983806"
} ]
}</code></pre>
</div>
</div>
</div>
<div class="sect4">
<h5 id="_응답_4_response_fields">Response fields</h5>
<h5 id="_응답_5_response_fields">Response fields</h5>
<table class="tableblock frame-all grid-all stretch">
<colgroup>
<col style="width: 33.3333%;">
Expand Down Expand Up @@ -933,7 +1042,7 @@ <h5 id="_응답_4_response_fields">Response fields</h5>
<div id="footer">
<div id="footer-text">
Version 0.0.1-SNAPSHOT<br>
Last updated 2024-08-18 21:01:07 +0900
Last updated 2024-09-01 13:50:54 +0900
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.3/highlight.min.js"></script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,41 @@ void findByUsernameTest() throws Exception {
// Then
}

@Test
@DisplayName("채팅방 상세 정보 조회")
void findChatRoomDetailsTest() throws Exception {
// Given
ChatRoom chatRoom = ChatRoom.createRoom("채팅방1");
ChatRoomResponse response = ChatRoomResponse.fromEntity(List.of("pjh5365", "Justin", "Apple"), chatRoom);
given(chatService.findByChatUUID("AAAA-BBBB-CCCC-DDDD")).willReturn(response);

// When
mockMvc.perform(get("/chat/rooms/details")
.queryParam("roomUUID", "AAAA-BBBB-CCCC-DDDD"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.message").value("요청이 성공적으로 처리되었습니다."))
.andExpect(jsonPath("$.data").exists())
.andDo(print())
.andDo(document("{class-name}/{method-name}/",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
queryParameters(
parameterWithName("roomUUID").description("검색할 채팅방 UUID")
),
responseFields(
fieldWithPath("success").description("성공여부"),
fieldWithPath("message").description("응답 메시지"),
fieldWithPath("data").description("채팅방 리스트"),
fieldWithPath("data.usernames").description("참여자 이름 리스트"),
fieldWithPath("data.chatRoomUUID").description("채팅방 UUID"),
fieldWithPath("data.chatRoomName").description("채팅방 이름")
)));

// Then
}

@Test
@DisplayName("채팅방 생성")
void createRoomTest() throws Exception {
Expand Down
9 changes: 4 additions & 5 deletions src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ spring:
ddl-auto: create
# 테스트용 도커 컨테이너의 설정에 맞춘다.
data:
redis:
host: redis # 도커 네트워크가 아닌 호스트의 주소를 사용
port: 6379
mongodb:
host: mongodb
host: mongodb # 도커 네트워크가 아닌 호스트의 주소를 사용
port: 27017

# 테스트용 도커 컨테이너의 설정에 맞춘다.
REDIS_HOST: redis
REDIS_PORT: 6379

0 comments on commit 6700619

Please sign in to comment.