diff --git a/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/controller/ChatController.java b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/controller/ChatController.java new file mode 100644 index 00000000..f85cfc06 --- /dev/null +++ b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/controller/ChatController.java @@ -0,0 +1,37 @@ +package RunningMachines.R2R.domain.crew.chat.controller; + +import RunningMachines.R2R.domain.crew.chat.dto.ChatMessageCreateRequestDto; +import RunningMachines.R2R.domain.crew.chat.dto.ChatMessageResponseDto; +import RunningMachines.R2R.domain.crew.chat.entity.CrewChatImage; +import RunningMachines.R2R.domain.crew.chat.entity.CrewChatMessage; +import RunningMachines.R2R.domain.crew.chat.service.ChatCommandService; +import RunningMachines.R2R.domain.crew.chat.service.ChatQueryService; +import RunningMachines.R2R.domain.user.entity.User; +import RunningMachines.R2R.domain.user.service.AuthCommandService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Repository; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/crew/{crewId}/chat") +@RequiredArgsConstructor +public class ChatController { + private final ChatCommandService chatCommandService; + private final ChatQueryService chatQueryService; + private final AuthCommandService authCommandService; + + @GetMapping + public ResponseEntity> getChatMessages(@PathVariable Long crewId) { + return ResponseEntity.ok(chatQueryService.getChatMessages(crewId)); + } + + @PostMapping + public ResponseEntity createChatMessage(@PathVariable Long crewId, @RequestBody ChatMessageCreateRequestDto requestDto) { + User currentUser = authCommandService.getCurrentUser(); + ChatMessageResponseDto responseDto = chatCommandService.createChatMessage(crewId, currentUser, requestDto); + return ResponseEntity.ok(responseDto); + } +} diff --git a/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/dto/ChatMessageCreateRequestDto.java b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/dto/ChatMessageCreateRequestDto.java new file mode 100644 index 00000000..b246b0bb --- /dev/null +++ b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/dto/ChatMessageCreateRequestDto.java @@ -0,0 +1,23 @@ +package RunningMachines.R2R.domain.crew.chat.dto; + +import lombok.Builder; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Data +@Builder +public class ChatMessageCreateRequestDto { + private Long crewId; + private String content; + private List images; + + public static ChatMessageCreateRequestDto of(Long crewId, String content, List images) { + return ChatMessageCreateRequestDto.builder() + .crewId(crewId) + .content(content) + .images(images) + .build(); + } +} diff --git a/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/dto/ChatMessageResponseDto.java b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/dto/ChatMessageResponseDto.java new file mode 100644 index 00000000..3f071b92 --- /dev/null +++ b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/dto/ChatMessageResponseDto.java @@ -0,0 +1,16 @@ +package RunningMachines.R2R.domain.crew.chat.dto; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class ChatMessageResponseDto { + private Long crewId; + private String senderNickname; + private String content; + private List imageUrls; + private String createdAt; +} diff --git a/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/entity/CrewChatImage.java b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/entity/CrewChatImage.java new file mode 100644 index 00000000..8e878229 --- /dev/null +++ b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/entity/CrewChatImage.java @@ -0,0 +1,26 @@ +package RunningMachines.R2R.domain.crew.chat.entity; + +import RunningMachines.R2R.global.util.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class CrewChatImage extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String imageUrl; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "chat_message_id") + private CrewChatMessage chatMessage; + + public void setChatMessage(CrewChatMessage chatMessage) { + this.chatMessage = chatMessage; + } +} diff --git a/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/entity/CrewChatMessage.java b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/entity/CrewChatMessage.java new file mode 100644 index 00000000..425f6e8e --- /dev/null +++ b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/entity/CrewChatMessage.java @@ -0,0 +1,42 @@ +package RunningMachines.R2R.domain.crew.chat.entity; + +import RunningMachines.R2R.domain.crew.common.entity.Crew; +import RunningMachines.R2R.domain.user.entity.User; +import RunningMachines.R2R.global.util.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrewChatMessage extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "crew_id", nullable = false) + private Crew crew; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User sender; + + private String content; + + @OneToMany(mappedBy = "chatMessage", cascade = CascadeType.ALL, orphanRemoval = true) + private List chatImages = new ArrayList<>(); + + public void addChatImage(CrewChatImage crewChatImage) { + this.chatImages.add(crewChatImage); + crewChatImage.setChatMessage(this); + } +} diff --git a/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/repository/ChatMessageRepository.java b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/repository/ChatMessageRepository.java new file mode 100644 index 00000000..80af1517 --- /dev/null +++ b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/repository/ChatMessageRepository.java @@ -0,0 +1,13 @@ +package RunningMachines.R2R.domain.crew.chat.repository; + +import RunningMachines.R2R.domain.crew.chat.entity.CrewChatMessage; +import RunningMachines.R2R.domain.crew.common.entity.Crew; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ChatMessageRepository extends JpaRepository { + List findByCrewOrderByCreatedAtAsc(Crew crew); +} diff --git a/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/service/ChatCommandService.java b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/service/ChatCommandService.java new file mode 100644 index 00000000..6494342e --- /dev/null +++ b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/service/ChatCommandService.java @@ -0,0 +1,63 @@ +package RunningMachines.R2R.domain.crew.chat.service; + +import RunningMachines.R2R.domain.crew.chat.dto.ChatMessageCreateRequestDto; +import RunningMachines.R2R.domain.crew.chat.dto.ChatMessageResponseDto; +import RunningMachines.R2R.domain.crew.chat.entity.CrewChatImage; +import RunningMachines.R2R.domain.crew.chat.entity.CrewChatMessage; +import RunningMachines.R2R.domain.crew.chat.repository.ChatMessageRepository; +import RunningMachines.R2R.domain.crew.common.entity.Crew; +import RunningMachines.R2R.domain.crew.common.repository.CrewRepository; +import RunningMachines.R2R.domain.user.entity.User; +import RunningMachines.R2R.global.s3.S3Provider; +import RunningMachines.R2R.global.s3.S3RequestDto; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ChatCommandService { + private final ChatMessageRepository chatMessageRepository; + private final CrewRepository crewRepository; + private final S3Provider s3Provider; + + public ChatMessageResponseDto createChatMessage(Long crewId, User sender, ChatMessageCreateRequestDto requestDto) { + Crew crew = crewRepository.findById(crewId) + .orElseThrow(() -> new IllegalArgumentException("크루를 찾을 수 없습니다.")); + + CrewChatMessage chatMessage = CrewChatMessage.builder() + .crew(crew) + .sender(sender) + .content(requestDto.getContent()) + .build(); + + List images = requestDto.getImages(); + if (images!=null) { + for (MultipartFile image : images) { + String imageUrl = s3Provider.uploadFile(image, S3RequestDto.builder() + .userId(sender.getId()) + .dirName("chat-images") + .build()); + CrewChatImage chatImage = CrewChatImage.builder() + .imageUrl(imageUrl) + .build(); + chatMessage.addChatImage(chatImage); + } + } + chatMessageRepository.save(chatMessage); + + return ChatMessageResponseDto.builder() + .crewId(crew.getId()) + .senderNickname(sender.getNickname()) + .content(chatMessage.getContent()) + .imageUrls(chatMessage.getChatImages() == null ? List.of() :chatMessage.getChatImages().stream() + .map(CrewChatImage::getImageUrl) + .collect(Collectors.toList())) + .createdAt(chatMessage.getCreatedAt().format(DateTimeFormatter.ofPattern("MM-dd HH:mm"))) + .build(); + } +} diff --git a/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/service/ChatQueryService.java b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/service/ChatQueryService.java new file mode 100644 index 00000000..0490b7ec --- /dev/null +++ b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/chat/service/ChatQueryService.java @@ -0,0 +1,37 @@ +package RunningMachines.R2R.domain.crew.chat.service; + +import RunningMachines.R2R.domain.crew.chat.dto.ChatMessageResponseDto; +import RunningMachines.R2R.domain.crew.chat.repository.ChatMessageRepository; +import RunningMachines.R2R.domain.crew.common.repository.CrewRepository; +import RunningMachines.R2R.domain.crew.common.entity.Crew; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ChatQueryService { + private final ChatMessageRepository chatMessageRepository; + private final CrewRepository crewRepository; + + public List getChatMessages(Long crewId) { + Crew crew = crewRepository.findById(crewId) + .orElseThrow(() -> new IllegalArgumentException("크루를 찾을 수 없습니다.")); + + return chatMessageRepository.findByCrewOrderByCreatedAtAsc(crew) + .stream() + .map(crewChatMessage -> ChatMessageResponseDto.builder() + .crewId(crew.getId()) + .senderNickname(crewChatMessage.getSender().getNickname()) + .content(crewChatMessage.getContent()) + .imageUrls(crewChatMessage.getChatImages().stream() + .map(crewChatImage -> crewChatImage.getImageUrl()) + .collect(Collectors.toList())) + .createdAt(crewChatMessage.getCreatedAt().format(DateTimeFormatter.ofPattern("MM-dd HH:mm"))) + .build()) + .collect(Collectors.toList()); + } +} diff --git a/src/backend/src/main/java/RunningMachines/R2R/domain/crew/post/gallery/controller/GalleryPostController.java b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/post/gallery/controller/GalleryPostController.java index e0d0f65b..6e06624c 100644 --- a/src/backend/src/main/java/RunningMachines/R2R/domain/crew/post/gallery/controller/GalleryPostController.java +++ b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/post/gallery/controller/GalleryPostController.java @@ -25,7 +25,6 @@ public class GalleryPostController { private final GalleryPostCommandService galleryPostCommandService; private final GalleryPostQueryService galleryPostQueryService; private final GalleryPostLikeService galleryPostLikeService; - private final CrewPostCommentRepository crewPostCommentRepository; private final CrewPostCommentService crewPostCommentService; @PostMapping @@ -63,4 +62,12 @@ public ResponseEntity> getComments(@PathVariabl List comments = crewPostCommentService.getComments(crewPostId); return ResponseEntity.ok(comments); } + + @GetMapping("/redirectToChat") + public ResponseEntity redirectToChat(@PathVariable Long crewId) { + String chatUrl = "/crew/" + crewId + "/chat"; + return ResponseEntity.status(303) // Redirect 상태 코드 + .header("Location", chatUrl) + .build(); + } } diff --git a/src/backend/src/main/java/RunningMachines/R2R/domain/crew/post/notice/controller/NoticePostController.java b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/post/notice/controller/NoticePostController.java index f9edf9b4..055557f7 100644 --- a/src/backend/src/main/java/RunningMachines/R2R/domain/crew/post/notice/controller/NoticePostController.java +++ b/src/backend/src/main/java/RunningMachines/R2R/domain/crew/post/notice/controller/NoticePostController.java @@ -40,4 +40,12 @@ public ResponseEntity getNoticePostDetail(@PathVariable L public ResponseEntity getNoticePostsByCrew(@PathVariable Long crewId) { return ResponseEntity.ok(noticePostQueryService.getNoticePostsByCrew(crewId)); } + + @GetMapping("/redirectToChat") + public ResponseEntity redirectToChat(@PathVariable Long crewId) { + String chatUrl = "/crew/" + crewId + "/chat"; + return ResponseEntity.status(303) // Redirect 상태 코드 + .header("Location", chatUrl) + .build(); + } }