diff --git a/src/main/java/com/sayup/SayUp/controller/AuthController.java b/src/main/java/com/sayup/SayUp/controller/AuthController.java index 583f4ae..2439ab0 100644 --- a/src/main/java/com/sayup/SayUp/controller/AuthController.java +++ b/src/main/java/com/sayup/SayUp/controller/AuthController.java @@ -9,8 +9,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.Map; - @RestController @RequestMapping("/api/auth") @AllArgsConstructor diff --git a/src/main/java/com/sayup/SayUp/controller/ChatRoomController.java b/src/main/java/com/sayup/SayUp/controller/ChatRoomController.java new file mode 100644 index 0000000..f7db16a --- /dev/null +++ b/src/main/java/com/sayup/SayUp/controller/ChatRoomController.java @@ -0,0 +1,22 @@ +package com.sayup.SayUp.controller; + +import com.sayup.SayUp.entity.ChatRoom; +import com.sayup.SayUp.service.ChatRoomService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/chat-rooms") +@RequiredArgsConstructor +public class ChatRoomController { + + private final ChatRoomService chatRoomService; + + @PostMapping("/enter") + public ChatRoom enterRoom( + @RequestParam("currentUserId") Long currentUserId, + @RequestParam("friendUserId") Long friendUserId + ) throws Exception { + return chatRoomService.createOrEnterRoom(currentUserId, friendUserId); + } +} diff --git a/src/main/java/com/sayup/SayUp/controller/FriendshipController.java b/src/main/java/com/sayup/SayUp/controller/FriendshipController.java index 8f0a2ff..9d4a8de 100644 --- a/src/main/java/com/sayup/SayUp/controller/FriendshipController.java +++ b/src/main/java/com/sayup/SayUp/controller/FriendshipController.java @@ -22,7 +22,7 @@ public class FriendshipController { private final FriendshipService friendshipService; @PostMapping("/request/{addresseeId}") - public ResponseEntity sendFriendRequest(@AuthenticationPrincipal UserDetails userDetails, @PathVariable Integer addresseeId) { + public ResponseEntity sendFriendRequest(@AuthenticationPrincipal UserDetails userDetails, @PathVariable Long addresseeId) { if (userDetails == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("User not authenticated"); } diff --git a/src/main/java/com/sayup/SayUp/dto/AuthRequestDTO.java b/src/main/java/com/sayup/SayUp/dto/AuthRequestDTO.java index 4786418..6ee9e7c 100644 --- a/src/main/java/com/sayup/SayUp/dto/AuthRequestDTO.java +++ b/src/main/java/com/sayup/SayUp/dto/AuthRequestDTO.java @@ -1,13 +1,15 @@ package com.sayup.SayUp.dto; -import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; @Data @AllArgsConstructor +@NoArgsConstructor public class AuthRequestDTO { @NotBlank(message = "Email cannot be blank") @Pattern(regexp="^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])+[.][a-zA-Z]{2,3}$", message="이메일 주소 양식을 확인해주세요") diff --git a/src/main/java/com/sayup/SayUp/dto/AuthResponseDTO.java b/src/main/java/com/sayup/SayUp/dto/AuthResponseDTO.java index 17182a0..60b3189 100644 --- a/src/main/java/com/sayup/SayUp/dto/AuthResponseDTO.java +++ b/src/main/java/com/sayup/SayUp/dto/AuthResponseDTO.java @@ -7,4 +7,5 @@ public class AuthResponseDTO { private final String token; private final String email; + private final String id; } diff --git a/src/main/java/com/sayup/SayUp/entity/ChatRoom.java b/src/main/java/com/sayup/SayUp/entity/ChatRoom.java new file mode 100644 index 0000000..d31f31b --- /dev/null +++ b/src/main/java/com/sayup/SayUp/entity/ChatRoom.java @@ -0,0 +1,35 @@ +package com.sayup.SayUp.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.*; + +@Entity +@Table(name = "ChatRoom") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ChatRoom { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // 채팅방에 참여하는 유저들 + @ManyToMany + @JoinTable( + name = "ChatRoom_Users", + joinColumns = @JoinColumn(name = "chatroom_id"), + inverseJoinColumns = @JoinColumn(name = "user_id") + ) + private List participants = new ArrayList<>(); + + @Lob + private String metadata; // TTS 벡터 등 JSON 문자열로 저장 + + private LocalDateTime createdAt = LocalDateTime.now(); +} diff --git a/src/main/java/com/sayup/SayUp/kakao/controller/KakaoCallbackController.java b/src/main/java/com/sayup/SayUp/kakao/controller/KakaoCallbackController.java index 9fe0a06..ee086de 100644 --- a/src/main/java/com/sayup/SayUp/kakao/controller/KakaoCallbackController.java +++ b/src/main/java/com/sayup/SayUp/kakao/controller/KakaoCallbackController.java @@ -1,6 +1,7 @@ package com.sayup.SayUp.kakao.controller; import com.sayup.SayUp.dto.AuthResponseDTO; +import com.sayup.SayUp.entity.User; import com.sayup.SayUp.kakao.dto.KakaoUserInfoResponseDto; import com.sayup.SayUp.kakao.service.KakaoService; import com.sayup.SayUp.security.JwtTokenProvider; @@ -26,16 +27,18 @@ public class KakaoCallbackController { @GetMapping("/callback") public ResponseEntity callback(@RequestParam("code") String code) { String accessToken = kakaoService.getAccessTokenFromKakao(code); + KakaoUserInfoResponseDto userInfo = kakaoService.getUserInfo(accessToken); if (userInfo.getKakaoAccount() == null || userInfo.getKakaoAccount().getEmail() == null) { - return ResponseEntity.badRequest().body(new AuthResponseDTO(null, null)); + return ResponseEntity.badRequest().body(new AuthResponseDTO(null, null, null)); } String email = userInfo.getKakaoAccount().getEmail(); - authService.loadOrCreateUser(email); + User user = authService.loadOrCreateUser(email); String jwt = jwtTokenProvider.createTokenFromEmail(email); - return ResponseEntity.ok(new AuthResponseDTO(jwt, email)); + AuthResponseDTO response = new AuthResponseDTO(jwt, email, String.valueOf(user.getUserId())); + return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/sayup/SayUp/repository/ChatRoomRepository.java b/src/main/java/com/sayup/SayUp/repository/ChatRoomRepository.java new file mode 100644 index 0000000..2beaa09 --- /dev/null +++ b/src/main/java/com/sayup/SayUp/repository/ChatRoomRepository.java @@ -0,0 +1,17 @@ +package com.sayup.SayUp.repository; + +import com.sayup.SayUp.entity.ChatRoom; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +public interface ChatRoomRepository extends JpaRepository { + + // 두 명의 유저가 모두 참여하고 있는 방을 찾기 + @Query("SELECT r FROM ChatRoom r JOIN r.participants p1 JOIN r.participants p2 WHERE p1.userId = :userId1 AND p2.userId = :userId2") + Optional findByUserIds(@Param("userId1") Long userId1, + @Param("userId2") Long userId2 + ); +} diff --git a/src/main/java/com/sayup/SayUp/repository/UserRepository.java b/src/main/java/com/sayup/SayUp/repository/UserRepository.java index e67b4d8..01ccbc7 100644 --- a/src/main/java/com/sayup/SayUp/repository/UserRepository.java +++ b/src/main/java/com/sayup/SayUp/repository/UserRepository.java @@ -10,7 +10,7 @@ import java.util.Optional; @Repository -public interface UserRepository extends JpaRepository { +public interface UserRepository extends JpaRepository { Optional findByEmail(String email); // email 또는 username 중 하나라도 일치하는 유저 조회 diff --git a/src/main/java/com/sayup/SayUp/service/AuthService.java b/src/main/java/com/sayup/SayUp/service/AuthService.java index 46094cd..2cf9677 100644 --- a/src/main/java/com/sayup/SayUp/service/AuthService.java +++ b/src/main/java/com/sayup/SayUp/service/AuthService.java @@ -67,12 +67,17 @@ public void register(AuthRequestDTO authRequestDTO) { } // 카카오 로그인 시 사용자 자동 등록 - public void loadOrCreateUser(String email) { - User user = new User(); - user.setEmail(email); - user.setPassword(passwordEncoder.encode("kakao_user")); // // OAuth 사용자는 비밀번호를 따로 설정하지 않음 + public User loadOrCreateUser(String email) { + return userRepository.findByEmail(email) + .orElseGet(() -> { + User user = new User(); + user.setEmail(email); + user.setPassword(passwordEncoder.encode("kakao_user")); // OAuth 사용자는 임시 비밀번호 - userRepository.save(user); + user.setRole("USER"); // 기본 역할 설정 + + return userRepository.save(user); + }); } /** @@ -112,8 +117,12 @@ public AuthResponseDTO login(AuthRequestDTO authRequestDTO) { String jwt = jwtTokenProvider.createToken(authentication); + User user = userRepository.findByEmail(authRequestDTO.getEmail()) + .orElseThrow(() -> new RuntimeException("User not found")); + logger.info("Login successful for email: {}", authRequestDTO.getEmail()); - return new AuthResponseDTO(jwt, authRequestDTO.getEmail()); + + return new AuthResponseDTO(jwt, authRequestDTO.getEmail(), String.valueOf(user.getUserId())); } diff --git a/src/main/java/com/sayup/SayUp/service/ChatRoomService.java b/src/main/java/com/sayup/SayUp/service/ChatRoomService.java new file mode 100644 index 0000000..363fb43 --- /dev/null +++ b/src/main/java/com/sayup/SayUp/service/ChatRoomService.java @@ -0,0 +1,56 @@ +package com.sayup.SayUp.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sayup.SayUp.entity.ChatRoom; +import com.sayup.SayUp.entity.User; +import com.sayup.SayUp.repository.ChatRoomRepository; +import com.sayup.SayUp.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class ChatRoomService { + + private final ChatRoomRepository chatRoomRepository; + private final UserRepository userRepository; + private final ObjectMapper objectMapper; + + /** + * 두 사용자가 참여하는 채팅방이 이미 존재하면 해당 방을 반환 + * 없으면 새 채팅방을 생성하여 반환 + * + * @param currentUserId 현재 로그인한 유저의 ID + * @param friendUserId 친구로 선택한 유저의 ID + * @return 기존 또는 새로 생성된 채팅방 + * @throws Exception JSON 변환 실패 등 예외 발생 시 + */ + public ChatRoom createOrEnterRoom(Long currentUserId, Long friendUserId) throws Exception { + // 두 사용자 간의 기존 채팅방이 존재하는지 확인 + Optional existingRoom = chatRoomRepository.findByUserIds(currentUserId, friendUserId); + if (existingRoom.isPresent()) return existingRoom.get(); + + // 사용자 정보 조회 (존재하지 않으면 예외 발생) + User currentUser = userRepository.findById(currentUserId).orElseThrow(); + User friendUser = userRepository.findById(friendUserId).orElseThrow(); + + // 친구의 TTS 벡터를 메타데이터에 저장 (key: tts_vector_{friendUserId}) + Map metadataMap = new HashMap<>(); + metadataMap.put("tts_vector_" + friendUserId, friendUser.getTtsVector()); + + String metadataJson = objectMapper.writeValueAsString(metadataMap); + + // 새로운 채팅방 객체 생성 및 저장 + ChatRoom room = ChatRoom.builder() + .participants(Arrays.asList(currentUser, friendUser)) + .metadata(metadataJson) + .build(); + + return chatRoomRepository.save(room); + } +} diff --git a/src/main/java/com/sayup/SayUp/service/FriendshipService.java b/src/main/java/com/sayup/SayUp/service/FriendshipService.java index 9e12fdf..888f524 100644 --- a/src/main/java/com/sayup/SayUp/service/FriendshipService.java +++ b/src/main/java/com/sayup/SayUp/service/FriendshipService.java @@ -29,8 +29,11 @@ public class FriendshipService { @Autowired private UserRepository userRepository; + /** + * @param addresseeId: 친구 요청을 받을 사용자의 userId + */ @Transactional - public void sendFriendRequest(CustomUserDetails requesterDetails, Integer addresseeId) { + public void sendFriendRequest(CustomUserDetails requesterDetails, Long addresseeId) { User requester = requesterDetails.getUser(); // User 객체 직접 접근 User addressee = userRepository.findById(addresseeId) .orElseThrow(() -> new RuntimeException("User not found")); @@ -65,6 +68,17 @@ public void sendFriendRequest(CustomUserDetails requesterDetails, Integer addres friendshipRepository.save(relationship); } + /** + * 전체 흐름 정리 + * 1. A 유저가 B에게 친구 요청 + * 서버에서 FriendRelationship 생성 -> relationshipId 생성 + * 2. B 유저가 getPendingRequests() API 호출 + * B를 향한 PENDING 상태의 요청들을 조회 + * 각 요청마다 relationshipId + 요청자 정보 반환 + * 3. B 유저가 특정 요청을 수락 + * /friend/accept API 호출 시 relationshipId 전달 + * 서버에서 해당 요청의 상태를 ACCEPTED로 변경 + */ @Transactional public void acceptFriendRequest(CustomUserDetails addresseeDetails, Long relationshipId) { User addressee = addresseeDetails.getUser(); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 803c755..638d322 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -41,4 +41,4 @@ springdoc.swagger-ui.operations-sorter=alpha springdoc.packages-to-scan=com.sayup.SayUp.controller kakao.client_id=${KAKAO} -kakao.redirect_uri=http://localhost:8080/callback +kakao.redirect_uri=http://localhost:8080/api/auth/kakao/callback diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html deleted file mode 100644 index bdacabc..0000000 --- a/src/main/resources/templates/login.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - KakaoLogin - - -
-

카카오 로그인

- - - -
- - \ No newline at end of file