Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/backend-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,26 @@ jobs:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('chat-service/**/*.gradle*', 'chat-service/**/gradle-wrapper.properties') }}
key: ${{ runner.os }}-gradle-${{ hashFiles('chat_service/**/*.gradle*', 'chat_service/**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: gradlew 실행 권한 부여
working-directory: chat-service
working-directory: chat_service
run: chmod +x ./gradlew

- name: Gradle로 테스트 실행
working-directory: chat-service
working-directory: chat_service
run: ./gradlew --info test

- name: 테스트 결과 리포트
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: 'chat-service/build/test-results/test/TEST-*.xml'
files: 'chat_service/build/test-results/test/TEST-*.xml'

- name: 테스트 결과 게시
uses: mikepenz/action-junit-report@v4
if: always()
with:
report_paths: 'chat-service/build/test-results/test/TEST-*.xml'
report_paths: 'chat_service/build/test-results/test/TEST-*.xml'
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "interactive"
}
18 changes: 17 additions & 1 deletion chat_service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,25 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
// Spring Web
implementation 'org.springframework.boot:spring-boot-starter-web'
// JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// Validation
implementation 'org.springframework.boot:spring-boot-starter-validation'
// H2
runtimeOnly 'com.h2database:h2'
// PostgreSQL
runtimeOnly 'org.postgresql:postgresql'
// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
//Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testRuntimeOnly 'com.h2database:h2'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.synapse.chat_service.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing
public class JpaConfig {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.synapse.chat_service.controller;

import com.synapse.chat_service.dto.request.ChatRoomRequest;
import com.synapse.chat_service.dto.response.ChatRoomResponse;
import com.synapse.chat_service.service.ChatRoomService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/api/v1/chat-rooms")
@RequiredArgsConstructor
public class ChatRoomController {

private final ChatRoomService chatRoomService;

/**
* 채팅방 생성
*/
@PostMapping
public ResponseEntity<ChatRoomResponse.Detail> createChatRoom(
@Valid @RequestBody ChatRoomRequest.Create request
) {
ChatRoomResponse.Detail response = chatRoomService.createChatRoom(request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

/**
* 채팅방 단건 조회
*/
@GetMapping("/{chatRoomId}")
public ResponseEntity<ChatRoomResponse.Detail> getChatRoom(
@PathVariable UUID chatRoomId
) {
ChatRoomResponse.Detail response = chatRoomService.getChatRoom(chatRoomId);
return ResponseEntity.ok(response);
}

/**
* 사용자별 채팅방 목록 조회 (페이징)
*/
@GetMapping
public ResponseEntity<Page<ChatRoomResponse.Simple>> getChatRoomsByUserId(
@RequestParam Long userId,
@PageableDefault(size = 20, sort = "createdDate", direction = Sort.Direction.DESC) Pageable pageable
) {
Page<ChatRoomResponse.Simple> response = chatRoomService.getChatRoomsByUserId(userId, pageable);
return ResponseEntity.ok(response);
}

/**
* 채팅방 제목 검색
*/
@GetMapping("/search")
public ResponseEntity<List<ChatRoomResponse.Simple>> searchChatRooms(
@RequestParam Long userId,
@RequestParam String keyword
) {
List<ChatRoomResponse.Simple> response = chatRoomService.searchChatRooms(userId, keyword);
return ResponseEntity.ok(response);
}

/**
* 채팅방 수정
*/
@PutMapping("/{chatRoomId}")
public ResponseEntity<ChatRoomResponse.Detail> updateChatRoom(
@PathVariable UUID chatRoomId,
@Valid @RequestBody ChatRoomRequest.Update request
) {
ChatRoomResponse.Detail response = chatRoomService.updateChatRoom(chatRoomId, request);
return ResponseEntity.ok(response);
}

/**
* 채팅방 삭제
*/
@DeleteMapping("/{chatRoomId}")
public ResponseEntity<Void> deleteChatRoom(@PathVariable UUID chatRoomId) {
chatRoomService.deleteChatRoom(chatRoomId);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.synapse.chat_service.controller;

import com.synapse.chat_service.dto.request.MessageRequest;
import com.synapse.chat_service.dto.response.MessageResponse;
import com.synapse.chat_service.service.MessageService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/api/v1/messages")
@RequiredArgsConstructor
public class MessageController {

private final MessageService messageService;

/**
* 메시지 생성
*/
@PostMapping
public ResponseEntity<MessageResponse.Detail> createMessage(
@Valid @RequestBody MessageRequest.Create request
) {
MessageResponse.Detail response = messageService.createMessage(request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

/**
* 메시지 단건 조회
*/
@GetMapping("/{messageId}")
public ResponseEntity<MessageResponse.Detail> getMessage(
@PathVariable Long messageId
) {
MessageResponse.Detail response = messageService.getMessage(messageId);
return ResponseEntity.ok(response);
}

/**
* 채팅방별 메시지 목록 조회 (시간순 정렬)
*/
@GetMapping("/chat-room/{chatRoomId}")
public ResponseEntity<List<MessageResponse.Simple>> getMessagesByChatRoomId(
@PathVariable UUID chatRoomId
) {
List<MessageResponse.Simple> response = messageService.getMessagesByChatRoomId(chatRoomId);
return ResponseEntity.ok(response);
}

/**
* 채팅방별 메시지 목록 조회 (페이징, 시간순 정렬)
*/
@GetMapping("/chat-room/{chatRoomId}/paging")
public ResponseEntity<Page<MessageResponse.Simple>> getMessagesByChatRoomIdWithPaging(
@PathVariable UUID chatRoomId,
@PageableDefault(size = 50, sort = "createdDate", direction = Sort.Direction.ASC) Pageable pageable
) {
Page<MessageResponse.Simple> response = messageService.getMessagesByChatRoomIdWithPaging(chatRoomId, pageable);
return ResponseEntity.ok(response);
}

/**
* 채팅방별 메시지 목록 조회 (페이징, 최신순 정렬)
*/
@GetMapping("/chat-room/{chatRoomId}/recent")
public ResponseEntity<Page<MessageResponse.Simple>> getMessagesRecentFirst(
@PathVariable UUID chatRoomId,
@PageableDefault(size = 50, sort = "createdDate", direction = Sort.Direction.DESC) Pageable pageable
) {
Page<MessageResponse.Simple> response = messageService.getMessagesRecentFirst(chatRoomId, pageable);
return ResponseEntity.ok(response);
}

/**
* 메시지 내용 검색
*/
@GetMapping("/chat-room/{chatRoomId}/search")
public ResponseEntity<List<MessageResponse.Simple>> searchMessages(
@PathVariable UUID chatRoomId,
@RequestParam String keyword
) {
List<MessageResponse.Simple> response = messageService.searchMessages(chatRoomId, keyword);
return ResponseEntity.ok(response);
}

/**
* 메시지 삭제
*/
@DeleteMapping("/{messageId}")
public ResponseEntity<Void> deleteMessage(@PathVariable Long messageId) {
messageService.deleteMessage(messageId);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.synapse.chat_service.domain.common;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;

import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity extends BaseTimeEntity {

@CreatedBy
@Column(updatable = false)
private String createdBy;

@LastModifiedBy
private String lastModifiedBy;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.synapse.chat_service.domain.common;

import java.time.LocalDateTime;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;

@LastModifiedDate
private LocalDateTime updatedDate;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.synapse.chat_service.domain.entity;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import com.synapse.chat_service.exception.commonexception.ValidException;
import com.synapse.chat_service.exception.domain.ExceptionType;

import com.synapse.chat_service.domain.common.BaseTimeEntity;

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

@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "chat_room_id", columnDefinition = "UUID")
private UUID id;

@NotNull
@Column(name = "user_id", nullable = false)
private Long userId;

@NotBlank
@Column(name = "title", nullable = false, length = 255)
private String title;

@OneToMany(mappedBy = "chatRoom", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Message> messages = new ArrayList<>();

@Builder
public ChatRoom(Long userId, String title) {
this.userId = userId;
this.title = title;
}

public void updateTitle(String newTitle) {
validateTitle(newTitle);
this.title = newTitle.trim();
}

private void validateTitle(String title) {
if (title == null || title.trim().isEmpty()) {
throw new ValidException(ExceptionType.INVALID_INPUT_VALUE, "채팅방 제목은 비어있을 수 없습니다.");
}
if (title.trim().length() > 255) {
throw new ValidException(ExceptionType.INVALID_INPUT_VALUE, "채팅방 제목은 255자를 초과할 수 없습니다.");
}
}
}
Loading
Loading