Skip to content
Open
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
Expand Up @@ -6,6 +6,10 @@
import com.fredmaina.chatapp.Auth.Repositories.UserRepository;
import com.fredmaina.chatapp.Auth.services.AuthService;
import com.fredmaina.chatapp.Auth.services.JWTService;
import com.fredmaina.chatapp.core.error.BadRequestException;
import com.fredmaina.chatapp.core.error.DataAlreadyExistsException;
import com.fredmaina.chatapp.core.error.ResourceNotFoundException;
import com.fredmaina.chatapp.core.error.UnauthorizedException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -34,7 +38,7 @@ public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest loginRequest
if(authResponse.isSuccess()){
return ResponseEntity.ok(authResponse);
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(authResponse);
throw new UnauthorizedException("Invalid username or password");

}

Expand All @@ -45,9 +49,9 @@ public ResponseEntity<AuthResponse> register(@RequestBody SignUpRequest signUpRe
return ResponseEntity.status(HttpStatus.CREATED).body(authResponse);
}
if ("Username already exists (case-insensitive)".equals(authResponse.getMessage()) || "Email already exists".equals(authResponse.getMessage())) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(authResponse);
throw new DataAlreadyExistsException("Username or email already exists");
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(authResponse); // General bad request for other issues
throw new BadRequestException("Bad request"); // General bad request for other issues
}
@PostMapping("/oauth/google")
public ResponseEntity<?> googleOAuth(@RequestBody GoogleOAuthRequest request) {
Expand All @@ -58,7 +62,7 @@ public ResponseEntity<?> googleOAuth(@RequestBody GoogleOAuthRequest request) {
return ResponseEntity.ok(response);
} else {
log.warn("Google OAuth failed: {}", response.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
throw new UnauthorizedException("Error while contacting Google OAuth");
}
}

Expand All @@ -69,8 +73,7 @@ public ResponseEntity<AuthResponse> me(@RequestHeader("Authorization") String au
User user = userRepository.findByEmail(email).orElse(null);

if (user == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(AuthResponse.builder().success(false).message("User not found for provided token.").build());
throw new ResourceNotFoundException("User not found for provided token.");
}

return ResponseEntity.ok(
Expand All @@ -86,8 +89,7 @@ public ResponseEntity<AuthResponse> setUsername(@RequestBody Map<String,String>
String username = map.get("username");

if (email == null || email.isBlank() || username == null || username.isBlank()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(AuthResponse.builder().success(false).message("Email and username are required.").build());
throw new BadRequestException("Email and username are required.");
}

AuthResponse authResponse = authService.setUsername(email,username);
Expand All @@ -96,9 +98,9 @@ public ResponseEntity<AuthResponse> setUsername(@RequestBody Map<String,String>
}
// Distinguish between user not found and username taken
if ("Username already taken (case-insensitive)".equals(authResponse.getMessage())) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(authResponse);
throw new DataAlreadyExistsException(authResponse.getMessage());
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(authResponse); // Assuming "Invalid email" means user not found
throw new ResourceNotFoundException("User not found"); // Assuming "Invalid email" means user not found

}

Expand All @@ -114,17 +116,11 @@ public ResponseEntity<Map<String, Object>> checkUsername(@PathVariable String us
));
} else {
log.error("username {} not found for some weird reason", username);
return ResponseEntity.ok(Map.of(
"success", true,
"exists", false
));
throw new ResourceNotFoundException("username not found");
}
} catch (Exception e) {
log.error("Error checking username: {}", username, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of(
"success", false,
"message", "Error checking username"
));
throw new RuntimeException("Error checking username");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import com.fredmaina.chatapp.core.DTOs.ChatSessionDto;
import com.fredmaina.chatapp.core.Repositories.ChatMessageRepository;
import com.fredmaina.chatapp.core.Services.ChatService;
import com.fredmaina.chatapp.core.error.ResourceNotFoundException;
import com.fredmaina.chatapp.core.error.UnauthorizedException;
import com.fredmaina.chatapp.core.models.ChatMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -38,21 +40,21 @@ public class ChatController {
@GetMapping("/chats")
public ResponseEntity<?> getUserChats(@RequestHeader(value = "Authorization", required = false) String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("success",false,"message","Missing or invalid Authorization header"));
throw new UnauthorizedException("Missing or invalid Authorization header");
}

String token = authHeader.replace("Bearer ", "");
String email;
try {
email = jwtService.getUsernameFromToken(token);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("success",false,"message","Invalid token"));
throw new UnauthorizedException("Invalid token");
}

Optional<User> userOptional = userRepository.findByEmail(email);

if (userOptional.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("success",false,"message","User not found"));
throw new ResourceNotFoundException("User not found");
}

User user = userOptional.get();
Expand All @@ -77,29 +79,30 @@ public ResponseEntity<?> deleteChatSession(
@PathVariable String anonSessionId) {

if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("success", false, "message", "Missing or invalid Authorization header"));
throw new UnauthorizedException("Missing or invalid Authorization header");
}

String token = authHeader.replace("Bearer ", "");
String email;
try {
email = jwtService.getUsernameFromToken(token);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("success", false, "message", "Invalid token"));
throw new UnauthorizedException("Invalid token");
}

Optional<User> userOptional = userRepository.findByEmail(email);
if (userOptional.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("success", false, "message", "User not found"));
throw new ResourceNotFoundException("User not found");
}
User user = userOptional.get();

