diff --git a/.github/workflows/be-cd.yml b/.github/workflows/be-cd.yml index d5c82b70..8117b3df 100644 --- a/.github/workflows/be-cd.yml +++ b/.github/workflows/be-cd.yml @@ -33,7 +33,7 @@ jobs: run: | chmod +x gradlew echo "::group::Gradle Build Logs" - ./gradlew build --exclude-task test --exclude-task asciidoctor + ./gradlew build --warning-mode all echo "::endgroup::" working-directory: ${{ env.ROOT_PATH }} diff --git a/be/src/main/java/movlit/be/auth/application/service/MyMemberDetails.java b/be/src/main/java/movlit/be/auth/application/service/MyMemberDetails.java index daa25aaf..dd544662 100644 --- a/be/src/main/java/movlit/be/auth/application/service/MyMemberDetails.java +++ b/be/src/main/java/movlit/be/auth/application/service/MyMemberDetails.java @@ -48,7 +48,6 @@ public Map getAttributes() { @Override public Collection getAuthorities() { - log.info("====== new SimpleGrantedAuthority(member.getRole())={}", new SimpleGrantedAuthority(member.getRole())); return Collections.singletonList( new SimpleGrantedAuthority(member.getRole()) ); @@ -60,7 +59,7 @@ public String getPassword() { } public MemberId getMemberId() { - if (member != null){ + if (member != null) { return member.getMemberId(); } @@ -68,7 +67,7 @@ public MemberId getMemberId() { } public Member getMember() { - if (member != null){ + if (member != null) { return member; } diff --git a/be/src/main/java/movlit/be/auth/application/service/OAuth2AuthenticationSuccessHandler.java b/be/src/main/java/movlit/be/auth/application/service/OAuth2AuthenticationSuccessHandler.java index b17230b3..f2034e07 100644 --- a/be/src/main/java/movlit/be/auth/application/service/OAuth2AuthenticationSuccessHandler.java +++ b/be/src/main/java/movlit/be/auth/application/service/OAuth2AuthenticationSuccessHandler.java @@ -25,7 +25,6 @@ public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS private final AuthCodeStorage authCodeStorage; private final JwtTokenUtil jwtTokenUtil; - @Value("${share.url}") private String url; @@ -36,11 +35,9 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo // OAuth2User로부터 email을 가져옴 MyMemberDetails oAuth2User = (MyMemberDetails) authentication.getPrincipal(); String email = oAuth2User.getMember().getEmail(); - log.info("OAuth2AuthenticationSuccessHandler의 Member email = {}", email); String code = IdGenerator.generate(); authCodeStorage.saveCode(code, email); - log.info("======== code = {}, email = {}", code, email); // 헤더 설정 String accessToken = jwtTokenUtil.generateAccessToken(email); diff --git a/be/src/main/java/movlit/be/auth/presentation/AuthenticationController.java b/be/src/main/java/movlit/be/auth/presentation/AuthenticationController.java index 1527ab8e..b2ffeffc 100644 --- a/be/src/main/java/movlit/be/auth/presentation/AuthenticationController.java +++ b/be/src/main/java/movlit/be/auth/presentation/AuthenticationController.java @@ -83,9 +83,6 @@ public ResponseEntity exchangeToken(@RequestBody Map body) { final String accessToken = jwtTokenUtil.generateAccessToken(email); final String refreshToken = jwtTokenUtil.generateRefreshToken(email); - log.info("======== accessToken={}", accessToken); - log.info("======== refreshToken={}", refreshToken); - refreshTokenStorage.saveRefreshToken(email, refreshToken); authCodeStorage.removeCode(code); diff --git a/be/src/main/java/movlit/be/book/getBookApi/GetBookController.java b/be/src/main/java/movlit/be/book/getBookApi/GetBookController.java index f3750f13..34c175c1 100644 --- a/be/src/main/java/movlit/be/book/getBookApi/GetBookController.java +++ b/be/src/main/java/movlit/be/book/getBookApi/GetBookController.java @@ -17,19 +17,19 @@ public class GetBookController { // BookBestseller 저장 api @GetMapping("/saveBooks/bestseller") public void BestsellersApiToDb() { - getBookBestService.repeatGet(20); // 한번에 최대 50개씩, 20번 실행 + getBookBestService.repeatGet(10); // 한번에 최대 50개씩, 20번 실행 } // BookNew 저장 api @GetMapping("/saveBooks/bookNew") public void BookNewApiToDb() { - getBookNewService.repeatGet(5); // 한번에 최대 50개씩, 20번 실행 + getBookNewService.repeatGet(2); // 한번에 최대 50개씩, 20번 실행 } // BookNewSpecial 저장 api @GetMapping("/saveBooks/bookNewSpecial") public void BookNewSpecialApiToDb() { - getBookNewSpecialService.repeatGet(10); // 한번에 최대 50개씩, 20번 실행 + getBookNewSpecialService.repeatGet(5); // 한번에 최대 50개씩, 20번 실행 } diff --git a/be/src/main/java/movlit/be/common/filter/JwtRequestFilter.java b/be/src/main/java/movlit/be/common/filter/JwtRequestFilter.java index 6d90cebc..73845709 100644 --- a/be/src/main/java/movlit/be/common/filter/JwtRequestFilter.java +++ b/be/src/main/java/movlit/be/common/filter/JwtRequestFilter.java @@ -31,7 +31,6 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse // TODO: Oauth2 로그인 시에만 header가 넘어오지 않음 final String authorizationHeader = request.getHeader("Authorization"); - log.info("===== [JwtRequestFilter] authorization Header= {} ", authorizationHeader); String email = null; String jwt = null; diff --git a/be/src/main/java/movlit/be/follow/application/service/FollowWriteService.java b/be/src/main/java/movlit/be/follow/application/service/FollowWriteService.java index f59fcf80..1e925ffe 100644 --- a/be/src/main/java/movlit/be/follow/application/service/FollowWriteService.java +++ b/be/src/main/java/movlit/be/follow/application/service/FollowWriteService.java @@ -81,7 +81,8 @@ public void publishFollowNotification(MemberEntity follower, MemberEntity follow // 알림 메세지 생성 : 'A'님이 'B'님을 팔로우합니다. String message = NotificationMessage.generateFollowingMessage(follower.getNickname(), followee.getNickname()); String memberId = followee.getMemberId().getValue(); - String url = basicUrl + "/members/" + memberId; + // 나를 팔로우한 사람(상대방)의 '마이페이지'로 이동 + String url = basicUrl + "/members/" + follower.getMemberId().getValue(); // 알림 DTO 생성 NotificationDto notificationDto = new NotificationDto( diff --git a/be/src/main/java/movlit/be/member/application/service/MemberReadService.java b/be/src/main/java/movlit/be/member/application/service/MemberReadService.java index aef985c6..09d31e9b 100644 --- a/be/src/main/java/movlit/be/member/application/service/MemberReadService.java +++ b/be/src/main/java/movlit/be/member/application/service/MemberReadService.java @@ -96,7 +96,7 @@ public MemberEntity findEntityById(MemberId memberId) { entityManager.clear(); // 1차 캐시 초기화 (안하면, 프로필 업데이트된 멤버정보를 제대로 조회 안하고, JPA에서 프로필업데이트되기 전, 멤버정보를 조회한다) MemberEntity memberEntity = memberRepository.findEntityById(memberId); - System.out.println("MemberReadService >>> 찾은 memberEntity " + memberEntity.toStringExceptLazyLoading()); + System.out.println("::MemberReadService >>> 찾은 memberEntity " + memberEntity.toStringExceptLazyLoading()); return memberEntity; } diff --git a/be/src/main/java/movlit/be/pub_sub/RedisListenerConfig.java b/be/src/main/java/movlit/be/pub_sub/RedisListenerConfig.java index f1c037d2..c31b71a9 100644 --- a/be/src/main/java/movlit/be/pub_sub/RedisListenerConfig.java +++ b/be/src/main/java/movlit/be/pub_sub/RedisListenerConfig.java @@ -37,31 +37,52 @@ public ChannelTopic updateRoomTopic() { return new ChannelTopic("updateRoom"); } + @Bean + public ChannelTopic createOneononeChatroomTopic() { + return new ChannelTopic("createOneononeChatroom"); + } + @Bean public ChannelTopic readMessageTopic() { return new ChannelTopic("readMessage"); } - /** 메시지 전송을 처리하는 subscriber 설정 추가*/ + /** + * 메시지 전송을 처리하는 subscriber 설정 추가 + */ @Bean public MessageListenerAdapter listenerAdapterSendMessage(RedisMessageSubscriber subscriber) { // subscriber 내의 메서드명을 지정 return new MessageListenerAdapter(subscriber, "sendMessage"); } - /** 채팅방정보 변경을 처리하는 subscriber 설정 추가*/ + /** + * 채팅방정보 변경을 처리하는 subscriber 설정 추가 + */ @Bean public MessageListenerAdapter listenerAdapterUpdateRoom(RedisMessageSubscriber subscriber) { return new MessageListenerAdapter(subscriber, "updateRoom"); } - /** 채팅 읽음을 처리하는 subscriber 설정 추가*/ + /** + * 채팅 읽음을 처리하는 subscriber 설정 추가 + */ @Bean public MessageListenerAdapter listenerAdapterReadMessage(RedisMessageSubscriber subscriber) { return new MessageListenerAdapter(subscriber, "readMessage"); } - /** 알림 메시지를 처리하는 subscriber 설정 추가*/ + /** + * 일대일 채팅방 시작되는 subscriber 설정 추가 + */ + @Bean + public MessageListenerAdapter listenerAdapterCreateOneononeChatroom(RedisMessageSubscriber subscriber) { + return new MessageListenerAdapter(subscriber, "createOneononeChatroom"); + } + + /** + * 알림 메시지를 처리하는 subscriber 설정 추가 + */ @Bean public MessageListenerAdapter listenerAdapterNotification(RedisNotificationSubscriber notificationSubscriber) { return new MessageListenerAdapter(notificationSubscriber, "onNotification"); // 메서드명 변경 @@ -76,10 +97,12 @@ public RedisMessageListenerContainer redisMessageListener( MessageListenerAdapter listenerAdapterSendMessage, MessageListenerAdapter listenerAdapterUpdateRoom, MessageListenerAdapter listenerAdapterReadMessage, + MessageListenerAdapter listenerAdapterCreateOneononeChatroom, MessageListenerAdapter listenerAdapterNotification, ChannelTopic sendMessageTopic, ChannelTopic updateRoomTopic, ChannelTopic readMessageTopic, + ChannelTopic createOneononeChatroomTopic, ChannelTopic notificationTopic ) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); @@ -88,6 +111,7 @@ public RedisMessageListenerContainer redisMessageListener( container.addMessageListener(listenerAdapterSendMessage, sendMessageTopic); container.addMessageListener(listenerAdapterUpdateRoom, updateRoomTopic); container.addMessageListener(listenerAdapterReadMessage, readMessageTopic); + container.addMessageListener(listenerAdapterCreateOneononeChatroom, createOneononeChatroomTopic); container.addMessageListener(listenerAdapterNotification, notificationTopic); return container; } diff --git a/be/src/main/java/movlit/be/pub_sub/RedisMessagePublisher.java b/be/src/main/java/movlit/be/pub_sub/RedisMessagePublisher.java index 144ef8c6..c68349f2 100644 --- a/be/src/main/java/movlit/be/pub_sub/RedisMessagePublisher.java +++ b/be/src/main/java/movlit/be/pub_sub/RedisMessagePublisher.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import movlit.be.pub_sub.chatMessage.presentation.dto.response.ChatMessageDto; +import movlit.be.pub_sub.chatRoom.presentation.dto.OneononeChatroomCreatePubDto; import movlit.be.pub_sub.chatRoom.presentation.dto.UpdateRoomDto; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.listener.ChannelTopic; @@ -19,9 +20,11 @@ public class RedisMessagePublisher { private final RedisTemplate redisTemplate; private final ChannelTopic sendMessageTopic; private final ChannelTopic updateRoomTopic; + private final ChannelTopic createOneononeChatroomTopic; /** * 채팅 보내기(sendMessage) 토픽 발행하는 메서드 + * * @param chatMessageDto */ public void sendMessage(ChatMessageDto chatMessageDto) { @@ -40,4 +43,12 @@ public void updateRoom(UpdateRoomDto updateRoomDto) { redisTemplate.convertAndSend(updateRoomTopic.getTopic(), updateRoomDto); } + /** + * 일대일 채팅방 생성 토픽 발행하는 메서드 + */ + public void createOneononeChatroom(OneononeChatroomCreatePubDto oneononeChatroomCreatePubDto) { + log.info("Publishing create oneononeChatroom {}", oneononeChatroomCreatePubDto); + redisTemplate.convertAndSend(createOneononeChatroomTopic.getTopic(), oneononeChatroomCreatePubDto); + } + } \ No newline at end of file diff --git a/be/src/main/java/movlit/be/pub_sub/RedisMessageSubscriber.java b/be/src/main/java/movlit/be/pub_sub/RedisMessageSubscriber.java index 61db124c..49993da4 100644 --- a/be/src/main/java/movlit/be/pub_sub/RedisMessageSubscriber.java +++ b/be/src/main/java/movlit/be/pub_sub/RedisMessageSubscriber.java @@ -2,9 +2,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import movlit.be.common.exception.ContentTypeNotExistException; @@ -12,12 +9,17 @@ import movlit.be.pub_sub.chatMessage.presentation.dto.response.ChatMessageDto; import movlit.be.pub_sub.chatMessage.presentation.dto.response.MessageType; import movlit.be.pub_sub.chatRoom.presentation.dto.GroupChatroomMemberResponse; +import movlit.be.pub_sub.chatRoom.presentation.dto.OneononeChatroomCreatePubDto; import movlit.be.pub_sub.chatRoom.presentation.dto.UpdateRoomDto; import movlit.be.pub_sub.chatRoom.presentation.dto.UpdateRoomDto.EventType; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.stereotype.Service; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * 메시지 수신자(Subscriber) 구현 */ @@ -105,7 +107,7 @@ public void updateRoom(String publishMessage) { log.info("RedisMessageSubscriber의 cachedMembers 개수 : {}", cachedMembers.size()); messagingTemplate.convertAndSend("/topic/chat/room/" + groupChatroomId.getValue(), response); - } else if (updateRoomDto.getEventType().equals(EventType.MEMBER_LEAVE)){ + } else if (updateRoomDto.getEventType().equals(EventType.MEMBER_LEAVE)) { // 기존 멤버 나가는 이벤트 처리 // eventMessage와 cachedMembers 함께 전송 Map response = new HashMap<>(); @@ -121,6 +123,24 @@ public void updateRoom(String publishMessage) { } } + public void createOneononeChatroom(String publishMessage) { + try { + OneononeChatroomCreatePubDto oneononeChatroomCreatePubDto = objectMapper.readValue(publishMessage, + OneononeChatroomCreatePubDto.class); + log.info("Received message to 'createOneononeChatroom' : {}", publishMessage); + messagingTemplate.convertAndSend( + "/topic/oneononeChatroom/create/publish/" + oneononeChatroomCreatePubDto.getTopicReceiverId() + .getValue(), + oneononeChatroomCreatePubDto + ); + + } catch (Exception e) { + log.error("Exception in OneononeChatroom publish : {}", e); + } + + + } + public void readMessage(String publishMessage) { try { ChatMessageDto chatMessageDto = objectMapper.readValue(publishMessage, ChatMessageDto.class); diff --git a/be/src/main/java/movlit/be/pub_sub/chatRoom/application/service/GroupChatroomService.java b/be/src/main/java/movlit/be/pub_sub/chatRoom/application/service/GroupChatroomService.java index 647b56b1..a273a1d3 100644 --- a/be/src/main/java/movlit/be/pub_sub/chatRoom/application/service/GroupChatroomService.java +++ b/be/src/main/java/movlit/be/pub_sub/chatRoom/application/service/GroupChatroomService.java @@ -132,11 +132,14 @@ private void publishNewGroupChatroomNoti(String contentId, String roomName, List heartingMemberIds = new ArrayList<>(); // 콘텐츠명 (영화 이름, 책 이름) String contentName = ""; + // 해당 콘텐츠의 상세페이지 url (채팅방 가입 유도) + String url = basicUrl; if (contentType.equals("MV")) { Long movieId = Long.parseLong(pureContentId); contentName = movieReadService.fetchByMovieId(movieId).getTitle(); heartingMemberIds = movieHeartService.fetchHeartingMemberIdsByMovieId(movieId); + url += "/movie/" + pureContentId; } else if (contentType.equals("BK")) { BookId bookId = new BookId(pureContentId); String bookName = bookDetailReadService.fetchByBookId(bookId).getTitle(); @@ -148,13 +151,13 @@ private void publishNewGroupChatroomNoti(String contentId, String roomName, } heartingMemberIds = bookHeartReadService.fetchHeartingMemberIdsByBookId(bookId); + url += "/book/" + pureContentId; } // 멤버들에게 알림 발송 if (!heartingMemberIds.isEmpty()) { for (MemberId heartigMemberId : heartingMemberIds) { log.info(">> 알림발송할 멤버 " + heartigMemberId.getValue()); - String url = basicUrl + "/chatMain/" + createdChatroom.getGroupChatroomId().getValue() + "/group"; NotificationDto notification = new NotificationDto( heartigMemberId.getValue(), NotificationMessage.generateNewGroupChatroomNotiMessage(contentType, contentName, roomName), diff --git a/be/src/main/java/movlit/be/pub_sub/chatRoom/application/service/OneononeChatroomService.java b/be/src/main/java/movlit/be/pub_sub/chatRoom/application/service/OneononeChatroomService.java index f5b2e437..df635963 100644 --- a/be/src/main/java/movlit/be/pub_sub/chatRoom/application/service/OneononeChatroomService.java +++ b/be/src/main/java/movlit/be/pub_sub/chatRoom/application/service/OneononeChatroomService.java @@ -2,8 +2,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import java.time.Duration; -import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import movlit.be.common.exception.FailedDeserializeException; @@ -14,15 +12,21 @@ import movlit.be.common.util.ids.OneononeChatroomId; import movlit.be.member.application.service.MemberReadService; import movlit.be.member.domain.entity.MemberEntity; +import movlit.be.pub_sub.RedisMessagePublisher; import movlit.be.pub_sub.chatRoom.domain.MemberROneononeChatroom; import movlit.be.pub_sub.chatRoom.domain.OneononeChatroom; import movlit.be.pub_sub.chatRoom.domain.repository.OneononeChatroomRepository; +import movlit.be.pub_sub.chatRoom.presentation.dto.OneononeChatroomCreatePubDto; +import movlit.be.pub_sub.chatRoom.presentation.dto.OneononeChatroomCreatePubRequest; import movlit.be.pub_sub.chatRoom.presentation.dto.OneononeChatroomRequest; import movlit.be.pub_sub.chatRoom.presentation.dto.OneononeChatroomResponse; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.Duration; +import java.util.List; + @Service @RequiredArgsConstructor @Slf4j @@ -32,6 +36,7 @@ public class OneononeChatroomService { private final OneononeChatroomRepository oneOnOneChatroomRepository; private final RedisTemplate redisTemplate; private final ObjectMapper objectMapper; + private final RedisMessagePublisher messagePublisher; @Transactional public OneononeChatroomResponse createOneononeChatroom(MemberId memberId, @@ -63,6 +68,20 @@ public OneononeChatroomResponse createOneononeChatroom(MemberId memberId, return senderResponse; } + public void publishOneononeChatroomCreate(MemberId topicSenderId, OneononeChatroomCreatePubRequest request) { + MemberEntity topicSender = memberReadService.findEntityByMemberId(topicSenderId); + OneononeChatroomCreatePubDto oneononeChatroomCreatePubDto = + new OneononeChatroomCreatePubDto( + request.getRoomId(), + request.getTopicReceiverId(), + topicSenderId, + topicSender.getNickname(), + topicSender.getProfileImgUrl(), + request.getChatMessage() + ); + messagePublisher.createOneononeChatroom(oneononeChatroomCreatePubDto); + } + private void validateAlreadyExist(MemberEntity sender, MemberEntity receiver) { if (oneOnOneChatroomRepository.existsOneOnOneChatroomBySenderAndReceiver( sender.getMemberId(), diff --git a/be/src/main/java/movlit/be/pub_sub/chatRoom/presentation/controller/OneononeChatroomController.java b/be/src/main/java/movlit/be/pub_sub/chatRoom/presentation/controller/OneononeChatroomController.java index 0d481d9f..6b63b699 100644 --- a/be/src/main/java/movlit/be/pub_sub/chatRoom/presentation/controller/OneononeChatroomController.java +++ b/be/src/main/java/movlit/be/pub_sub/chatRoom/presentation/controller/OneononeChatroomController.java @@ -1,12 +1,12 @@ package movlit.be.pub_sub.chatRoom.presentation.controller; -import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import movlit.be.auth.application.service.MyMemberDetails; import movlit.be.common.util.ids.MemberId; import movlit.be.pub_sub.chatRoom.application.service.FetchMyOneononeChatroomUseCase; import movlit.be.pub_sub.chatRoom.application.service.OneononeChatroomService; +import movlit.be.pub_sub.chatRoom.presentation.dto.OneononeChatroomCreatePubRequest; import movlit.be.pub_sub.chatRoom.presentation.dto.OneononeChatroomRequest; import movlit.be.pub_sub.chatRoom.presentation.dto.OneononeChatroomResponse; import org.springframework.http.HttpStatus; @@ -17,6 +17,8 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RestController @RequiredArgsConstructor @Slf4j @@ -53,6 +55,16 @@ public ResponseEntity createOneOnOneChatroom( } + // 처음 일대일 채팅방을 만든 후 메시지 보낼 경우 상대방에게 채팅방 발행 + @PostMapping("/api/chat/oneOnOne/create-publish") + public void sendCreateOneononeChatroom( + @AuthenticationPrincipal MyMemberDetails details, + @RequestBody OneononeChatroomCreatePubRequest request) { + log.info("Received OneononeChatroom Create Send : {}", request); + MemberId topicSenderId = details.getMemberId(); + oneononeChatroomService.publishOneononeChatroomCreate(topicSenderId, request); + } + // TODO : 채팅방 나가기 } diff --git a/be/src/main/java/movlit/be/pub_sub/chatRoom/presentation/dto/OneononeChatroomCreatePubDto.java b/be/src/main/java/movlit/be/pub_sub/chatRoom/presentation/dto/OneononeChatroomCreatePubDto.java new file mode 100644 index 00000000..41f5819b --- /dev/null +++ b/be/src/main/java/movlit/be/pub_sub/chatRoom/presentation/dto/OneononeChatroomCreatePubDto.java @@ -0,0 +1,42 @@ +package movlit.be.pub_sub.chatRoom.presentation.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import movlit.be.common.util.ids.MemberId; +import movlit.be.common.util.ids.OneononeChatroomId; +import movlit.be.pub_sub.chatMessage.presentation.dto.response.ChatMessageDto; + +@Getter +@NoArgsConstructor +@ToString +public class OneononeChatroomCreatePubDto { + + private OneononeChatroomId roomId; + private MemberId topicReceiverId; + private MemberId receiverId; // 토픽을 받는 사람입장에서의 채팅 receiver + private String receiverNickname; + private String receiverProfileImgUrl; + private ChatMessageDto recentMessage; + + public OneononeChatroomCreatePubDto(OneononeChatroomId roomId, MemberId topicReceiverId, MemberId receiverId, + String receiverNickname, String receiverProfileImgUrl) { + this.roomId = roomId; + this.topicReceiverId = topicReceiverId; + this.receiverId = receiverId; + this.receiverNickname = receiverNickname; + this.receiverProfileImgUrl = receiverProfileImgUrl; + } + + public OneononeChatroomCreatePubDto(OneononeChatroomId roomId, MemberId topicReceiverId, MemberId receiverId, + String receiverNickname, String receiverProfileImgUrl, + ChatMessageDto recentMessage) { + this.roomId = roomId; + this.topicReceiverId = topicReceiverId; + this.receiverId = receiverId; + this.receiverNickname = receiverNickname; + this.receiverProfileImgUrl = receiverProfileImgUrl; + this.recentMessage = recentMessage; + } + +} diff --git a/be/src/main/java/movlit/be/pub_sub/chatRoom/presentation/dto/OneononeChatroomCreatePubRequest.java b/be/src/main/java/movlit/be/pub_sub/chatRoom/presentation/dto/OneononeChatroomCreatePubRequest.java new file mode 100644 index 00000000..3ad12248 --- /dev/null +++ b/be/src/main/java/movlit/be/pub_sub/chatRoom/presentation/dto/OneononeChatroomCreatePubRequest.java @@ -0,0 +1,26 @@ +package movlit.be.pub_sub.chatRoom.presentation.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import movlit.be.common.util.ids.MemberId; +import movlit.be.common.util.ids.OneononeChatroomId; +import movlit.be.pub_sub.chatMessage.presentation.dto.response.ChatMessageDto; + +@Getter +@NoArgsConstructor +@ToString +public class OneononeChatroomCreatePubRequest { + + private OneononeChatroomId roomId; + private MemberId topicReceiverId; + private ChatMessageDto chatMessage; + + public OneononeChatroomCreatePubRequest(OneononeChatroomId roomId, MemberId topicReceiverId, + ChatMessageDto chatMessage) { + this.roomId = roomId; + this.topicReceiverId = topicReceiverId; + this.chatMessage = chatMessage; + } + +} diff --git a/fe/src/App.css b/fe/src/App.css index d1f41775..dc2605a3 100644 --- a/fe/src/App.css +++ b/fe/src/App.css @@ -3,7 +3,7 @@ justify-content: space-between; align-items: center; padding: 10px 20px; - background-color: #f8f8f8; + background-color: #ffffff; border-bottom: 1px solid #ddd; } @@ -20,7 +20,8 @@ } .nav-left .active { - color: #ff0000; /* 활성화된 링크 색상 */ + /*color: #c1edbb; !* 활성화된 링크 색상 *!*/ + color: #2E8B57; } .nav-right { @@ -29,10 +30,20 @@ } .search-box { - padding: 8px; + padding: 6px 30px 6px 36px; border: 1px solid #ccc; - border-radius: 5px; + border-radius: 2px; margin-right: 20px; + width: 270px; /* 원하는 만큼 조절 */ + background: url('./images/search-icon.png') no-repeat 10px center; + background-color: #f5f5f5; + background-size: 20px 20px; /* 돋보기 아이콘 크기 (원하는 크기로 조절) */ +} + +.search-box:focus { + outline: none; /* 기본 파란색 아웃라인 제거 */ + border: 1px solid #c1edbb; /* 원하는 색상으로 테두리 변경 */ + box-shadow: 0 0 1px #c1edbb; /* 추가로 box-shadow까지 주어 더 자연스럽게 만들 수도 있음 */ } .nav-right a { @@ -47,7 +58,16 @@ .logout-button { padding: 8px 15px; - background-color: #ff0000; + background-color: #c1edbb; + color: #fff; + border: none; + border-radius: 5px; + cursor: pointer; +} + +.logout-button:hover { + padding: 8px 15px; + background-color: #2E8B57; color: #fff; border: none; border-radius: 5px; @@ -61,7 +81,11 @@ border: none; border-radius: 5px; cursor: pointer; - background-color: rgb(255, 51, 102); + background-color: #c1edbb; +} + +.search-button:hover { + background-color: #2E8B57; } .nav-right-logged-in { @@ -110,6 +134,7 @@ top: -3px; right: -3px; background: red; + /*background: #2E8B57;*/ color: white; border-radius: 50%; width: 15px; @@ -137,11 +162,9 @@ .delete-all-btn { - marginBottom: 20px; margin-left: 20px; padding: 10px 20px; - backgroundColor: #FF6B6B; color: white; border: none; borderRadius: 5px; @@ -149,6 +172,11 @@ fontSize: 16px; } +.delete-all-btn:hover { + background-color: #2E8B57; + color: #fff; +} + .notification-item { display: flex; diff --git a/fe/src/App.jsx b/fe/src/App.jsx index f9ffc95f..673ab63c 100644 --- a/fe/src/App.jsx +++ b/fe/src/App.jsx @@ -46,21 +46,6 @@ function App() { setIsSnackbarOpen(false); }; - - const handleLogout = async () => { - try { - await axiosInstance.post('/members/logout'); - localStorage.removeItem('accessToken'); - document.cookie = - 'refreshToken=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; - updateLoginStatus(false); - setProfileImage(null); - navigate('/member/login'); - } catch (error) { - console.error('Logout error:', error); - } - }; - // Enter 키 이벤트 처리 (기존과 동일) const handleKeyDown = (event) => { if (event.key === 'Enter') { @@ -272,14 +257,14 @@ function App() { setInputStr(e.target.value)} onKeyDown={(e) => handleKeyDown(e)} /> - + {/**/} {!isLoggedIn && ( <> )} - - )} @@ -335,9 +316,9 @@ function App() { {/* Material-UI Snackbar 추가 (ToastContainer 대신) */} {snackbarMessage} diff --git a/fe/src/assets/css/CreateGroupChatModal.css b/fe/src/assets/css/CreateGroupChatModal.css index a460386e..f5bede8c 100644 --- a/fe/src/assets/css/CreateGroupChatModal.css +++ b/fe/src/assets/css/CreateGroupChatModal.css @@ -164,8 +164,13 @@ cursor: pointer; } +.modal-cancel:hover { + background-color: #212121; + color: white; +} + .modal-confirm { - background: green; + background: #c1edbb; color: white; padding: 10px 20px; border: none; @@ -173,6 +178,13 @@ cursor: pointer; } +.modal-confirm:hover { + background: #2E8B57; + color: white; + +} + + .modal-search-button:hover { background-color: #0056b3; } diff --git a/fe/src/assets/css/CreateGroupChatNameModal.css b/fe/src/assets/css/CreateGroupChatNameModal.css index 9d6f3567..bac67f18 100644 --- a/fe/src/assets/css/CreateGroupChatNameModal.css +++ b/fe/src/assets/css/CreateGroupChatNameModal.css @@ -101,12 +101,15 @@ background-color: #0056b3; } -.modal-2-footer .cancel-button { - background-color: #f44336; +.cancel-button { + /*background-color: #f44336;*/ + background: #ccc; + color: black; } -.modal-2-footer .cancel-button:hover { - background-color: #d32f2f; +.cancel-button:hover { + background-color: #212121; + color: white; } /* 모달 애니메이션 */ diff --git a/fe/src/components/BookDetailPage.jsx b/fe/src/components/BookDetailPage.jsx index 31927efb..42556efc 100644 --- a/fe/src/components/BookDetailPage.jsx +++ b/fe/src/components/BookDetailPage.jsx @@ -1,5 +1,5 @@ import React, {useEffect, useRef, useState} from 'react'; -import {Link, useParams} from 'react-router-dom'; +import {Link, useParams, useLocation, useNavigate } from 'react-router-dom'; import axiosInstance from '../axiosInstance'; import {FaComment, FaHeart, FaRegHeart, FaRegStar, FaStar, FaStarHalfAlt, FaUserCircle,} from 'react-icons/fa'; import BookCarouselRecommend from "../pages/BookCarouselRecommend.jsx"; @@ -51,6 +51,11 @@ function BookDetailPage() { const [refreshKey, setRefreshKey] = useState(0); // 채팅 리스트 새로고침 키 추가 const [currentMemberId, setCurrentMemberId] = useState(null); // 현재 로그인된 memberId 상태 추가 + // 알림(새 그룹채팅방 생성됨)을 통해 상세페이지 접속 -> 바로 "그룹채팅방 입장" 모달 띄우기 + const location = useLocation(); // 현재 URL의 location 객체 가져오기 + const params = new URLSearchParams(location.search); // 쿼리 파라미터 읽기 + const navigate = useNavigate(); + useEffect(() => { axiosInstance .get(`/books/${bookId}/detail`) @@ -76,9 +81,11 @@ function BookDetailPage() { .catch((error) => console.error('Error fetching book data:', error)); + fetchUserComment(); fetchComments(0); + axiosInstance .get(`/members/id`) .then((response) => { @@ -87,6 +94,15 @@ function BookDetailPage() { .catch((error) => console.error('Error fetching member id:', error)); }, [bookId]); + // 알림(새 그룹채팅방 생성됨)을 통해 상세페이지 접속 -> 바로 "그룹채팅방 입장" 모달 띄우기 + useEffect(() => { + const fromNoti = params.get('fromNoti'); // 'fromNoti' 쿼리 파라미터 값을 가져오기 + + if (fromNoti === 'true' && bookId && bookData && crews) { + handleJoinGroupChatroom(bookId, bookData.bookImgUrl, bookData.title, crews); + } + }, [bookId, params, bookData, crews]); // bookData와 crews를 의존성 배열에 추가 + // Intersection Observer 설정 (코멘트 무한 스크롤) useEffect(() => { if (!isInitialLoad) { @@ -490,6 +506,11 @@ function BookDetailPage() { }; console.log(selectedCard); + + // 모달창 호출 후 fromNoti를 false로 설정 + params.set('fromNoti', 'false'); + navigate({ search: params.toString() }, { replace: true }); + handleOpenGroupChatInfoModal(selectedCard, "book"); } catch (err) { @@ -539,6 +560,8 @@ function BookDetailPage() { setSelectedCategory(null); }; + + return (
- ` +
diff --git a/fe/src/components/MovieDetailPage.jsx b/fe/src/components/MovieDetailPage.jsx index f0bf3ef6..f50554cc 100644 --- a/fe/src/components/MovieDetailPage.jsx +++ b/fe/src/components/MovieDetailPage.jsx @@ -1,5 +1,5 @@ +import {Link, useParams, useLocation, useNavigate} from 'react-router-dom'; import React, {useEffect, useRef, useState, useContext} from 'react'; -import {Link, useParams} from 'react-router-dom'; import axiosInstance from '../axiosInstance'; import {FaComment, FaHeart, FaRegHeart, FaRegStar, FaStar, FaStarHalfAlt, FaUserCircle,} from 'react-icons/fa'; import MovieCarousel from '../pages/MovieCarousel'; @@ -45,6 +45,12 @@ function MovieDetailPage() { const initialVisibleCrews = 14; + // 알림(새 그룹채팅방 생성됨)을 통해 상세페이지 접속 -> 바로 "그룹채팅방 입장" 모달 띄우기 + const location = useLocation(); // 현재 URL의 location 객체 가져오기 + const params = new URLSearchParams(location.search); // 쿼리 파라미터 읽기 + const navigate = useNavigate(); + + // 관련 영화 데이터 가져오기 const { movies: relatedMovies, loading: relatedMoviesLoading, @@ -158,6 +164,16 @@ function MovieDetailPage() { .catch((error) => console.error('Error fetching member id:', error)); }, [movieId]); + // 알림(새 그룹채팅방 생성됨)을 통해 상세페이지 접속 -> 바로 "그룹채팅방 입장" 모달 띄우기 + useEffect(() => { + const fromNoti = params.get('fromNoti'); // 'fromNoti' 쿼리 파라미터 값을 가져오기 + + if (fromNoti === 'true' && movieId && movieData && crews) { + handleJoinGroupChatroom(movieId, movieData.posterUrl, movieData.title, crews); + } + }, [movieId, params, movieData, crews]); // bookData와 crews를 의존성 배열에 추가 + + // Intersection Observer 설정 (코멘트 무한 스크롤) useEffect(() => { if (!isInitialLoad) { const options = { @@ -211,6 +227,7 @@ function MovieDetailPage() { } }; + // 코멘트 불러오기 함수 const fetchComments = (currentPage = 0) => { axiosInstance .get(`/movies/${movieId}/comments?page=${currentPage}`) @@ -222,6 +239,7 @@ function MovieDetailPage() { setTotalComments(fetchedTotalComments); if (currentPage === 0) { + // 각 코멘트 객체에 isLiked와 commentLikeCount, profileImgUrl이 존재하는지 확인하고, 값이 없으면 false, 0, null으로 설정 const updatedComments = response.data.content.map((comment) => ({ ...comment, isLiked: comment.isLiked || false, @@ -233,6 +251,7 @@ function MovieDetailPage() { response.data.content.length > 4 || fetchedTotalComments > 4 ); } else { + // 마찬가지로 isLiked와 commentLikeCount, profileImgUrl 존재 여부 확인 및 기본값 설정 const updatedComments = response.data.content.map((comment) => ({ ...comment, isLiked: comment.isLiked || false, @@ -247,6 +266,7 @@ function MovieDetailPage() { .catch((error) => console.error('Error fetching comments:', error)); }; + // Intersection Observer 콜백 함수 const handleObserver = (entities) => { const target = entities[0]; if (target.isIntersecting && hasMore && !isInitialLoad) { @@ -254,7 +274,9 @@ function MovieDetailPage() { } }; + // 코멘트 별점 클릭 핸들러 const handleRatingClick = (newRating, e) => { + // 클릭 위치에 따라 반 별 또는 온전한 별로 설정 const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; const halfWidth = rect.width / 2; @@ -268,11 +290,13 @@ function MovieDetailPage() { setShowCommentInput(true); }; + // 찜하기/찜해제 처리 const handleWishClick = async () => { try { let updatedHeartCount; if (movieData.isHearted) { + // 찜 해제 (DELETE 요청) await axiosInstance.delete(`/movies/${movieId}/hearts`); updatedHeartCount = movieData.heartCount - 1; setMovieData((prevMovieData) => ({ @@ -282,6 +306,7 @@ function MovieDetailPage() { })); updateSnackbar('찜 목록에서 제거되었습니다.', 'success'); } else { + // 찜하기 (POST 요청) const response = await axiosInstance.post(`/movies/${movieId}/hearts`); updatedHeartCount = response.data.movieHeartCnt; setMovieData((prevMovieData) => ({ @@ -292,6 +317,7 @@ function MovieDetailPage() { updateSnackbar('찜 목록에 추가되었습니다.', 'success'); } + // 찜 상태에 따라 버튼 및 카운트 업데이트 const button = document.getElementById('wishButton'); const heartCountSpan = document.getElementById('heartCount'); @@ -336,6 +362,7 @@ function MovieDetailPage() { try { if (userCommentId) { + // 코멘트 수정 (PUT 요청) await axiosInstance.put( `/movies/comments/${userCommentId}`, requestBody @@ -382,12 +409,14 @@ function MovieDetailPage() { setVisibleCrews(crews.slice(0, initialVisibleCrews)); }; + // 코멘트 더보기 처리 const handleLoadMore = () => { setIsInitialLoad(false); fetchComments(page); setShowLessComments(true); }; + // 코멘트 더보기 취소 처리 const handleShowLessComments = () => { setComments(comments.slice(0, 4)); setShowLessComments(false); @@ -396,6 +425,7 @@ function MovieDetailPage() { setIsInitialLoad(true); }; + // 좋아요/좋아요 취소 처리 const handleLikeClick = async (commentId, isLiked) => { try { if (isLiked) { @@ -471,9 +501,17 @@ function MovieDetailPage() { genreName: genre.name })), }; + // 가입 여부 확인 api 호출 console.log(selectedCard); + + // 모달창 호출 후 fromNoti를 false로 설정 + params.set('fromNoti', 'false'); + navigate({ search: params.toString() }, { replace: true }); + handleOpenGroupChatInfoModal(selectedCard, "movie"); + + } catch (err) { console.error('Error fetching room info:', err); } @@ -614,6 +652,7 @@ function MovieDetailPage() {
{renderStars(movieData.voteAverage)}
+ {/* Circular Progress Bar 추가 */}
)} + {/* 코멘트 입력 및 수정/삭제 버튼 */} {myRating > 0 && showCommentInput && (