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
8 changes: 8 additions & 0 deletions backend-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
package co.teamsphere.api.DTO;

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

import java.time.LocalDateTime;
import java.util.UUID;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChatSummaryDTO {
private UUID id;
private String chatName;
private String chatImage;
private UUID createdBy;
private MessageDTO lastMessage;

public ChatSummaryDTO(UUID id, String chatName, String chatImage, UUID createdBy,
UUID messageId, String content, LocalDateTime timeStamp,
boolean isRead, UUID userId, UUID chatId,
String otherUserName, String otherUserProfile) {
this.id = id;
this.chatName = chatName;
this.chatImage = chatImage;
this.createdBy = createdBy;
this.lastMessage = (messageId != null)
? new MessageDTO(messageId, content, timeStamp, isRead, userId, chatId)
: null;
this.chatName = otherUserName;
this.chatImage = otherUserProfile;
}
}

Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package co.teamsphere.api.DTO;

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

import java.time.LocalDateTime;
import java.util.UUID;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MessageDTO {
private UUID id;
private String content;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import co.teamsphere.api.request.LoginRequest;
import co.teamsphere.api.request.SignupRequest;
import co.teamsphere.api.response.AuthResponse;
import co.teamsphere.api.response.ErrorResponse;
import co.teamsphere.api.services.AuthenticationService;
import co.teamsphere.api.utils.GoogleAuthRequest;
import co.teamsphere.api.utils.GoogleUserInfo;
Expand All @@ -19,6 +20,7 @@
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;

import java.time.Instant;
import java.time.ZoneOffset;
import java.util.UUID;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -93,30 +95,33 @@ public ResponseEntity<String> verifyJwtToken() {
),
@ApiResponse(responseCode = "400", description = "Invalid input or user already exists")
})
@PostMapping(value="/signup", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<AuthResponse> userSignupMethod (
@PostMapping(value = "/signup", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<?> userSignupMethod(
@Schema(description = "User details", implementation = SignupRequest.class)
@Valid @ModelAttribute SignupRequest request) throws UserException, ProfileImageException {
try {
log.info("Processing signup request for user with email: {}, username:{}", request.getEmail(), request.getUsername());
@Valid @ModelAttribute SignupRequest request) {

AuthResponse authResponse = authenticationService.signupUser(request);
log.info("Processing signup request for user with email: {}, username: {}", request.getEmail(), request.getUsername());

try {
AuthResponse authResponse = authenticationService.signupUser(request);
log.info("Signup process completed successfully for user with email: {}", request.getEmail());
return ResponseEntity.status(HttpStatus.CREATED).body(authResponse);

return new ResponseEntity<>(authResponse, HttpStatus.CREATED);
} catch (UserException e) {
log.error("Error during signup process", e);
throw e; // Rethrow specific exception to be handled by global exception handler
} catch (ProfileImageException e){
log.warn("File type not accepted, {}", request.getFile().getContentType());
throw new ProfileImageException("Profile Picture type is not allowed!");
log.error("User-related error during signup process", e);
return buildErrorResponse(HttpStatus.BAD_REQUEST, e.getMessage(), "/signup");

} catch (ProfileImageException e) {
log.warn("File type not accepted: {}", request.getFile().getContentType());
return buildErrorResponse(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "Profile picture type is not allowed!", "/signup");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this part, we have a global exception handler, and they get propagated from there as errors. i would move these or add the error handling to there.


} catch (Exception e) {
log.error("Unexpected error during signup process", e);
throw new UserException("Unexpected error during signup process");
return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected error during signup process", "/signup");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

}
}


@Operation(summary = "Login a user", description = "Login with email and password.")
@ApiResponses(value = {
@ApiResponse(
Expand All @@ -130,27 +135,29 @@ public ResponseEntity<AuthResponse> userSignupMethod (
@ApiResponse(responseCode = "401", description = "Invalid credentials")
})
@PostMapping("/login")
public ResponseEntity<AuthResponse> userLoginMethod(
public ResponseEntity<?> userLoginMethod(
@Schema(description = "Login request body", implementation = LoginRequest.class)
@Valid @RequestBody LoginRequest loginRequest) throws UserException {
try {
log.info("Processing login request for user with username: {}", loginRequest.getEmail());
@Valid @RequestBody LoginRequest loginRequest) {

AuthResponse authResponse = authenticationService.loginUser(loginRequest.getEmail(), loginRequest.getPassword());
log.info("Processing login request for user with username: {}", loginRequest.getEmail());

try {
AuthResponse authResponse = authenticationService.loginUser(loginRequest.getEmail(), loginRequest.getPassword());
log.info("Login successful for user with username: {}", loginRequest.getEmail());
return ResponseEntity.ok(authResponse);

return new ResponseEntity<>(authResponse, HttpStatus.OK);
} catch (BadCredentialsException e) {
log.warn("Authentication failed for user with username: {}", loginRequest.getEmail());
throw new UserException("Invalid username or password.");
return buildErrorResponse(HttpStatus.UNAUTHORIZED, "Invalid username or password.", "/login");

} catch (Exception e) {
log.error("Unexpected error during login process", e);
throw new UserException("Unexpected error during login process.");
return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected error during login process.", "/login");
}
}

@Transactional // move business logic to service layer
//TODO: move business logic to service layer
@Transactional
@Operation(summary = "Authenticate via Google", description = "login/signup via Google OAuth.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
Expand Down Expand Up @@ -213,4 +220,18 @@ public ResponseEntity<AuthResponse> authenticateWithGoogleMethod(
return new ResponseEntity<>(new AuthResponse("Error during Google authentication: " + e.getMessage(), false), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
private ResponseEntity<ErrorResponse> buildErrorResponse(HttpStatus status, String message, String endpoint) {
ErrorResponse errorResponse = new ErrorResponse(
new ErrorResponse.ErrorDetails(
status.value(),
message,
"An error occurred while processing the request.",
endpoint,
"POST",
Instant.now().toString(),
UUID.randomUUID().toString() // Unique request ID for tracking
)
);
return ResponseEntity.status(status).body(errorResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package co.teamsphere.api.exception;

import lombok.Data;

@Data
public class CloudflareException {
private int code;
private String message;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package co.teamsphere.api.repository;

import co.teamsphere.api.DTO.ChatSummaryDTO;
import co.teamsphere.api.models.Chat;
import co.teamsphere.api.models.User;
import org.springframework.data.domain.Page;
Expand All @@ -14,10 +15,31 @@

@Repository
public interface ChatRepository extends JpaRepository<Chat, UUID> {

@Query("SELECT c FROM Chat c JOIN c.users u WHERE u.id = :userId")
Page<Chat> findChatsByUserId(@Param("userId") UUID userId, Pageable pageable);

@Query("""
SELECT new co.teamsphere.api.DTO.ChatSummaryDTO(
c.id,
c.chatName,
c.chatImage,
c.createdBy.id,
m.id,
m.content,
m.timeStamp,
m.isRead,
m.username.id,
m.chat.id,
COALESCE((SELECT u.username FROM c.users u WHERE u.id <> :userId AND c.isGroup = false), ''),
COALESCE((SELECT u.profilePicture FROM c.users u WHERE u.id <> :userId AND c.isGroup = false), '')
)
FROM Chat c
JOIN c.users u
LEFT JOIN c.messages m ON m.timeStamp = (
SELECT MAX(m2.timeStamp) FROM Messages m2 WHERE m2.chat.id = c.id
)
WHERE u.id = :userId
GROUP BY c, m.id, m.content, m.isRead, m.username.id, m.chat.id
ORDER BY MAX(m.timeStamp) DESC
""")
Page<ChatSummaryDTO> findChatsByUserId(@Param("userId") UUID userId, Pageable pageable);

@Query("select c from Chat c Where c.isGroup=false And :user Member of c.users And :reqUser Member of c.users")
Chat findSingleChatByUsersId(@Param("user") User user, @Param("reqUser") User reqUser);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package co.teamsphere.api.response;

import co.teamsphere.api.exception.CloudflareException;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand All @@ -18,7 +19,7 @@ public class CloudflareApiResponse {

private Result result;
private boolean success;
private List<String> errors;
private List<CloudflareException> errors;
private List<String> messages;

@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package co.teamsphere.api.response;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class ErrorResponse {
private ErrorDetails error;

@Getter
@Setter
@AllArgsConstructor
public static class ErrorDetails {
private int status;
private String message;
private String details;
private String endpoint;
private String method;
private String timestamp;
private String requestId;
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package co.teamsphere.api.services;

import org.springframework.stereotype.Service;

import co.teamsphere.api.exception.ProfileImageException;
import co.teamsphere.api.exception.UserException;
import co.teamsphere.api.request.SignupRequest;
import co.teamsphere.api.response.AuthResponse;

import jakarta.validation.Valid;
import org.springframework.stereotype.Service;

import java.io.IOException;

@Service
public interface AuthenticationService {
AuthResponse signupUser(@Valid SignupRequest request) throws UserException, ProfileImageException;
AuthResponse signupUser(@Valid SignupRequest request) throws UserException, ProfileImageException, IOException;

AuthResponse loginUser(String username, String password) throws UserException;
}
Loading