try {
chatService.deleteChatSession(user.getId(), anonSessionId);
return ResponseEntity.ok(Map.of("success", true, "message", "Chat session deleted successfully."));
} catch (RuntimeException e) {
// Log the exception e
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("success", false, "message", "Failed to delete chat session: " + e.getMessage()));
final String errMsg = String.format("Error deleting chat session %s",e.getMessage());
log.error(errMsg);
throw new RuntimeException(errMsg);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.fredmaina.chatapp.core.error;

public class BadRequestException extends RuntimeException{
public BadRequestException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.fredmaina.chatapp.core.error;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.time.Instant;

@ControllerAdvice
public class ChatAppExceptionHandler {

@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorResponse> badRequestHandler(final BadRequestException ex) {
return buildResponseEntity(ex.getMessage(), HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<ErrorResponse> unauthorizedHandler(final UnauthorizedException ex) {
return buildResponseEntity(ex.getMessage(), HttpStatus.UNAUTHORIZED);
}

@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> resourceNotFoundHandler(final ResourceNotFoundException ex) {
return buildResponseEntity(ex.getMessage(), HttpStatus.NOT_FOUND);
}

@ExceptionHandler(DataAlreadyExistsException.class)
public ResponseEntity<ErrorResponse> duplicateDataRequestHandler(final DataAlreadyExistsException ex) {
return buildResponseEntity(ex.getMessage(), HttpStatus.CONFLICT);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(final Exception ex) {
return buildResponseEntity(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}

private ResponseEntity<ErrorResponse> buildResponseEntity(final String errorMessage, final HttpStatus httpStatus){
ErrorResponse errorDetails = ErrorResponse.builder()
.timestamp(Instant.now())
.status(httpStatus.value())
.error(httpStatus.getReasonPhrase())
.message(errorMessage)
.build();
return new ResponseEntity<>(errorDetails, httpStatus);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.fredmaina.chatapp.core.error;

public class DataAlreadyExistsException extends RuntimeException{
public DataAlreadyExistsException(final String message) {
super(message);
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/fredmaina/chatapp/core/error/ErrorResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.fredmaina.chatapp.core.error;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.Instant;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ErrorResponse {
private Instant timestamp;
private Integer status;
private String error;
private String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.fredmaina.chatapp.core.error;

public class ResourceNotFoundException extends RuntimeException{
public ResourceNotFoundException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.fredmaina.chatapp.core.error;

public class UnauthorizedException extends RuntimeException{
public UnauthorizedException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.fredmaina.chatapp.core.error;

import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import static org.junit.jupiter.api.Assertions.*;

class ChatAppExceptionHandlerTest {
private final ChatAppExceptionHandler exceptionHandler = new ChatAppExceptionHandler();

@Test
void badRequestHandler() {
// Given
String errorMessage = "Invalid request!";
BadRequestException ex = new BadRequestException(errorMessage);
// When
ResponseEntity<ErrorResponse> response = exceptionHandler.badRequestHandler(ex);
// Then
assertResult(response, HttpStatus.BAD_REQUEST);
}

@Test
void unauthorizedHandler() {
// Given
String errorMessage = "Your are not authorized!";
UnauthorizedException ex = new UnauthorizedException(errorMessage);
// When
ResponseEntity<ErrorResponse> response = exceptionHandler.unauthorizedHandler(ex);
// Then
assertResult(response, HttpStatus.UNAUTHORIZED);
}

@Test
void resourceNotFoundHandler() {
// Given
String errorMessage = "User not found";
ResourceNotFoundException ex = new ResourceNotFoundException(errorMessage);
// When
ResponseEntity<ErrorResponse> response = exceptionHandler.resourceNotFoundHandler(ex);
// Then
assertResult(response, HttpStatus.NOT_FOUND);
}

@Test
void duplicateDataRequestHandler() {
// Given
String errorMessage = "User already exists";
DataAlreadyExistsException ex = new DataAlreadyExistsException(errorMessage);
// When
ResponseEntity<ErrorResponse> response = exceptionHandler.duplicateDataRequestHandler(ex);
// Then
assertResult(response, HttpStatus.CONFLICT);
}

@Test
void handleGlobalException() {
// Given
String errorMessage = "User already exists";
Exception ex = new RuntimeException(errorMessage);
// When
ResponseEntity<ErrorResponse> response = exceptionHandler.handleGlobalException(ex);
// Then
assertResult(response, HttpStatus.INTERNAL_SERVER_ERROR);
}

private void assertResult(ResponseEntity<ErrorResponse> response, HttpStatus status){
assertNotNull(response);
assertEquals(status, response.getStatusCode());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.fredmaina.chatapp.core.error;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.time.Instant;

import static org.junit.jupiter.api.Assertions.*;

class ErrorResponseTest {

private static final String ERRMSG = "Not found";
private static final int STATUS = 404;

@Test
void builder() {
final ErrorResponse result = buildErrorResponse();
Assertions.assertAll(()->assertEquals(STATUS, result.getStatus()),
()->assertEquals(ERRMSG, result.getMessage()),
()->assertEquals(ERRMSG, result.getError()));
}

final ErrorResponse buildErrorResponse(){
return ErrorResponse.builder()
.timestamp(Instant.now())
.status(STATUS)
.error(ERRMSG)
.message(ERRMSG)
.build();
}
}