diff --git a/src/main/java/com/assu/server/domain/chat/config/WebSocketConfig.java b/src/main/java/com/assu/server/domain/chat/config/WebSocketConfig.java index 0f9920b7..cdb02cb4 100644 --- a/src/main/java/com/assu/server/domain/chat/config/WebSocketConfig.java +++ b/src/main/java/com/assu/server/domain/chat/config/WebSocketConfig.java @@ -11,8 +11,17 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws/chat") // 클라이언트 WebSocket 연결 지점 - .setAllowedOriginPatterns("http://localhost:63342") + .setAllowedOriginPatterns( + "http://localhost:63342", + "http://localhost:5173", // Vite 기본 + "http://localhost:3000", // CRA/Next 기본 + "http://127.0.0.1:*", + "http://192.168.*.*:*") // 같은 LAN의 실제 기기 테스트용 .withSockJS(); // fallback for old browsers + + // ✅ 모바일/안드로이드용 (네이티브 WebSocket) + registry.addEndpoint("/ws/chat-native") + .setAllowedOriginPatterns("*"); // wss 사용 시 TLS 세팅 } @Override diff --git a/src/main/java/com/assu/server/domain/chat/controller/ChatController.java b/src/main/java/com/assu/server/domain/chat/controller/ChatController.java index 52b677d7..d7893a9b 100644 --- a/src/main/java/com/assu/server/domain/chat/controller/ChatController.java +++ b/src/main/java/com/assu/server/domain/chat/controller/ChatController.java @@ -4,16 +4,20 @@ import com.assu.server.domain.chat.dto.ChatResponseDTO; import com.assu.server.domain.chat.service.ChatService; import com.assu.server.global.apiPayload.code.status.SuccessStatus; +import com.assu.server.global.util.PrincipalDetails; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import com.assu.server.global.apiPayload.BaseResponse; import org.springframework.messaging.simp.SimpMessagingTemplate; import java.util.List; +@Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/chat") @@ -22,64 +26,89 @@ public class ChatController { private final SimpMessagingTemplate simpMessagingTemplate; @Operation( - summary = "채팅방 목록 조회 API", - description = "Request Header에 User id를 입력해 주세요." + summary = "채팅방을 생성하는 API", + description = "# [v1.0 (2025-08-05)](https://clumsy-seeder-416.notion.site/2241197c19ed80c38871ec77deced713) 채팅방을 생성합니다.\n"+ + "- storeId: Request Body, Long\n" + + "- partnerId: Request Body, Long\n" ) - @GetMapping("/rooms") - public BaseResponse> getChatRoomList() { - return BaseResponse.onSuccess(SuccessStatus._OK, chatService.getChatRoomList()); + @PostMapping("/rooms") + public BaseResponse createChatRoom( + @AuthenticationPrincipal PrincipalDetails pd, + @RequestBody ChatRequestDTO.CreateChatRoomRequestDTO request) { + Long memberId = pd.getMember().getId(); + return BaseResponse.onSuccess(SuccessStatus._OK, chatService.createChatRoom(request, memberId)); } @Operation( - summary = "채팅방 생성 API", - description = "상대방의 id를 request body에 입력해 주세요" + summary = "채팅방 목록을 조회하는 API", + description = "# [v1.0 (2025-08-05)](https://clumsy-seeder-416.notion.site/API-1d71197c19ed819f8f70fb437e9ce62b?p=2241197c19ed816993c3c5ae17d6f099&pm=s) 채팅방 목록을 조회합니다.\n" ) - @PostMapping("/create/rooms") - public BaseResponse createChatRoom(@RequestBody ChatRequestDTO.CreateChatRoomRequestDTO request) { - return BaseResponse.onSuccess(SuccessStatus._OK, chatService.createChatRoom(request)); + @GetMapping("/rooms") + public BaseResponse> getChatRoomList( + @AuthenticationPrincipal PrincipalDetails pd + ) { + Long memberId = pd.getMember().getId(); + return BaseResponse.onSuccess(SuccessStatus._OK, chatService.getChatRoomList(memberId)); } @Operation( summary = "채팅 API", - description = "roomId, senderId, message를 입력해 주세요" + description = "# [v1.0 (2025-08-05)](https://clumsy-seeder-416.notion.site/2241197c19ed800eab45c35073761c97?v=2241197c19ed8134b64f000cc26c5d31&p=2371197c19ed80968342e2bc8fe88cee&pm=s) 메시지를 전송합니다.\n"+ + "- roomId: Request Body, Long\n" + + "- senderId: Request Body, Long\n"+ + "- receiverId: Request Body, Long\n" + + "- message: Request Body, String\n" ) @MessageMapping("/send") public void handleMessage(@Payload ChatRequestDTO.ChatMessageRequestDTO request) { + log.info("[WS] handleMessage IN: {}", request); // ★ 호출 여부 확인 ChatResponseDTO.SendMessageResponseDTO response = chatService.handleMessage(request); - + log.info("[WS] handleMessage SAVED id={}", response.messageId()); // 저장 확인용 simpMessagingTemplate.convertAndSend("/sub/chat/" + request.roomId(), response); } @Operation( summary = "메시지 읽음 처리 API", - description = "roomId를 입력해 주세요." + description = "# [v1.0 (2025-08-05)](https://clumsy-seeder-416.notion.site/2241197c19ed800eab45c35073761c97?v=2241197c19ed8134b64f000cc26c5d31&p=2241197c19ed81ffa771cb18ab157b54&pm=s) 메시지를 읽음처리합니다.\n"+ + "- roomId: Path Variable, Long\n" ) @PatchMapping("rooms/{roomId}/read") public BaseResponse readMessage( - @PathVariable Long roomId) { - ChatResponseDTO.ReadMessageResponseDTO response = chatService.readMessage(roomId); + @AuthenticationPrincipal PrincipalDetails pd, + @PathVariable Long roomId + ) { + Long memberId = pd.getMember().getId(); + ChatResponseDTO.ReadMessageResponseDTO response = chatService.readMessage(roomId, memberId); return BaseResponse.onSuccess(SuccessStatus._OK, response); } @Operation( summary = "채팅방 상세 조회 API", - description = "roomId를 입력해 주세요." + description = "# [v1.0 (2025-08-05)](https://clumsy-seeder-416.notion.site/2241197c19ed800eab45c35073761c97?v=2241197c19ed8134b64f000cc26c5d31&p=2241197c19ed81399395fd66f73730af&pm=s) 채팅방을 클릭했을 때 메시지를 조회합니다.\n"+ + "- roomId: Path Variable, Long\n" ) @GetMapping("rooms/{roomId}/messages") - public BaseResponse getChatHistory(@PathVariable Long roomId) { - ChatResponseDTO.ChatHistoryResponseDTO response = chatService.readHistory(roomId); + public BaseResponse getChatHistory( + @AuthenticationPrincipal PrincipalDetails pd, + @PathVariable Long roomId + ) { + Long memberId = pd.getMember().getId(); + ChatResponseDTO.ChatHistoryResponseDTO response = chatService.readHistory(roomId, memberId); return BaseResponse.onSuccess(SuccessStatus._OK, response); } @Operation( - summary = "채팅방 나가기 API" + - " (참여자가 2명이면 채팅방이 살아있지만, 이미 한 명이 나갔다면 채팅방이 삭제됩니다.)", - description = "roomId를 입력해 주세요." + summary = "채팅방을 나가는 API" + + "참여자가 2명이면 채팅방이 살아있지만, 이미 한 명이 나갔다면 채팅방이 삭제됩니다.", + description = "# [v1.0 (2025-08-05)](https://clumsy-seeder-416.notion.site/2241197c19ed800eab45c35073761c97?v=2241197c19ed8134b64f000cc26c5d31&p=2371197c19ed8079a6e1c2331cb4f534&pm=s) 채팅방을 나갑니다.\n"+ + "- roomId: Path Variable, Long\n" ) @DeleteMapping("rooms/{roomId}/leave") public BaseResponse leaveChattingRoom( + @AuthenticationPrincipal PrincipalDetails pd, @PathVariable Long roomId ) { - return BaseResponse.onSuccess(SuccessStatus._OK, chatService.leaveChattingRoom(roomId)); + Long memberId = pd.getMember().getId(); + return BaseResponse.onSuccess(SuccessStatus._OK, chatService.leaveChattingRoom(roomId, memberId)); } } diff --git a/src/main/java/com/assu/server/domain/chat/converter/ChatConverter.java b/src/main/java/com/assu/server/domain/chat/converter/ChatConverter.java index a84621fa..70fe10c2 100644 --- a/src/main/java/com/assu/server/domain/chat/converter/ChatConverter.java +++ b/src/main/java/com/assu/server/domain/chat/converter/ChatConverter.java @@ -44,7 +44,12 @@ public static ChattingRoom toCreateChattingRoom(Admin admin, Partner partner) { } public static ChatResponseDTO.CreateChatRoomResponseDTO toCreateChatRoomIdDTO(ChattingRoom room) { - return new ChatResponseDTO.CreateChatRoomResponseDTO(room.getId()); + return ChatResponseDTO.CreateChatRoomResponseDTO.builder() + .roomId(room.getId()) + .adminViewName(room.getPartner().getName()) + .partnerViewName(room.getAdmin().getName()) + .build(); + } public static Message toMessageEntity(ChatRequestDTO.ChatMessageRequestDTO request, ChattingRoom room, Member sender, Member receiver) { @@ -60,8 +65,10 @@ public static ChatResponseDTO.SendMessageResponseDTO toSendMessageDTO(Message me return ChatResponseDTO.SendMessageResponseDTO.builder() .roomId(message.getChattingRoom().getId()) .senderId(message.getSender().getId()) + .receiverId(message.getReceiver().getId()) .message(message.getMessage()) .sentAt(message.getCreatedAt()) + .messageType(message.getType()) .build(); } diff --git a/src/main/java/com/assu/server/domain/chat/dto/ChatMessageDTO.java b/src/main/java/com/assu/server/domain/chat/dto/ChatMessageDTO.java index 628af1db..ad20a6f1 100644 --- a/src/main/java/com/assu/server/domain/chat/dto/ChatMessageDTO.java +++ b/src/main/java/com/assu/server/domain/chat/dto/ChatMessageDTO.java @@ -1,6 +1,7 @@ package com.assu.server.domain.chat.dto; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -21,6 +22,9 @@ public class ChatMessageDTO { private String message; private LocalDateTime sendTime; + @JsonProperty("isRead") private boolean isRead; + + @JsonProperty("isMyMessage") private boolean isMyMessage; } diff --git a/src/main/java/com/assu/server/domain/chat/dto/ChatRequestDTO.java b/src/main/java/com/assu/server/domain/chat/dto/ChatRequestDTO.java index 90798fcd..2123afc2 100644 --- a/src/main/java/com/assu/server/domain/chat/dto/ChatRequestDTO.java +++ b/src/main/java/com/assu/server/domain/chat/dto/ChatRequestDTO.java @@ -5,7 +5,7 @@ public class ChatRequestDTO { @Getter public static class CreateChatRoomRequestDTO { - private Long adminId; + private Long storeId; private Long partnerId; } diff --git a/src/main/java/com/assu/server/domain/chat/dto/ChatResponseDTO.java b/src/main/java/com/assu/server/domain/chat/dto/ChatResponseDTO.java index 8c4790aa..29bbaa16 100644 --- a/src/main/java/com/assu/server/domain/chat/dto/ChatResponseDTO.java +++ b/src/main/java/com/assu/server/domain/chat/dto/ChatResponseDTO.java @@ -1,5 +1,8 @@ package com.assu.server.domain.chat.dto; +import com.assu.server.domain.chat.entity.enums.MessageType; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.google.protobuf.Enum; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -16,21 +19,30 @@ public class ChatResponseDTO { @Builder public static class CreateChatRoomResponseDTO { private Long roomId; + private String adminViewName; + private String partnerViewName; } // 메시지 전송 @Builder public record SendMessageResponseDTO( + Long messageId, Long roomId, Long senderId, + Long receiverId, String message, + MessageType messageType, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime sentAt ) {} // 메시지 읽음 처리 public record ReadMessageResponseDTO( Long roomId, - int readCount + Long readerId, + List readMessagesId, + int readCount, + boolean isRead ) {} // 채팅방 들어갔을 때 조회 diff --git a/src/main/java/com/assu/server/domain/chat/service/ChatService.java b/src/main/java/com/assu/server/domain/chat/service/ChatService.java index bc44c561..e11b3c13 100644 --- a/src/main/java/com/assu/server/domain/chat/service/ChatService.java +++ b/src/main/java/com/assu/server/domain/chat/service/ChatService.java @@ -6,10 +6,10 @@ import java.util.List; public interface ChatService { - List getChatRoomList(); - ChatResponseDTO.CreateChatRoomResponseDTO createChatRoom(ChatRequestDTO.CreateChatRoomRequestDTO request); + List getChatRoomList(Long memberId); + ChatResponseDTO.CreateChatRoomResponseDTO createChatRoom(ChatRequestDTO.CreateChatRoomRequestDTO request, Long memberId); ChatResponseDTO.SendMessageResponseDTO handleMessage(ChatRequestDTO.ChatMessageRequestDTO request); - ChatResponseDTO.ReadMessageResponseDTO readMessage(Long roomId); - ChatResponseDTO.ChatHistoryResponseDTO readHistory(Long roomId); - ChatResponseDTO.LeaveChattingRoomResponseDTO leaveChattingRoom(Long roomId); + ChatResponseDTO.ReadMessageResponseDTO readMessage(Long roomId, Long memberId); + ChatResponseDTO.ChatHistoryResponseDTO readHistory(Long roomId, Long memberId); + ChatResponseDTO.LeaveChattingRoomResponseDTO leaveChattingRoom(Long roomId, Long memberId); } diff --git a/src/main/java/com/assu/server/domain/chat/service/ChatServiceImpl.java b/src/main/java/com/assu/server/domain/chat/service/ChatServiceImpl.java index 8b4e1e3c..e4ef31b3 100644 --- a/src/main/java/com/assu/server/domain/chat/service/ChatServiceImpl.java +++ b/src/main/java/com/assu/server/domain/chat/service/ChatServiceImpl.java @@ -16,13 +16,21 @@ import com.assu.server.domain.member.repository.MemberRepository; import com.assu.server.domain.partner.entity.Partner; import com.assu.server.domain.partner.repository.PartnerRepository; +import com.assu.server.domain.store.entity.Store; +import com.assu.server.domain.store.repository.StoreRepository; import com.assu.server.global.apiPayload.code.status.ErrorStatus; import com.assu.server.global.exception.DatabaseException; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; + +import java.util.ArrayList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +@Slf4j @Service @RequiredArgsConstructor public class ChatServiceImpl implements ChatService { @@ -31,29 +39,33 @@ public class ChatServiceImpl implements ChatService { private final PartnerRepository partnerRepository; private final AdminRepository adminRepository; private final MessageRepository messageRepository; + private final StoreRepository storeRepository; @Override - public List getChatRoomList() { -// Long memberId = SecurityUtil.getCurrentUserId; - Long memberId = 1L; + public List getChatRoomList(Long memberId) { List chatRoomList = chatRepository.findChattingRoomsByMemberId(memberId); return ChatConverter.toChatRoomListResultDTO(chatRoomList); } @Override - public ChatResponseDTO.CreateChatRoomResponseDTO createChatRoom(ChatRequestDTO.CreateChatRoomRequestDTO request) { -// Long memberId = SecurityUtil.getCurrentUserId; -// Long opponentId = request.getOpponentId(); + public ChatResponseDTO.CreateChatRoomResponseDTO createChatRoom(ChatRequestDTO.CreateChatRoomRequestDTO request, Long memberId) { - Long adminId = request.getAdminId(); + Long storeId = request.getStoreId(); Long partnerId = request.getPartnerId(); - Admin admin = adminRepository.findById(adminId) + Admin admin = adminRepository.findById(memberId) .orElseThrow(() -> new DatabaseException(ErrorStatus.NO_SUCH_ADMIN)); Partner partner = partnerRepository.findById(partnerId) .orElseThrow(() -> new DatabaseException(ErrorStatus.NO_SUCH_PARTNER)); + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new DatabaseException(ErrorStatus.NO_SUCH_STORE)); + + + if (!store.getPartner().getMember().getId().equals(partner.getMember().getId())) { + throw new DatabaseException(ErrorStatus.NO_SUCH_STORE_WITH_THAT_PARTNER); + } ChattingRoom room = ChatConverter.toCreateChattingRoom(admin, partner); @@ -66,13 +78,11 @@ public ChatResponseDTO.CreateChatRoomResponseDTO createChatRoom(ChatRequestDTO.C admin.getName() ); ChattingRoom savedRoom = chatRepository.save(room); - - - return ChatConverter.toCreateChatRoomIdDTO(savedRoom); } @Override + @Transactional public ChatResponseDTO.SendMessageResponseDTO handleMessage(ChatRequestDTO.ChatMessageRequestDTO request) { // 유효성 검사 ChattingRoom room = chatRepository.findById(request.roomId()) @@ -83,43 +93,48 @@ public ChatResponseDTO.SendMessageResponseDTO handleMessage(ChatRequestDTO.ChatM .orElseThrow(() -> new DatabaseException(ErrorStatus.NO_SUCH_MEMBER)); Message message = ChatConverter.toMessageEntity(request, room, sender, receiver); - messageRepository.save(message); - - return ChatConverter.toSendMessageDTO(message); +// messageRepository.save(message); + log.info("saved message start"); + Message saved = messageRepository.saveAndFlush(message); + log.info("saved message middle"); + log.info("saved message id={}, roomId={}, senderId={}, receiverId={}", + saved.getId(), room.getId(), sender.getId(), receiver.getId()); + + log.info("saved message end"); + boolean exists = messageRepository.existsById(saved.getId()); + log.info("Saved? {}", exists); // true 아니면 트랜잭션/DB 문제 + return ChatConverter.toSendMessageDTO(saved); } @Transactional @Override - public ChatResponseDTO.ReadMessageResponseDTO readMessage(Long roomId) { -// Long memberId = SecurityUtil.getCurrentUserId(); - Long memberId = 2L; + public ChatResponseDTO.ReadMessageResponseDTO readMessage(Long roomId, Long memberId) { List unreadMessages = messageRepository.findUnreadMessagesByRoomAndReceiver(roomId, memberId); + List readMessagesIdList = new ArrayList<>(); + for(Message unreadMessage : unreadMessages) { + readMessagesIdList.add(unreadMessage.getId()); + } unreadMessages.forEach(Message::markAsRead); - return new ChatResponseDTO.ReadMessageResponseDTO(roomId, unreadMessages.size()); + + return new ChatResponseDTO.ReadMessageResponseDTO(roomId, memberId,readMessagesIdList, unreadMessages.size(), true); } @Override - public ChatResponseDTO.ChatHistoryResponseDTO readHistory(Long roomId) { -// Long memberId = SecurityUtil.getCurrentUserId(); - Long memberId = 1L; + public ChatResponseDTO.ChatHistoryResponseDTO readHistory(Long roomId, Long memberId) { ChattingRoom room = chatRepository.findById(roomId) .orElseThrow(() -> new DatabaseException(ErrorStatus.NO_SUCH_ROOM)); - List allMessages = messageRepository.findAllMessagesByRoomAndMemberId(roomId, memberId); + List allMessages = messageRepository.findAllMessagesByRoomAndMemberId(room.getId(), memberId); - return ChatConverter.toChatHistoryDTO(roomId, allMessages); + return ChatConverter.toChatHistoryDTO(room.getId(), allMessages); } @Override - public ChatResponseDTO.LeaveChattingRoomResponseDTO leaveChattingRoom(Long roomId) { -// Long memberId = SecurityUtil.getCurrentUserId(); - - Long memberId = 2L; - + public ChatResponseDTO.LeaveChattingRoomResponseDTO leaveChattingRoom(Long roomId, Long memberId) { // 멤버 조회 Member member = memberRepository.findById(memberId) .orElseThrow(() -> new DatabaseException(ErrorStatus.NO_SUCH_MEMBER)); diff --git a/src/main/java/com/assu/server/domain/store/entity/Store.java b/src/main/java/com/assu/server/domain/store/entity/Store.java index e756de02..7c460ba2 100644 --- a/src/main/java/com/assu/server/domain/store/entity/Store.java +++ b/src/main/java/com/assu/server/domain/store/entity/Store.java @@ -3,15 +3,7 @@ import com.assu.server.domain.common.enums.ActivationStatus; import com.assu.server.domain.partner.entity.Partner; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; +import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; @@ -28,7 +20,7 @@ public class Store extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne(fetch = FetchType.LAZY) + @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "partner_id") private Partner partner; diff --git a/src/main/java/com/assu/server/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/assu/server/global/apiPayload/code/status/ErrorStatus.java index af3be54c..f0d331a3 100644 --- a/src/main/java/com/assu/server/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/assu/server/global/apiPayload/code/status/ErrorStatus.java @@ -51,10 +51,12 @@ public enum ErrorStatus implements BaseErrorCode { NO_SUCH_STORE(HttpStatus.NOT_FOUND, "STORE_4006", "존재하지 않는 스토어 ID입니다."), NO_SUCH_USAGE(HttpStatus.NOT_FOUND, "USAGE4001", "존재하지 않는 제휴 사용 내역입니다."), NO_PAPER_FOR_STORE(HttpStatus.NOT_FOUND, "ADMIN_4005", "존재하지 않는 paper ID입니다."), - EXISTED_PHONE(HttpStatus.NOT_FOUND,"MEMBER_4005","이미 존재하는 전화번호입니다."), - EXISTED_EMAIL(HttpStatus.NOT_FOUND,"MEMBER_4006","이미 존재하는 이메일입니다."), - EXISTED_STUDENT(HttpStatus.NOT_FOUND,"MEMBER_4007","이미 존재하는 학번입니다."), NO_AVAILABLE_PARTNER(HttpStatus.NOT_FOUND, "MEMBER_4009", "제휴업체를 찾을 수 없습니다."), + NO_SUCH_STORE_WITH_THAT_PARTNER(HttpStatus.NOT_FOUND,"MEMBER_4006","해당 store ID에 해당하는 partner ID가 존재하지 않습니다."), + EXISTED_PHONE(HttpStatus.NOT_FOUND,"MEMBER_4007","이미 존재하는 전화번호입니다."), + EXISTED_EMAIL(HttpStatus.NOT_FOUND,"MEMBER_4008","이미 존재하는 이메일입니다."), + EXISTED_STUDENT(HttpStatus.NOT_FOUND,"MEMBER_4009","이미 존재하는 학번입니다."), + MEMBER_ALREADY_WITHDRAWN(HttpStatus.BAD_REQUEST, "MEMBER_4010", "이미 탈퇴된 회원입니다."), // 제휴 에러 diff --git a/src/main/java/com/assu/server/global/config/SecurityConfig.java b/src/main/java/com/assu/server/global/config/SecurityConfig.java index a11cfad1..8d7f72b8 100644 --- a/src/main/java/com/assu/server/global/config/SecurityConfig.java +++ b/src/main/java/com/assu/server/global/config/SecurityConfig.java @@ -21,6 +21,9 @@ public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthFilter jwtAuthF .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() + // ✅ WebSocket 핸드셰이크 허용 (네이티브 + SockJS 모두 포함) + .requestMatchers("/ws/**").permitAll() + // Swagger 등 공개 리소스 .requestMatchers( "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", diff --git a/src/main/java/com/assu/server/global/util/PrincipalDetails.java b/src/main/java/com/assu/server/global/util/PrincipalDetails.java index 1e21ab1b..dc7582be 100644 --- a/src/main/java/com/assu/server/global/util/PrincipalDetails.java +++ b/src/main/java/com/assu/server/global/util/PrincipalDetails.java @@ -13,9 +13,11 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; @Getter @Builder diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4c103422..61918f57 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -19,14 +19,14 @@ spring: highlight_sql : true lifecycle: timeout-per-shutdown-phase: 30s - rabbitmq: - listener: - simple: - acknowledge-mode: manual - prefetch: 20 - concurrency: 1 - max-concurrency: 4 - default-requeue-rejected: false + rabbitmq: + listener: + simple: + acknowledge-mode: manual + prefetch: 20 + concurrency: 1 + max-concurrency: 4 + default-requeue-rejected: false logging: level: diff --git a/src/test/java/com/assu/server/ServerApplicationTests.java b/src/test/java/com/assu/server/ServerApplicationTests.java index 64bd3ef7..da762c7d 100644 --- a/src/test/java/com/assu/server/ServerApplicationTests.java +++ b/src/test/java/com/assu/server/ServerApplicationTests.java @@ -1,6 +1,7 @@ package com.assu.server; import com.assu.server.domain.auth.security.jwt.JwtUtil; +//import com.google.api.client.http.javanet.ConnectionFactory; import com.google.firebase.messaging.FirebaseMessaging; import org.junit.jupiter.api.Test; import org.mockito.Mock; @@ -22,8 +23,8 @@ @ActiveProfiles("test") class ServerApplicationTests { - @Mock - private FirebaseMessaging firebaseMessaging; + @Mock + private FirebaseMessaging firebaseMessaging; @MockitoBean private ConnectionFactory connectionFactory; @@ -37,40 +38,40 @@ FirebaseMessaging firebaseMessaging() { return Mockito.mock(FirebaseMessaging.class); } - @Bean - RedisConnectionFactory redisConnectionFactory() { - return Mockito.mock(RedisConnectionFactory.class); - } + @Bean + RedisConnectionFactory redisConnectionFactory() { + return Mockito.mock(RedisConnectionFactory.class); + } - @Bean + @Bean @SuppressWarnings("unchecked") - RedisTemplate redisTemplate() { - return Mockito.mock(RedisTemplate.class); - } - - @Bean - StringRedisTemplate stringRedisTemplate() { - return Mockito.mock(StringRedisTemplate.class); - } - - @Bean - JwtUtil jwtUtil() { - return Mockito.mock(JwtUtil.class); - } - - @Bean(name = "rabbitListenerContainerFactory") - RabbitListenerContainerFactory rabbitListenerContainerFactory() { - var factory = Mockito.mock(RabbitListenerContainerFactory.class); - var container = Mockito.mock(org.springframework.amqp.rabbit.listener.MessageListenerContainer.class); - Mockito.when(factory.createListenerContainer(Mockito.any())) - .thenReturn(container); - return factory; - } - - } - - @Test - void contextLoads() { - } + RedisTemplate redisTemplate() { + return Mockito.mock(RedisTemplate.class); + } + + @Bean + StringRedisTemplate stringRedisTemplate() { + return Mockito.mock(StringRedisTemplate.class); + } + + @Bean + JwtUtil jwtUtil() { + return Mockito.mock(JwtUtil.class); + } + + @Bean(name = "rabbitListenerContainerFactory") + RabbitListenerContainerFactory rabbitListenerContainerFactory() { + var factory = Mockito.mock(RabbitListenerContainerFactory.class); + var container = Mockito.mock(org.springframework.amqp.rabbit.listener.MessageListenerContainer.class); + Mockito.when(factory.createListenerContainer(Mockito.any())) + .thenReturn(container); + return factory; + } + + } + + @Test + void contextLoads() { + } } diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index ac859d1f..8a3197b2 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -21,6 +21,11 @@ jwt: access-valid-seconds: 3600 refresh-valid-seconds: 1209600 +assu: + security: + school-crypto: + base64-key: "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=" #"dummy-base64-key"를 Base64로 인코딩한 값 + messaging: rabbit: enabled: false