diff --git a/src/main/java/com/dangsim/chat/controller/ChatMessageStompController.java b/src/main/java/com/dangsim/chat/controller/ChatMessageStompController.java index 7372337..eb3ee0c 100644 --- a/src/main/java/com/dangsim/chat/controller/ChatMessageStompController.java +++ b/src/main/java/com/dangsim/chat/controller/ChatMessageStompController.java @@ -1,7 +1,5 @@ package com.dangsim.chat.controller; -import java.security.Principal; - import org.springframework.messaging.handler.annotation.DestinationVariable; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.stereotype.Controller; @@ -21,10 +19,10 @@ public class ChatMessageStompController { @MessageMapping("/chat-rooms/{chatRoomId}") public void createChatMessage( @DestinationVariable Long chatRoomId, - Principal principal, @Valid CreateChatMessageRequest request ) { - Long userId = Long.valueOf(principal.getName()); + + Long userId = request.senderId(); chatMessageService.createChatMessage(request, userId, chatRoomId); } diff --git a/src/main/java/com/dangsim/chat/dto/ChatRoomMapper.java b/src/main/java/com/dangsim/chat/dto/ChatRoomMapper.java index 39d2098..58b3cb7 100644 --- a/src/main/java/com/dangsim/chat/dto/ChatRoomMapper.java +++ b/src/main/java/com/dangsim/chat/dto/ChatRoomMapper.java @@ -37,12 +37,13 @@ public static TaskInfoResponse toTaskInfoResponse(Task task) { } public static ChatRoomInfoResponse toChatRoomInfoResponse(Long chatRoomId, User chatPartner, - TaskInfoResponse taskInfoResponse) { + TaskInfoResponse taskInfoResponse, Long userId) { return new ChatRoomInfoResponse( chatRoomId, taskInfoResponse, chatPartner.getId(), - chatPartner.getNickname() + chatPartner.getNickname(), + userId ); } @@ -56,9 +57,9 @@ public static String getThumbNail(List imageUrls) { public static boolean isCompletedTask(Task task) { if (Objects.equals(task.getStatus(), TASK_COMPLETE)) { - return false; + return true; } - return true; + return false; } } diff --git a/src/main/java/com/dangsim/chat/dto/request/CreateChatMessageRequest.java b/src/main/java/com/dangsim/chat/dto/request/CreateChatMessageRequest.java index 0ddee86..44851f6 100644 --- a/src/main/java/com/dangsim/chat/dto/request/CreateChatMessageRequest.java +++ b/src/main/java/com/dangsim/chat/dto/request/CreateChatMessageRequest.java @@ -11,6 +11,8 @@ public record CreateChatMessageRequest( MessageType type, @Size(max = 300, message = "최대 300자까지 입력이 가능합니다.") @NotBlank - String content + String content, + @NotNull + Long senderId ) { } diff --git a/src/main/java/com/dangsim/chat/dto/response/ChatRoomInfoResponse.java b/src/main/java/com/dangsim/chat/dto/response/ChatRoomInfoResponse.java index 9fa6d77..cb2d3db 100644 --- a/src/main/java/com/dangsim/chat/dto/response/ChatRoomInfoResponse.java +++ b/src/main/java/com/dangsim/chat/dto/response/ChatRoomInfoResponse.java @@ -6,6 +6,7 @@ public record ChatRoomInfoResponse( Long chatRoomId, TaskInfoResponse taskInfo, Long chatPartnerId, - String partnerNickname + String partnerNickname, + Long myId ) { } diff --git a/src/main/java/com/dangsim/chat/dto/response/ChatRoomSimpleResponse.java b/src/main/java/com/dangsim/chat/dto/response/ChatRoomSimpleResponse.java index 75a7d80..d17706f 100644 --- a/src/main/java/com/dangsim/chat/dto/response/ChatRoomSimpleResponse.java +++ b/src/main/java/com/dangsim/chat/dto/response/ChatRoomSimpleResponse.java @@ -1,5 +1,7 @@ package com.dangsim.chat.dto.response; +import java.util.Objects; + import com.dangsim.chat.entity.ChatMessage; import com.dangsim.chat.entity.ChatRoom; import com.dangsim.common.util.DateTimeFormatUtils; @@ -12,7 +14,8 @@ public record ChatRoomSimpleResponse( String nickname, String content, String timestamp, - Boolean isRead + Boolean isRead, + Long chatMessageId ) { @QueryProjection @@ -21,9 +24,35 @@ public ChatRoomSimpleResponse(ChatRoom chatRoom, ChatMessage lastChatMessage, Us chatRoom.getId(), partner.getId(), partner.getNickname(), - lastChatMessage.getMessage(), - DateTimeFormatUtils.formatDateTime(lastChatMessage.getCreatedAt()), - lastChatMessage.isRead() + initChatMessage(lastChatMessage), + initTimeStamp(lastChatMessage), + initIsRead(lastChatMessage), + lastChatMessage.getId() ); } + + private static final String DEFAULT = "채팅을 시작해보세요"; + + public static String initChatMessage(ChatMessage lastChatMessage) { + if (Objects.isNull(lastChatMessage)) { + return DEFAULT; + } + return lastChatMessage.getMessage(); + } + + public static String initTimeStamp(ChatMessage lastChatMessage) { + if (Objects.isNull(lastChatMessage)) { + return ""; + } + return DateTimeFormatUtils.formatDateTime(lastChatMessage.getCreatedAt()); + } + + public static Boolean initIsRead(ChatMessage lastChatMessage) { + if (Objects.isNull(lastChatMessage)) { + return false; + } + return lastChatMessage.isRead(); + } + } + diff --git a/src/main/java/com/dangsim/chat/repository/ChatRoomQueryRepositoryImpl.java b/src/main/java/com/dangsim/chat/repository/ChatRoomQueryRepositoryImpl.java index aa57042..68387e3 100644 --- a/src/main/java/com/dangsim/chat/repository/ChatRoomQueryRepositoryImpl.java +++ b/src/main/java/com/dangsim/chat/repository/ChatRoomQueryRepositoryImpl.java @@ -33,15 +33,26 @@ public class ChatRoomQueryRepositoryImpl implements ChatRoomQueryRepository { private final JPAQueryFactory queryFactory; @Override - public CursorPageResponse findChatRoomsByCursor(String cursor, int size, Long userId) { + public CursorPageResponse findChatRoomsByCursor( + String cursor, int size, Long userId) { QChatMessage lastMessage = new QChatMessage("lastMessage"); QUser partner = new QUser("partner"); + BooleanExpression isRequester = chatRoom.requester.id.eq(userId) + .and(partner.id.eq(chatRoom.performer.id)); + BooleanExpression isPerformer = chatRoom.performer.id.eq(userId) + .and(partner.id.eq(chatRoom.requester.id)); + BooleanExpression partnerJoinCondition = isRequester.or(isPerformer); + + BooleanExpression cursorFilter = null; + if (cursor != null && !cursor.isBlank()) { + cursorFilter = lastMessage.id.lt(Long.parseLong(cursor)); + } + List chatRooms = queryFactory .select(new QChatRoomSimpleResponse(chatRoom, lastMessage, partner)) .from(chatRoom) - //최근 메시지 조인 .leftJoin(lastMessage) .on(lastMessage.chatRoomId.eq(chatRoom.id), lastMessage.id.eq( @@ -50,17 +61,14 @@ public CursorPageResponse findChatRoomsByCursor(String c .from(chatMessage) .where(chatMessage.chatRoomId.eq(chatRoom.id)) )) - //상대방 유저 조인 .leftJoin(partner) - .on( - chatRoom.requester.id.eq(userId).and(partner.id.eq(chatRoom.performer.id)) - .or(chatRoom.performer.id.eq(userId).and(partner.id.eq(chatRoom.requester.id))) - ) + .on(partnerJoinCondition) .where( - chatRoom.requester.id.eq(userId).or(chatRoom.performer.id.eq(userId)), - isBeforeCursor(lastMessage.createdAt, cursor) + chatRoom.requester.id.eq(userId) + .or(chatRoom.performer.id.eq(userId)), + cursorFilter ) - .orderBy(lastMessage.createdAt.desc()) + .orderBy(lastMessage.id.desc()) .limit(size + 1) .fetch(); @@ -74,15 +82,14 @@ public CursorPageResponse findChatRoomsByCursor(String c @Override public CursorPageResponse findChatMessagesByCursor(Long chatRoomId, String cursor, int size, Long userId) { - List allMessages = queryFactory .select(new QChatMessageDetailResponse(chatMessage)) .from(chatMessage) .where( chatMessage.chatRoomId.eq(chatRoomId), - isBeforeCursor(chatMessage.createdAt, cursor) + chatMessageCursorFilter(cursor) ) - .orderBy(chatMessage.createdAt.desc()) + .orderBy(chatMessage.id.desc()) .limit(size + 1) .fetch(); @@ -95,6 +102,22 @@ public CursorPageResponse findChatMessagesByCursor(Long return new CursorPageResponse<>(Collections.singletonList(detailResponse), nextCursor, hasNext); } + private BooleanExpression chatRoomCursorFilter(String cursor) { + if (Objects.isNull(cursor) || cursor.isBlank()) { + return null; + } + + return chatRoom.id.lt(Long.parseLong(cursor)); + } + + private BooleanExpression chatMessageCursorFilter(String cursor) { + if (Objects.isNull(cursor) || cursor.isBlank()) { + return null; + } + + return chatMessage.id.lt(Long.parseLong(cursor)); + } + private BooleanExpression isBeforeCursor(DateTimePath dateTimePath, String cursor) { if (cursor == null) { return null; @@ -106,7 +129,7 @@ private String getNextCursor(List chatrooms, boolean has if (Objects.isNull(chatrooms) || chatrooms.isEmpty() || !hasNext) { return null; } - return chatrooms.get(chatrooms.size() - 1).timestamp(); + return String.valueOf(chatrooms.get(chatrooms.size() - 1).chatMessageId()); } private String getNextCursorByMessage(List messages, boolean hasNext) { diff --git a/src/main/java/com/dangsim/chat/service/ChatRoomService.java b/src/main/java/com/dangsim/chat/service/ChatRoomService.java index 1ba9f29..6f32935 100644 --- a/src/main/java/com/dangsim/chat/service/ChatRoomService.java +++ b/src/main/java/com/dangsim/chat/service/ChatRoomService.java @@ -1,8 +1,5 @@ package com.dangsim.chat.service; -import java.time.LocalDateTime; -import java.util.Objects; - import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,7 +14,6 @@ import com.dangsim.chat.repository.ChatRoomRepository; import com.dangsim.common.CursorPageResponse; import com.dangsim.common.exception.runtime.BaseException; -import com.dangsim.common.util.DateTimeFormatUtils; import com.dangsim.task.dto.response.TaskInfoResponse; import com.dangsim.task.entity.Task; import com.dangsim.task.exception.TaskErrorCode; @@ -51,18 +47,13 @@ public ChatRoomResponse createChatRoom(@Valid CreateChatRoomRequest request, Use @Transactional(readOnly = true) public CursorPageResponse getChatRoomsByCursor(String cursor, int size, Long userId) { - if (Objects.isNull(cursor) || cursor.isBlank()) { - cursor = DateTimeFormatUtils.formatDateTime(LocalDateTime.now()); - } return chatRoomRepository.findChatRoomsByCursor(cursor, size, userId); } public CursorPageResponse getChatMessagesByCursor(Long chatRoomId, String cursor, int size, Long userId) { - if (Objects.isNull(cursor) || cursor.isBlank()) { - cursor = DateTimeFormatUtils.formatDateTime(LocalDateTime.now()); - } + return chatRoomRepository.findChatMessagesByCursor(chatRoomId, cursor, size, userId); } @@ -84,7 +75,7 @@ public ChatRoomInfoResponse getChatRoomInfo(Long chatRoomId, Long userId) { .orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND)); return ChatRoomMapper.toChatRoomInfoResponse(chatRoomId, chatPartner, - taskInfoResponse); + taskInfoResponse, userId); } } diff --git a/src/main/java/com/dangsim/common/config/CorsConfig.java b/src/main/java/com/dangsim/common/config/CorsConfig.java index 7b47ac1..b7f40a8 100644 --- a/src/main/java/com/dangsim/common/config/CorsConfig.java +++ b/src/main/java/com/dangsim/common/config/CorsConfig.java @@ -14,4 +14,4 @@ // } // }; // } -// } +// } \ No newline at end of file diff --git a/src/main/java/com/dangsim/common/config/SecurityConfig.java b/src/main/java/com/dangsim/common/config/SecurityConfig.java index 1da9d5a..643a563 100644 --- a/src/main/java/com/dangsim/common/config/SecurityConfig.java +++ b/src/main/java/com/dangsim/common/config/SecurityConfig.java @@ -7,6 +7,7 @@ import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; @@ -21,6 +22,14 @@ @EnableWebSecurity public class SecurityConfig { + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return web -> web.ignoring() + .requestMatchers( + "/error", "/favicon.ico", "/ws/**" + ); + } + @Bean public SecurityFilterChain filterChain(HttpSecurity http, JwtProvider jwtProvider, UserRepository userRepository) throws Exception { @@ -32,6 +41,7 @@ public SecurityFilterChain filterChain(HttpSecurity http, JwtProvider jwtProvide // .anyRequest().permitAll() .requestMatchers("/api/auth/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/tasks").permitAll() + .requestMatchers("/ws-chat/**").permitAll() .requestMatchers( "/swagger-ui/**", "/v3/api-docs/**", @@ -53,7 +63,7 @@ public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(List.of( "http://localhost:3000", - "https://dangsim-fe.pages.dev" + "https://dangsim-fe.pages.dev", "/ws/**" )); configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")); configuration.setAllowedHeaders(List.of("*")); diff --git a/src/main/java/com/dangsim/common/config/WebSocketConfig.java b/src/main/java/com/dangsim/common/config/WebSocketConfig.java index 9239a9b..c4a9ab5 100644 --- a/src/main/java/com/dangsim/common/config/WebSocketConfig.java +++ b/src/main/java/com/dangsim/common/config/WebSocketConfig.java @@ -21,8 +21,8 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-chat") //웹소켓 연결 주소 - .setAllowedOriginPatterns("*") //cors 허용 - .withSockJS(); + .setAllowedOrigins("http://localhost:3000"); //cors 허용 + // .withSockJS(); } @Override diff --git a/src/main/java/com/dangsim/common/interceptor/StompChannelInterceptor.java b/src/main/java/com/dangsim/common/interceptor/StompChannelInterceptor.java index 5efb904..c0696ab 100644 --- a/src/main/java/com/dangsim/common/interceptor/StompChannelInterceptor.java +++ b/src/main/java/com/dangsim/common/interceptor/StompChannelInterceptor.java @@ -46,6 +46,7 @@ public Message preSend(Message message, MessageChannel channel) { Long userId = jwtProvider.getUserIdFromToken(token); Principal principal = () -> String.valueOf(userId); accessor.setUser(principal); + accessor.setLeaveMutable(true); } return message; } diff --git a/src/main/java/com/dangsim/task/service/TaskService.java b/src/main/java/com/dangsim/task/service/TaskService.java index 9760d3a..d491bb3 100644 --- a/src/main/java/com/dangsim/task/service/TaskService.java +++ b/src/main/java/com/dangsim/task/service/TaskService.java @@ -9,7 +9,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.dangsim.chat.entity.ChatMessage; import com.dangsim.chat.entity.ChatRoom; +import com.dangsim.chat.repository.ChatMessageRepository; import com.dangsim.chat.repository.ChatRoomRepository; import com.dangsim.common.CursorPageResponse; import com.dangsim.common.exception.runtime.BaseException; @@ -37,9 +39,12 @@ @RequiredArgsConstructor public class TaskService { + private static final String HELLO_CHAT_MESSAGE = "채팅을 시작해보세요"; + private final TaskRepository taskRepository; private final PaymentRepository paymentRepository; private final ChatRoomRepository chatRoomRepository; + private final ChatMessageRepository chatMessageRepository; @Transactional public TaskResponseDto createTask(TaskRequestDto requestDto, User user) { @@ -113,6 +118,9 @@ public TaskMatchResponse matchPerformer(Long taskId, User performer) { ChatRoom savedChatRoom = chatRoomRepository.save(ChatRoom.of(findTask, findTask.getUser(), performer)); + ChatMessage chatMessage = ChatMessage.of(savedChatRoom.getId(), performer.getId(), HELLO_CHAT_MESSAGE); + chatMessageRepository.save(chatMessage); + findTask.updateStatus(TASK_IN_PROGRESS); return TaskMapper.toTaskMatchResponse(savedChatRoom); diff --git a/src/test/java/com/dangsim/task/service/TaskServiceTest.java b/src/test/java/com/dangsim/task/service/TaskServiceTest.java index 06cb0e0..8cb8a84 100644 --- a/src/test/java/com/dangsim/task/service/TaskServiceTest.java +++ b/src/test/java/com/dangsim/task/service/TaskServiceTest.java @@ -13,9 +13,6 @@ import java.util.List; import java.util.Optional; -import com.dangsim.payment.entity.PaymentStatus; -import com.dangsim.pg.repository.PaymentGatewayRepository; -import com.dangsim.pg.service.PaymentGatewayService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -25,6 +22,7 @@ import org.springframework.test.util.ReflectionTestUtils; import com.dangsim.chat.entity.ChatRoom; +import com.dangsim.chat.repository.ChatMessageRepository; import com.dangsim.chat.repository.ChatRoomRepository; import com.dangsim.common.exception.runtime.BaseException; import com.dangsim.common.fixture.ChatRoomFixture; @@ -33,8 +31,11 @@ import com.dangsim.common.fixture.UserFixture; import com.dangsim.common.util.DateTimeFormatUtils; import com.dangsim.payment.entity.Payment; +import com.dangsim.payment.entity.PaymentStatus; import com.dangsim.payment.exception.PaymentErrorCode; import com.dangsim.payment.repository.PaymentRepository; +import com.dangsim.pg.repository.PaymentGatewayRepository; +import com.dangsim.pg.service.PaymentGatewayService; import com.dangsim.task.dto.request.TaskRequestDto; import com.dangsim.task.dto.response.TaskDeleteResponse; import com.dangsim.task.dto.response.TaskDetailsResponseDto; @@ -65,6 +66,9 @@ public class TaskServiceTest { @Mock PaymentGatewayRepository paymentGatewayRepository; + @Mock + ChatMessageRepository chatMessageRepository; + @InjectMocks PaymentGatewayService paymentGatewayService;