Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
import com.memesphere.domain.chat.dto.response.ChatResponse;
import com.memesphere.domain.chat.service.ChatService;
import com.memesphere.global.apipayload.ApiResponse;
import com.memesphere.global.jwt.CustomUserDetails;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@Tag(name = "실시간 채팅", description = "실시간 채팅 관련 API")
@RestController
Expand All @@ -22,16 +24,25 @@ public class ChatController {

private final ChatService chatService;

@MessageMapping("/chat/{coin_id}")
@SendTo("/sub/{coin_id}")
@MessageMapping("/chat/{coin_id}") // 클라이언트가 pub/chat/{coin_id}로 STOMP 메시지를 전송하면 실행됨
@SendTo("/sub/{coin_id}") // 메서드가 반환한 데이터를 구독 중인 클라이언트에게 전송
public ChatResponse chat(@DestinationVariable("coin_id") Long coin_id,
@Payload ChatRequest chatRequest) {

return chatService.saveMessage(coin_id, chatRequest);
}

@GetMapping("/chat/{coin_id}/list")
@Operation(summary = "코인별 채팅 전체 메시지 조회 API",
description = "특정 코인의 채팅방의 전체 메시지를 보여줍니다.")
public ApiResponse<Page<ChatResponse>> getChatList(@PathVariable("coin_id") Long coin_id,
Pageable pageable) {

return ApiResponse.onSuccess(chatService.getChatList(coin_id, pageable));
}

//최신 댓글 조회 Api
@GetMapping("/latest/{coin_id}")
@GetMapping("/chat/{coin_id}/latest")
@Operation(summary = "코인별 최신 댓글 조회 API",
description = "특정 코인에 대한 최신 댓글을 반환합니다. 요청 시 최신 댓글 하나만 가져옵니다.")
public ApiResponse<ChatResponse> getLatestMessages(
Expand All @@ -42,4 +53,16 @@ public ApiResponse<ChatResponse> getLatestMessages(

return ApiResponse.onSuccess(latestMessage);
}

@PostMapping("/chat/{coin_id}/like")
@Operation(summary = "채팅 좋아요 관련 API",
description = "채팅에서 하트를 클릭 시 하트가 채워지고, 좋아요 누른 사람의 숫자에 반영됩니다.")
public ApiResponse<String> postChatLike(@PathVariable("coin_id") Long coin_id,
@RequestParam(value = "chat_id", required = true) Long chat_id,
@AuthenticationPrincipal CustomUserDetails customUserDetails) {

Long userId = customUserDetails.getUser().getId();

return ApiResponse.onSuccess(chatService.postChatLike(chat_id, userId));
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package com.memesphere.domain.chat.converter;

import com.memesphere.domain.chat.entity.Chat;
import com.memesphere.domain.chat.entity.ChatLike;
import com.memesphere.domain.memecoin.entity.MemeCoin;
import com.memesphere.domain.chat.dto.request.ChatRequest;
import com.memesphere.domain.chat.dto.response.ChatResponse;
import com.memesphere.domain.user.entity.User;

public class ChatConverter {

public static Chat toChat(MemeCoin memeCoin, ChatRequest chatRequest) {
public static Chat toChat(MemeCoin memeCoin, ChatRequest chatRequest, User user) {

return Chat.builder()
// .user(user)
.user(user)
.memeCoin(memeCoin)
.message(chatRequest.getMessage())
.build();
Expand All @@ -27,4 +29,11 @@ public static ChatResponse toChatResponse(Chat chat) {
.nickname(chat.getUser().getNickname())
.build();
}

public static ChatLike toChatLike(Chat chat, User user) {
return ChatLike.builder()
.user(user)
.chat(chat)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
@NoArgsConstructor
public class ChatRequest {

@Schema(description = "메시지 내용")
@Schema(description = "닉네임", example = "코인전문가")
private String nickname;

@Schema(description = "메시지 내용", example = "도지코인 현재 얼마인가요?")
private String message;
}
2 changes: 1 addition & 1 deletion src/main/java/com/memesphere/domain/chat/entity/Chat.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class Chat extends BaseEntity {
@Column(name="chat_id")
private Long id;

@Column
@Column(columnDefinition = "varchar(500)", nullable = false)
private String message;

@ManyToOne(fetch=FetchType.LAZY)
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.memesphere.domain.chat.repository;

import com.memesphere.domain.chat.entity.Chat;
import com.memesphere.domain.chat.entity.ChatLike;
import com.memesphere.domain.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface ChatLikeRepository extends JpaRepository<ChatLike, Long> {

Optional<ChatLike> findByChatAndUser(Chat chat, User user);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.memesphere.domain.chat.repository;

import com.memesphere.domain.chat.entity.Chat;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ChatRepository extends JpaRepository<Chat, Long>, ChatCustomRepository {
import java.util.List;

public interface ChatRepository extends JpaRepository<Chat, Long> {

Chat findFirstByMemeCoin_IdOrderByCreatedAtDesc(Long coin_id);
Page<Chat> findAllByMemeCoin_Id(Long coin_id, Pageable pageable);
}
48 changes: 46 additions & 2 deletions src/main/java/com/memesphere/domain/chat/service/ChatService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import com.memesphere.domain.chat.converter.ChatConverter;
import com.memesphere.domain.chat.entity.Chat;
import com.memesphere.domain.chat.entity.ChatLike;
import com.memesphere.domain.chat.repository.ChatLikeRepository;
import com.memesphere.domain.user.entity.User;
import com.memesphere.domain.user.repository.UserRepository;
import com.memesphere.global.apipayload.code.status.ErrorStatus;
import com.memesphere.global.apipayload.exception.GeneralException;
import com.memesphere.domain.memecoin.entity.MemeCoin;
Expand All @@ -10,30 +14,47 @@
import com.memesphere.domain.memecoin.repository.MemeCoinRepository;
import com.memesphere.domain.chat.repository.ChatRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

@Service
@RequiredArgsConstructor
public class ChatService {

private final MemeCoinRepository memeCoinRepository;
private final ChatRepository chatRepository;
private final UserRepository userRepository;
private final ChatLikeRepository chatLikeRepository;

public ChatResponse saveMessage(Long coin_id, ChatRequest chatRequest) {

MemeCoin memeCoin = memeCoinRepository.findById(coin_id)
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMECOIN_NOT_FOUND));

Chat chat = ChatConverter.toChat(memeCoin, chatRequest);
User user = userRepository.findByNickname(chatRequest.getNickname())
.orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND));

Chat chat = ChatConverter.toChat(memeCoin, chatRequest, user);
Chat savedChat = chatRepository.save(chat);

return ChatConverter.toChatResponse(savedChat);
}

@Transactional
public Page<ChatResponse> getChatList(Long coin_id, Pageable pageable) {

Page<Chat> chatPage = chatRepository.findAllByMemeCoin_Id(coin_id, pageable);
return chatPage.map(ChatConverter::toChatResponse);
}

// 최신 댓글을 가져오는 메서드
@Transactional
public ChatResponse getLatestMessages(Long coin_id) {


MemeCoin memeCoin = memeCoinRepository.findById(coin_id)
//id에 해당하는 밈코인 없을 때
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMECOIN_NOT_FOUND));
Expand All @@ -50,4 +71,27 @@ public ChatResponse getLatestMessages(Long coin_id) {
return ChatConverter.toChatResponse(latestChat);
}

@Transactional
public String postChatLike(Long chat_id, Long user_id) {

User user = userRepository.findById(user_id)
.orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND));

Chat chat = chatRepository.findById(chat_id)
.orElseThrow(() -> new GeneralException(ErrorStatus.CHAT_NOT_FOUND));

// 사용자가 좋아요 눌렀는지 확인
Optional<ChatLike> existingLike = chatLikeRepository.findByChatAndUser(chat, user);
System.out.println("좋아요" + existingLike.isPresent());

if (existingLike.isPresent()) {
chatLikeRepository.delete(existingLike.get());
return "좋아요를 취소했습니다.";
} else {
ChatLike chatLike = ChatConverter.toChatLike(chat, user);
chatLikeRepository.save(chatLike);
return "좋아요를 눌렀습니다.";
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ public enum ErrorStatus implements BaseCode {

// 이미지 에러
INVALID_FILE_EXTENTION(HttpStatus.BAD_REQUEST, "INVALID FILE EXTENSION", "지원되지 않는 파일 형식입니다."),
PRESIGNED_URL_FAILED(HttpStatus.BAD_REQUEST, "PRESIGNED URL GENERATION FAILED", "presigned URL 생성에 실패했습니다.");
PRESIGNED_URL_FAILED(HttpStatus.BAD_REQUEST, "PRESIGNED URL GENERATION FAILED", "presigned URL 생성에 실패했습니다."),

// 채팅 에러
CHAT_NOT_FOUND(HttpStatus.NOT_FOUND, "CHAT NOT FOUND", "채팅을 찾을 수 없습니다.");

private final HttpStatus httpStatus;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.memesphere.global.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
Expand All @@ -19,11 +17,8 @@ public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer {
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("http://localhost:8080")
.withSockJS(); // ws, wss 대신 http, https를 통해 웹 소켓 연결하도록 함
registry.addEndpoint("/connection")
.setAllowedOriginPatterns("*");
registry.addEndpoint("/ws") //socket 연결 url: ws://localhost:8008/ws
.setAllowedOrigins("*");
}

/*
Expand All @@ -33,14 +28,9 @@ public void registerStompEndpoints(StompEndpointRegistry registry) {
public void configureMessageBroker(MessageBrokerRegistry registry) {

//sub 으로 시작되는 요청을 구독한 모든 사용자들에게 메시지를 broadcast한다.
registry.enableSimpleBroker("/sub");
registry.enableSimpleBroker("/sub"); // 구독 url
//pub로 시작되는 메시지는 message-handling method로 라우팅된다.
//클라이언트가 서버로 전송하는 메세지의 경로 앞에 /pub 붙임
registry.setApplicationDestinationPrefixes("/pub");
registry.setApplicationDestinationPrefixes("/pub"); // prefix 정의
}

// @Bean
// public WebSocketHandler webSocketHandler() {
// return new WebSocketHandler();
// }
}
Loading