Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,84 +1,77 @@
package com.mentora.backend.chat.controller;

//import com.mentora.backend.auth.service.AuthService;
import com.mentora.backend.chat.dto.ChatListDto;
import com.mentora.backend.chat.dto.ChatRenameDto;
import com.mentora.backend.chat.dto.ChatRenameRequest;
import com.mentora.backend.chat.dto.ChatResponseDto;
import com.mentora.backend.chat.dto.UserMessageDto;
import com.mentora.backend.chat.service.ChatListService;
import com.mentora.backend.chat.service.ChatService;
import org.springframework.http.MediaType;
import com.mentora.backend.security.CustomOAuth2UserService;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;

@RestController
@RequestMapping("/api/chat")
public class ChatController {

private final ChatService chatService;
// private final AuthService authService;
private final CustomOAuth2UserService oAuth2UserService;
private final ChatListService chatListService;

// public ChatController(ChatService chatService, AuthService authService, ChatListService chatListService, AgentGateway agentGateway) {
//
// this.chatService = chatService;
// this.authService = authService;
// this.chatListService = chatListService;
// }
public ChatController(ChatService chatService, CustomOAuth2UserService oAuth2UserService, ChatListService chatListService) {
this.chatService = chatService;
this.chatListService = chatListService;
this.oAuth2UserService = oAuth2UserService;
}

// @GetMapping("/chats_history")
// public ResponseEntity<Map<String, List<ChatListDto>>> getChatsHistory() {
// String currentUserId = authService.currentUserId();
// Map<String, List<ChatListDto>> chatListMap = chatListService.getChatList(currentUserId);
// return ResponseEntity.ok(chatListMap);
//
// }
@GetMapping("/chats_history")
public ResponseEntity<Map<String, List<ChatListDto>>> getChatsHistory() {
String currentUserId = oAuth2UserService.currentUserId();
Map<String, List<ChatListDto>> chatListMap = chatListService.getChatList(currentUserId);
return ResponseEntity.ok(chatListMap);
}

// @GetMapping("/search")
// public ResponseEntity<List<ChatListDto>> searchChats(@RequestParam(required = false) String title) {
// String currentUserId = authService.currentUserId();
// List<ChatListDto> searchResults = chatService.searchChatsByTitle(currentUserId, title);
// return ResponseEntity.ok(searchResults);
// }
@GetMapping("/search")
public ResponseEntity<List<ChatListDto>> searchChats(@RequestParam(required = false) String title) {
String currentUserId = oAuth2UserService.currentUserId();
List<ChatListDto> searchResults = chatService.searchChatsByTitle(currentUserId, title);
return ResponseEntity.ok(searchResults);
}

@DeleteMapping("/delete/{chatId}")
public ResponseEntity<Void> deleteChatId(@PathVariable UUID chatId) {
try {
chatService.DeleteChatById(chatId);
return ResponseEntity.noContent().build();
} catch (NoSuchElementException e) {
return ResponseEntity.notFound().build();
}
public ResponseEntity<Void> deleteChatId(@PathVariable @NotNull UUID chatId) {
String currentUserId = oAuth2UserService.currentUserId();
chatService.deleteChatById(currentUserId, chatId);
return ResponseEntity.noContent().build();
}

@PutMapping("/rename/{chatId}")
public ResponseEntity<ChatRenameDto> renameChat(@PathVariable UUID chatId, @RequestParam String newTitle) {
ChatRenameDto responseDto = chatService.ChatRenameById(chatId, newTitle);
public ResponseEntity<ChatRenameDto> renameChat(
@PathVariable @NotNull UUID chatId,
@Valid @RequestBody ChatRenameRequest request
) {
String currentUserId = oAuth2UserService.currentUserId();
ChatRenameDto responseDto = chatService.chatRenameById(currentUserId, chatId, request.getNewTitle());
return ResponseEntity.ok(responseDto);
}

@GetMapping("/history/{chatId}")
public ResponseEntity<ChatResponseDto> getChatHistory(@PathVariable @NotNull UUID chatId) {
String currentUserId = oAuth2UserService.currentUserId();
ChatResponseDto chatHistory = chatService.getChatHistoryById(currentUserId, chatId);
return ResponseEntity.ok(chatHistory);
}

// @GetMapping("/history/{chatId}")
// public ResponseEntity<ChatResponseDto> getChatHistory(@PathVariable UUID chatId) {
// String currentUserId = authService.currentUserId();
// ChatResponseDto chatHistory = chatService.getChatHistoryById(currentUserId, chatId);
// return ResponseEntity.ok(chatHistory);
// }

// @DeleteMapping("/deleteAllChats/")
// public ResponseEntity<String> deleteAllChats() {
// String currentUserId = authService.currentUserId();
// chatService.deleteAllChatsByUserId(currentUserId);
// return ResponseEntity.ok("All Chats Deleted Successfully");
// }



}
@DeleteMapping("/deleteAllChats/")
public ResponseEntity<String> deleteAllChats() {
String currentUserId = oAuth2UserService.currentUserId();
chatService.deleteAllChatsByUserId(currentUserId);
return ResponseEntity.ok("All Chats Deleted Successfully");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.mentora.backend.chat.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* DTO for chat rename requests with validation.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatRenameRequest {

public static final String TITLE_PATTERN = "^[a-zA-Z0-9\\s\\-_.,!?()]+$";

@NotBlank(message = "Title cannot be blank")
@Size(min = 1, max = 255, message = "Title must be between 1 and 255 characters")
@Pattern(
regexp = ChatRenameRequest.TITLE_PATTERN,
message = "Title contains invalid characters. Only letters, numbers, spaces, and basic punctuation are allowed"
)
private String newTitle;
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
package com.mentora.backend.chat.service;


import com.mentora.backend.chat.dto.ChatRenameDto;
import com.mentora.backend.chat.dto.ChatResponseDto;
import com.mentora.backend.chat.dto.UserMessageDto;
import com.mentora.backend.chat.dto.ChatListDto;
import com.mentora.backend.chat.model.Chat;
import com.mentora.backend.chat.model.Message;
import com.mentora.backend.chat.repository.ChatRepository;
import com.mentora.backend.enums.MessageSender;
import com.mentora.backend.chat.dto.ChatListDto;
import com.mentora.backend.error.ChatNotFoundException;
import com.mentora.backend.error.UnauthorizedAccessException;
import jakarta.transaction.Transactional;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import reactor.core.publisher.Flux;

import java.util.*;
import java.util.stream.Collectors;
Expand All @@ -36,9 +32,25 @@ private Chat createNewChat(String userId, String text) {
}

private Chat findExistingChat(UUID chatId) {
return chatRepository.findById(chatId).orElseThrow(() -> new NoSuchElementException("Chat not found."));
return chatRepository.findById(chatId)
.orElseThrow(() -> new ChatNotFoundException(chatId));
}

/**
* Validates that the current user owns the chat.
* Throws UnauthorizedAccessException if the user doesn't own the chat.
*
* @param chat the chat to validate
* @param userId the current user's ID
* @throws UnauthorizedAccessException if user doesn't own the chat
*/
private void validateChatOwnership(Chat chat, String userId) {
if (!chat.getUserId().equals(userId)) {
throw new UnauthorizedAccessException(
"You don't have permission to access this chat"
);
}
}

private Message createUserMessage(String messageText, String modelName) {
Message message = new Message();
Expand All @@ -64,35 +76,68 @@ private String generateTitleFromMessage(String messageText) {
return String.join(" ", Arrays.copyOfRange(words, 0, Math.min(words.length, 3)));
}

/**
* Deletes a chat by ID with authorization check.
*
* @param userId the current user's ID
* @param chatId the ID of the chat to delete
* @throws ChatNotFoundException if chat not found
* @throws UnauthorizedAccessException if user doesn't own the chat
*/
@Transactional
public void DeleteChatById(UUID id) {
if (chatRepository.existsById(id)) {
chatRepository.deleteById(id);
} else {
throw new NoSuchElementException("Chat not found.");
}
public void deleteChatById(String userId, UUID chatId) {
Chat chat = chatRepository.findById(chatId)
.orElseThrow(() -> new ChatNotFoundException(chatId));

validateChatOwnership(chat, userId);

chatRepository.delete(chat);
}

/**
* Deletes all chats belonging to a user.
*
* @param userId the user's ID
*/
@Transactional
public void deleteAllChatsByUserId(String userId) {
chatRepository.deleteAllByUserId(userId);
}

/**
* Renames a chat with authorization check and validation.
*
* @param userId the current user's ID
* @param chatId the ID of the chat to rename
* @param newTitle the new title (already validated by controller)
* @return ChatRenameDto containing the updated chat info
* @throws ChatNotFoundException if chat not found
* @throws UnauthorizedAccessException if user doesn't own the chat
*/
@Transactional
public ChatRenameDto ChatRenameById(UUID id, String title) {
if (title == null || title.trim().isEmpty()) {
throw new IllegalArgumentException("Title cannot be empty.");
}
Chat chat = chatRepository.findById(id).orElseThrow(() -> new NoSuchElementException("Chat not found."));
public ChatRenameDto chatRenameById(String userId, UUID chatId, String newTitle) {
Chat chat = chatRepository.findById(chatId)
.orElseThrow(() -> new ChatNotFoundException(chatId));

validateChatOwnership(chat, userId);

chat.setTitle(title.trim());
chat.setTitle(newTitle.trim());
chatRepository.save(chat);

return ChatRenameDto.builder().chatId(chat.getId()).newTitle(chat.getTitle()).build();
return ChatRenameDto.builder()
.chatId(chat.getId())
.newTitle(chat.getTitle())
.build();
}

/**
* Searches chats by title for a specific user.
*
* @param userId the user's ID
* @param title the search query (optional)
* @return list of matching chats
*/
public List<ChatListDto> searchChatsByTitle(String userId, String title) {

List<Chat> chats;

if (title == null || title.trim().isEmpty()) {
Expand All @@ -104,19 +149,32 @@ public List<ChatListDto> searchChatsByTitle(String userId, String title) {
chats = chatRepository.findByUserIdAndTitleContainingIgnoreCaseOrderByUpdatedAtDesc(userId, title);
}

return chats.stream().map(chat -> ChatListDto.builder().chatId(chat.getId()).title(chat.getTitle()).build()).collect(Collectors.toList());
return chats.stream()
.map(chat -> ChatListDto.builder()
.chatId(chat.getId())
.title(chat.getTitle())
.build())
.collect(Collectors.toList());
}


/**
* Gets chat history with authorization check.
*
* @param userId the current user's ID
* @param chatId the chat ID
* @return ChatResponseDto containing chat history
* @throws ChatNotFoundException if chat not found
* @throws UnauthorizedAccessException if user doesn't own the chat
*/
public ChatResponseDto getChatHistoryById(String userId, UUID chatId) {
Chat chat = chatRepository.findById(chatId).orElseThrow(() -> new RuntimeException("Chat not found"));
Chat chat = chatRepository.findById(chatId)
.orElseThrow(() -> new ChatNotFoundException(chatId));

validateChatOwnership(chat, userId);

if (!chat.getUserId().equals(userId)) {
throw new RuntimeException("Unauthorized access to chat");
}

return ChatResponseDto.builder().chatId(chat.getId()).newMessages(chat.getMessages()).build();
return ChatResponseDto.builder()
.chatId(chat.getId())
.newMessages(chat.getMessages())
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.mentora.backend.error;

import java.util.UUID;

/**
* Exception thrown when a chat is not found.
* This is used for 404 Not Found responses.
*/
public class ChatNotFoundException extends RuntimeException {

public ChatNotFoundException(UUID chatId) {
super("Chat not found: " + chatId);
}

public ChatNotFoundException(String message) {
super(message);
}

public ChatNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
Loading