Skip to content

Commit

Permalink
Merge pull request #88 from billbill-project/feature/chat
Browse files Browse the repository at this point in the history
[feat] 채팅목록 조회 구현
  • Loading branch information
jimmy0524 authored Dec 20, 2024
2 parents 244b6e7 + 034c0d0 commit d666fec
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -29,24 +32,28 @@ public class ChatController {
@Operation(summary = "채팅방 나가기", description = "채팅방 나가기 API")
@PatchMapping("/{channelId}")
public BaseResponse<String> 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));
}

@Operation(summary = "채팅방 생성 및 id 조회", description = "빌리기 버튼 누를 때 api")
@PostMapping("")
public BaseResponse<String> startChannel(@RequestBody ChatRequest.borrowInfo request) {
log.info("api 호출 정상적~");
String userId = MDC.get(JWTUtil.MDC_USER_ID).toString();
return new BaseResponse<>(chatService.startChannel(request, userId));
}

@Operation(summary = "채팅방 info 조회", description = "채팅방 info 조회 API")
@GetMapping("/{channelId}")
public BaseResponse<ChatResponse.ViewChannelInfoResponse> 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<List<ViewChatInfoResponse>> getChatList(@RequestParam(required = false) String beforeTimestamp) {
String userId = MDC.get(JWTUtil.MDC_USER_ID).toString();
return new BaseResponse<>(chatService.getChatList(beforeTimestamp, userId));
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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()
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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<ChatInfo> chatInfoList;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
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);

ViewChannelInfoResponse getInfoChannel(String channelId, String userId);

String startChannel(ChatRequest.borrowInfo request, String userId);

List<ViewChatInfoResponse> getChatList(String beforeTimestamp, String userId);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -89,4 +91,25 @@ public ChatResponse.ViewChannelInfoResponse getInfoChannel(String channelId, Str

return ChatConverter.toViewChannelInfo(chatChannel, opponent, item, totalPrice, status, userId);
}

public List<ViewChatInfoResponse> getChatList(String beforeTimestamp, String userId) {
userRepository.findById(userId)
.orElseThrow(() -> new CustomException(ErrorCode.NotFound, "회원을 찾을 수 없습니다.", HttpStatus.NOT_FOUND));

List<String> activeChatIdsByUserId = chatRepository.findActiveChatIdsByUserId(userId);

if (activeChatIdsByUserId == null || activeChatIdsByUserId.isEmpty()) {
return Collections.emptyList();
}

ChatInfoList webhookResult = webhookService.sendWebhookForChatList(activeChatIdsByUserId, beforeTimestamp);
List<ChatInfo> 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());
}
}
Original file line number Diff line number Diff line change
@@ -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<String> chatRoomIds, String beforeTimestamp);
}
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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<String> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

public enum ChannelState {
PRE, // 거래전
ACCEPTED, // 거래수락
CONFIRMED, // 거래확정
CANCELLED // 거래취소
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,4 +26,10 @@ public interface ChatRepository extends JpaRepository<ChatChannelJpaEntity,Strin
"And c.contact = :user "
)
List<ChatChannelJpaEntity> 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<String> findActiveChatIdsByUserId(String userId);
}

0 comments on commit d666fec

Please sign in to comment.