diff --git a/src/main/java/site/billbill/apiserver/api/chat/controller/ChatController.java b/src/main/java/site/billbill/apiserver/api/chat/controller/ChatController.java index 48dd616..0fa89a1 100644 --- a/src/main/java/site/billbill/apiserver/api/chat/controller/ChatController.java +++ b/src/main/java/site/billbill/apiserver/api/chat/controller/ChatController.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jboss.logging.MDC; @@ -11,9 +12,11 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import site.billbill.apiserver.api.chat.dto.request.ChatRequest; import site.billbill.apiserver.api.chat.dto.response.ChatResponse; +import site.billbill.apiserver.api.chat.dto.response.ChatResponse.ViewChatInfoResponse; import site.billbill.apiserver.api.chat.service.ChatService; import site.billbill.apiserver.common.response.BaseResponse; import site.billbill.apiserver.common.utils.jwt.JWTUtil; @@ -29,7 +32,6 @@ public class ChatController { @Operation(summary = "채팅방 나가기", description = "채팅방 나가기 API") @PatchMapping("/{channelId}") public BaseResponse leaveChatChannel(@PathVariable(value = "channelId") String channelId) { - log.info("api 호출 정상적~"); String userId = MDC.get(JWTUtil.MDC_USER_ID).toString(); return new BaseResponse<>(chatService.leaveChatChannel(channelId,userId)); } @@ -37,7 +39,6 @@ public BaseResponse leaveChatChannel(@PathVariable(value = "channelId") @Operation(summary = "채팅방 생성 및 id 조회", description = "빌리기 버튼 누를 때 api") @PostMapping("") public BaseResponse startChannel(@RequestBody ChatRequest.borrowInfo request) { - log.info("api 호출 정상적~"); String userId = MDC.get(JWTUtil.MDC_USER_ID).toString(); return new BaseResponse<>(chatService.startChannel(request, userId)); } @@ -45,8 +46,14 @@ public BaseResponse startChannel(@RequestBody ChatRequest.borrowInfo req @Operation(summary = "채팅방 info 조회", description = "채팅방 info 조회 API") @GetMapping("/{channelId}") public BaseResponse getInfoChannel(@PathVariable(value = "channelId") String channelId) { - log.info("api 호출 정상적~"); String userId = MDC.get(JWTUtil.MDC_USER_ID).toString(); - return new BaseResponse<>(chatService.getInfoChannel(channelId,userId)); + return new BaseResponse<>(chatService.getInfoChannel(channelId, userId)); + } + + @Operation(summary = "채팅목록 조회", description = "채팅목록 조회 API") + @GetMapping("/list") + public BaseResponse> getChatList(@RequestParam(required = false) String beforeTimestamp) { + String userId = MDC.get(JWTUtil.MDC_USER_ID).toString(); + return new BaseResponse<>(chatService.getChatList(beforeTimestamp, userId)); } } diff --git a/src/main/java/site/billbill/apiserver/api/chat/converter/ChatConverter.java b/src/main/java/site/billbill/apiserver/api/chat/converter/ChatConverter.java index b9c8d16..5b3f5be 100644 --- a/src/main/java/site/billbill/apiserver/api/chat/converter/ChatConverter.java +++ b/src/main/java/site/billbill/apiserver/api/chat/converter/ChatConverter.java @@ -1,6 +1,7 @@ package site.billbill.apiserver.api.chat.converter; import java.time.LocalDate; +import site.billbill.apiserver.api.chat.dto.request.WebhookRequest.ChatInfo; import site.billbill.apiserver.api.chat.dto.response.ChatResponse; import site.billbill.apiserver.model.chat.ChatChannelJpaEntity; import site.billbill.apiserver.model.post.ItemsJpaEntity; @@ -19,7 +20,9 @@ public static ChatChannelJpaEntity toChatChannel(String channelId, ItemsJpaEntit .build(); } - public static ChatResponse.ViewChannelInfoResponse toViewChannelInfo(ChatChannelJpaEntity channel, UserJpaEntity opponent, ItemsJpaEntity item, int totalPrice, + public static ChatResponse.ViewChannelInfoResponse toViewChannelInfo(ChatChannelJpaEntity channel, + UserJpaEntity opponent, ItemsJpaEntity item, + int totalPrice, String status, String userId) { return ChatResponse.ViewChannelInfoResponse.builder() @@ -36,4 +39,26 @@ public static ChatResponse.ViewChannelInfoResponse toViewChannelInfo(ChatChannel .myId(userId) .build(); } + + public static ChatResponse.ViewChatInfoResponse toViewChatInfo(ChatInfo chatInfo, + String userId, UserJpaEntity opponent, + ItemsJpaEntity item) { + int unReadCount = chatInfo.getUnreadCount(); + + if (chatInfo.getLastSender().equals(userId)) { + unReadCount = 0; + } + + return ChatResponse.ViewChatInfoResponse.builder() + .channelId(chatInfo.getChannelId()) + .lastChat(chatInfo.getLastChat()) + .lastSender(chatInfo.getLastSender()) + .updatedAt(chatInfo.getUpdatedAt()) + .unreadCount(unReadCount) + .opponentId(opponent.getUserId()) + .opponentProfileUrl(opponent.getProfile()) + .opponentNickname(opponent.getNickname()) + .itemFirstUrl(item.getImages().get(0)) + .build(); + } } diff --git a/src/main/java/site/billbill/apiserver/api/chat/dto/request/WebhookRequest.java b/src/main/java/site/billbill/apiserver/api/chat/dto/request/WebhookRequest.java new file mode 100644 index 0000000..3fecc31 --- /dev/null +++ b/src/main/java/site/billbill/apiserver/api/chat/dto/request/WebhookRequest.java @@ -0,0 +1,27 @@ +package site.billbill.apiserver.api.chat.dto.request; + +import java.time.LocalDateTime; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class WebhookRequest { + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ChatInfo { + private String channelId; + private int unreadCount; + private String lastChat; + private String lastSender; + private LocalDateTime updatedAt; + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ChatInfoList { + private List chatInfoList; + } +} diff --git a/src/main/java/site/billbill/apiserver/api/chat/dto/response/ChatResponse.java b/src/main/java/site/billbill/apiserver/api/chat/dto/response/ChatResponse.java index cc764b5..05f2619 100644 --- a/src/main/java/site/billbill/apiserver/api/chat/dto/response/ChatResponse.java +++ b/src/main/java/site/billbill/apiserver/api/chat/dto/response/ChatResponse.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.annotation.JsonFormat; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; import lombok.Builder; import lombok.Getter; @@ -23,4 +25,18 @@ public static class ViewChannelInfoResponse { @JsonFormat(pattern = "yyyy-MM-dd") LocalDate endedAt; } + + @Getter + @Builder + public static class ViewChatInfoResponse { + private String channelId; + private int unreadCount; + private String lastChat; + private String lastSender; + private LocalDateTime updatedAt; + private String opponentId; + private String opponentNickname; + private String opponentProfileUrl; + private String itemFirstUrl; + } } diff --git a/src/main/java/site/billbill/apiserver/api/chat/service/ChatService.java b/src/main/java/site/billbill/apiserver/api/chat/service/ChatService.java index ac7db1e..97dd711 100644 --- a/src/main/java/site/billbill/apiserver/api/chat/service/ChatService.java +++ b/src/main/java/site/billbill/apiserver/api/chat/service/ChatService.java @@ -1,7 +1,9 @@ package site.billbill.apiserver.api.chat.service; +import java.util.List; import site.billbill.apiserver.api.chat.dto.request.ChatRequest; import site.billbill.apiserver.api.chat.dto.response.ChatResponse.ViewChannelInfoResponse; +import site.billbill.apiserver.api.chat.dto.response.ChatResponse.ViewChatInfoResponse; public interface ChatService { String leaveChatChannel(String postId, String userId); @@ -9,4 +11,6 @@ public interface ChatService { ViewChannelInfoResponse getInfoChannel(String channelId, String userId); String startChannel(ChatRequest.borrowInfo request, String userId); + + List getChatList(String beforeTimestamp, String userId); } diff --git a/src/main/java/site/billbill/apiserver/api/chat/service/ChatServiceImpl.java b/src/main/java/site/billbill/apiserver/api/chat/service/ChatServiceImpl.java index 5ce6c63..a788033 100644 --- a/src/main/java/site/billbill/apiserver/api/chat/service/ChatServiceImpl.java +++ b/src/main/java/site/billbill/apiserver/api/chat/service/ChatServiceImpl.java @@ -1,16 +1,19 @@ package site.billbill.apiserver.api.chat.service; import java.time.temporal.ChronoUnit; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import site.billbill.apiserver.api.chat.converter.ChatConverter; import site.billbill.apiserver.api.chat.dto.request.ChatRequest; +import site.billbill.apiserver.api.chat.dto.request.WebhookRequest.ChatInfo; +import site.billbill.apiserver.api.chat.dto.request.WebhookRequest.ChatInfoList; import site.billbill.apiserver.api.chat.dto.response.ChatResponse; +import site.billbill.apiserver.api.chat.dto.response.ChatResponse.ViewChatInfoResponse; import site.billbill.apiserver.common.enums.exception.ErrorCode; import site.billbill.apiserver.common.utils.ULID.ULIDUtil; import site.billbill.apiserver.exception.CustomException; @@ -27,7 +30,6 @@ @RequiredArgsConstructor @Transactional(readOnly = true) public class ChatServiceImpl implements ChatService { - private static final Logger log = LoggerFactory.getLogger(ChatServiceImpl.class); private final ChatRepository chatRepository; private final UserRepository userRepository; private final ItemsRepository itemsRepository; @@ -89,4 +91,25 @@ public ChatResponse.ViewChannelInfoResponse getInfoChannel(String channelId, Str return ChatConverter.toViewChannelInfo(chatChannel, opponent, item, totalPrice, status, userId); } + + public List getChatList(String beforeTimestamp, String userId) { + userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.NotFound, "회원을 찾을 수 없습니다.", HttpStatus.NOT_FOUND)); + + List activeChatIdsByUserId = chatRepository.findActiveChatIdsByUserId(userId); + + if (activeChatIdsByUserId == null || activeChatIdsByUserId.isEmpty()) { + return Collections.emptyList(); + } + + ChatInfoList webhookResult = webhookService.sendWebhookForChatList(activeChatIdsByUserId, beforeTimestamp); + List chatInfoList = webhookResult.getChatInfoList(); + + return chatInfoList.stream().map(chatInfo -> { + ChatChannelJpaEntity chatChannel = chatRepository.findById(chatInfo.getChannelId()) + .orElseThrow(() -> new CustomException(ErrorCode.NotFound, "채널을 찾을 수 없습니다.", HttpStatus.NOT_FOUND)); + UserJpaEntity opponent = chatChannel.getOpponent(userId); + return ChatConverter.toViewChatInfo(chatInfo, userId, opponent, chatChannel.getItem()); + }).collect(Collectors.toList()); + } } diff --git a/src/main/java/site/billbill/apiserver/api/chat/service/WebhookService.java b/src/main/java/site/billbill/apiserver/api/chat/service/WebhookService.java index 1010434..1e47900 100644 --- a/src/main/java/site/billbill/apiserver/api/chat/service/WebhookService.java +++ b/src/main/java/site/billbill/apiserver/api/chat/service/WebhookService.java @@ -1,5 +1,9 @@ package site.billbill.apiserver.api.chat.service; +import java.util.List; +import site.billbill.apiserver.api.chat.dto.request.WebhookRequest; + public interface WebhookService { void sendWebhookForChatRoomCreate(String channelId, String contact, String owner); + WebhookRequest.ChatInfoList sendWebhookForChatList(List chatRoomIds, String beforeTimestamp); } diff --git a/src/main/java/site/billbill/apiserver/api/chat/service/WebhookServiceImpl.java b/src/main/java/site/billbill/apiserver/api/chat/service/WebhookServiceImpl.java index d7c6b1d..a6dc382 100644 --- a/src/main/java/site/billbill/apiserver/api/chat/service/WebhookServiceImpl.java +++ b/src/main/java/site/billbill/apiserver/api/chat/service/WebhookServiceImpl.java @@ -1,24 +1,33 @@ package site.billbill.apiserver.api.chat.service; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import java.util.HashMap; +import java.util.List; import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; +import site.billbill.apiserver.api.chat.dto.request.WebhookRequest; +import site.billbill.apiserver.common.enums.exception.ErrorCode; +import site.billbill.apiserver.exception.CustomException; @Slf4j @Service -public class WebhookServiceImpl { +public class WebhookServiceImpl implements WebhookService { private final WebClient webClient; + private final ObjectMapper objectMapper; @Value("${webhook.url}") private String webhookUrl; @Autowired - public WebhookServiceImpl(WebClient.Builder webClientBuilder) { + public WebhookServiceImpl(WebClient.Builder webClientBuilder, ObjectMapper objectMapper) { this.webClient = webClientBuilder.baseUrl(webhookUrl).build(); + this.objectMapper = objectMapper; } public void sendWebhookForChatRoomCreate(String channelId, String contact, String owner) { @@ -27,12 +36,33 @@ public void sendWebhookForChatRoomCreate(String channelId, String contact, Strin payload.put("contactId", contact); payload.put("ownerId", owner); - webClient.post() - .uri("") - .bodyValue(payload) + try { + webClient.post() + .uri("/channel") + .bodyValue(payload) + .retrieve() + .bodyToMono(Void.class) + .block(); + } catch (Exception e) { + log.error("Webhook 호출 실패: {}", e.getMessage()); + throw new CustomException(ErrorCode.ServerError, "Webhook 호출 실패", HttpStatus.BAD_GATEWAY); + } + } + + public WebhookRequest.ChatInfoList sendWebhookForChatList(List chatRoomIds, String beforeTimestamp) { + String jsonResponse = webClient.post() + .uri("/chat/list") + .bodyValue(Map.of( + "chatRoomIds", chatRoomIds, + "beforeTimestamp", beforeTimestamp + )) .retrieve() - .bodyToMono(Void.class) - .doOnError(error -> log.error("Webhook 호출 실패: {}", error.getMessage())) - .subscribe(); + .bodyToMono(String.class) + .block(); + try { + return objectMapper.readValue(jsonResponse, WebhookRequest.ChatInfoList.class); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } } } diff --git a/src/main/java/site/billbill/apiserver/common/enums/chat/ChannelState.java b/src/main/java/site/billbill/apiserver/common/enums/chat/ChannelState.java index 79d2c0c..3a84181 100644 --- a/src/main/java/site/billbill/apiserver/common/enums/chat/ChannelState.java +++ b/src/main/java/site/billbill/apiserver/common/enums/chat/ChannelState.java @@ -2,7 +2,6 @@ public enum ChannelState { PRE, // 거래전 - ACCEPTED, // 거래수락 CONFIRMED, // 거래확정 CANCELLED // 거래취소 } diff --git a/src/main/java/site/billbill/apiserver/repository/chat/ChatRepository.java b/src/main/java/site/billbill/apiserver/repository/chat/ChatRepository.java index 85fe707..47d11af 100644 --- a/src/main/java/site/billbill/apiserver/repository/chat/ChatRepository.java +++ b/src/main/java/site/billbill/apiserver/repository/chat/ChatRepository.java @@ -4,6 +4,7 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import site.billbill.apiserver.model.chat.ChatChannelJpaEntity; import site.billbill.apiserver.model.post.ItemsJpaEntity; import site.billbill.apiserver.model.user.UserJpaEntity; @@ -25,4 +26,10 @@ public interface ChatRepository extends JpaRepository findAllByItemAndContactUser(ItemsJpaEntity item, UserJpaEntity user); + + @Query("SELECT c.channelId " + + "FROM ChatChannelJpaEntity c " + + "WHERE (c.owner.userId = :userId AND c.ownerLeft = false) " + + " OR (c.contact.userId = :userId AND c.contactLeft = false)") + List findActiveChatIdsByUserId(String userId); }