diff --git a/backend-service/pom.xml b/backend-service/pom.xml
index 693ab77..c4b4f03 100644
--- a/backend-service/pom.xml
+++ b/backend-service/pom.xml
@@ -138,6 +138,14 @@
org.springframework.cloud
spring-cloud-starter-bootstrap
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
org.mockito
mockito-core
diff --git a/backend-service/src/main/java/co/teamsphere/api/DTO/ChatSummaryDTO.java b/backend-service/src/main/java/co/teamsphere/api/DTO/ChatSummaryDTO.java
index 03e9d22..18c4cfa 100644
--- a/backend-service/src/main/java/co/teamsphere/api/DTO/ChatSummaryDTO.java
+++ b/backend-service/src/main/java/co/teamsphere/api/DTO/ChatSummaryDTO.java
@@ -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;
+ }
}
+
diff --git a/backend-service/src/main/java/co/teamsphere/api/DTO/MessageDTO.java b/backend-service/src/main/java/co/teamsphere/api/DTO/MessageDTO.java
index 49c0b3e..a885cfb 100644
--- a/backend-service/src/main/java/co/teamsphere/api/DTO/MessageDTO.java
+++ b/backend-service/src/main/java/co/teamsphere/api/DTO/MessageDTO.java
@@ -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;
diff --git a/backend-service/src/main/java/co/teamsphere/api/controller/AuthController.java b/backend-service/src/main/java/co/teamsphere/api/controller/AuthController.java
index 903ca47..e8d4ed8 100644
--- a/backend-service/src/main/java/co/teamsphere/api/controller/AuthController.java
+++ b/backend-service/src/main/java/co/teamsphere/api/controller/AuthController.java
@@ -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;
@@ -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;
@@ -93,30 +95,33 @@ public ResponseEntity verifyJwtToken() {
),
@ApiResponse(responseCode = "400", description = "Invalid input or user already exists")
})
- @PostMapping(value="/signup", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
- public ResponseEntity 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");
+
} 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");
}
}
+
@Operation(summary = "Login a user", description = "Login with email and password.")
@ApiResponses(value = {
@ApiResponse(
@@ -130,27 +135,29 @@ public ResponseEntity userSignupMethod (
@ApiResponse(responseCode = "401", description = "Invalid credentials")
})
@PostMapping("/login")
- public ResponseEntity 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",
@@ -213,4 +220,18 @@ public ResponseEntity authenticateWithGoogleMethod(
return new ResponseEntity<>(new AuthResponse("Error during Google authentication: " + e.getMessage(), false), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
+ private ResponseEntity 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);
+ }
}
diff --git a/backend-service/src/main/java/co/teamsphere/api/exception/CloudflareException.java b/backend-service/src/main/java/co/teamsphere/api/exception/CloudflareException.java
new file mode 100644
index 0000000..1c4ff5d
--- /dev/null
+++ b/backend-service/src/main/java/co/teamsphere/api/exception/CloudflareException.java
@@ -0,0 +1,9 @@
+package co.teamsphere.api.exception;
+
+import lombok.Data;
+
+@Data
+public class CloudflareException {
+ private int code;
+ private String message;
+}
diff --git a/backend-service/src/main/java/co/teamsphere/api/repository/ChatRepository.java b/backend-service/src/main/java/co/teamsphere/api/repository/ChatRepository.java
index 6ccd77e..2990c32 100644
--- a/backend-service/src/main/java/co/teamsphere/api/repository/ChatRepository.java
+++ b/backend-service/src/main/java/co/teamsphere/api/repository/ChatRepository.java
@@ -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;
@@ -14,10 +15,31 @@
@Repository
public interface ChatRepository extends JpaRepository {
-
- @Query("SELECT c FROM Chat c JOIN c.users u WHERE u.id = :userId")
- Page 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 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);
diff --git a/backend-service/src/main/java/co/teamsphere/api/response/CloudflareApiResponse.java b/backend-service/src/main/java/co/teamsphere/api/response/CloudflareApiResponse.java
index e928ee6..7bf8f12 100644
--- a/backend-service/src/main/java/co/teamsphere/api/response/CloudflareApiResponse.java
+++ b/backend-service/src/main/java/co/teamsphere/api/response/CloudflareApiResponse.java
@@ -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;
@@ -18,7 +19,7 @@ public class CloudflareApiResponse {
private Result result;
private boolean success;
- private List errors;
+ private List errors;
private List messages;
@Getter
diff --git a/backend-service/src/main/java/co/teamsphere/api/response/ErrorResponse.java b/backend-service/src/main/java/co/teamsphere/api/response/ErrorResponse.java
new file mode 100644
index 0000000..53fb802
--- /dev/null
+++ b/backend-service/src/main/java/co/teamsphere/api/response/ErrorResponse.java
@@ -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;
+ }
+}
diff --git a/backend-service/src/main/java/co/teamsphere/api/services/AuthenticationService.java b/backend-service/src/main/java/co/teamsphere/api/services/AuthenticationService.java
index 1f29d88..46764f7 100644
--- a/backend-service/src/main/java/co/teamsphere/api/services/AuthenticationService.java
+++ b/backend-service/src/main/java/co/teamsphere/api/services/AuthenticationService.java
@@ -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;
}
diff --git a/backend-service/src/main/java/co/teamsphere/api/services/impl/AuthenticationServiceImpl.java b/backend-service/src/main/java/co/teamsphere/api/services/impl/AuthenticationServiceImpl.java
index fa5d9d6..8b42d23 100644
--- a/backend-service/src/main/java/co/teamsphere/api/services/impl/AuthenticationServiceImpl.java
+++ b/backend-service/src/main/java/co/teamsphere/api/services/impl/AuthenticationServiceImpl.java
@@ -1,21 +1,7 @@
package co.teamsphere.api.services.impl;
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import java.util.Objects;
-import java.util.regex.Pattern;
-
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.validation.annotation.Validated;
-
import co.teamsphere.api.config.JWTTokenProvider;
+import co.teamsphere.api.exception.CloudflareException;
import co.teamsphere.api.exception.ProfileImageException;
import co.teamsphere.api.exception.UserException;
import co.teamsphere.api.models.User;
@@ -25,9 +11,24 @@
import co.teamsphere.api.response.CloudflareApiResponse;
import co.teamsphere.api.services.AuthenticationService;
import co.teamsphere.api.services.CloudflareApiService;
-
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Objects;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
@Service
@Validated
@@ -57,93 +58,80 @@ public AuthenticationServiceImpl(
@Override
@Transactional
- public AuthResponse signupUser(@Valid SignupRequest request) throws UserException, ProfileImageException {
- try {
- if (isEmailInvalid(request.getEmail())) {
- log.warn("Bad Email={} was passed in", request.getEmail());
- throw new UserException("Valid email was not passed in");
- }
-
- // Check if user with the given email or username already exists
- if (userRepository.findByEmail(request.getEmail()).isPresent()) {
- log.warn("Email={} is already used with another account", request.getEmail());
- throw new UserException("Email is already used with another account");
- }
-
- if (userRepository.findByUsername(request.getUsername()).isPresent()) {
- log.warn("Username={} is already used with another account", request.getUsername());
- throw new UserException("Username is already used with another account");
- }
-
- if (request.getFile().isEmpty() || (!request.getFile().getContentType().equals("image/jpeg") && !request.getFile().getContentType().equals("image/png"))) {
- log.warn("File type not accepted, {}", request.getFile().getContentType());
- throw new ProfileImageException("Profile Picture type is not allowed!");
- }
-
- // Upload profile picture to Cloudflare
- CloudflareApiResponse responseEntity = cloudflareApiService.uploadImage(request.getFile());
- String baseUrl = Objects.requireNonNull(responseEntity.getResult().getVariants().get(0));
- String profileUrl = baseUrl.substring(0, baseUrl.lastIndexOf("/") + 1) + "public";
-
- var currentDateTime = LocalDateTime.now().atOffset(ZoneOffset.UTC);
-
- // Creating a new user
- var newUser = User.builder()
- .email(request.getEmail())
- .username(request.getUsername())
- .password(passwordEncoder.encode(request.getPassword()))
- .profilePicture(profileUrl)
- .createdDate(currentDateTime)
- .lastUpdatedDate(currentDateTime)
- .build();
-
- userRepository.save(newUser);
-
- // auto-login after signup
- Authentication authentication = new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword());
- SecurityContextHolder.getContext().setAuthentication(authentication);
- String token = jwtTokenProvider.generateJwtToken(authentication);
-
- return new AuthResponse(token, true);
+ public AuthResponse signupUser(@Valid SignupRequest request) throws UserException, ProfileImageException, IOException {
+ if (isEmailInvalid(request.getEmail())) {
+ log.warn("Bad Email={} was passed in", request.getEmail());
+ throw new UserException("Valid email was not passed in");
}
- catch (UserException e) {
- // TODO: think about returning a response and not throwing an error in a catch block
- log.error("Error during signup process", e);
- throw e; // Rethrow specific exception to be handled by global exception handler
+
+ // Check if user with the given email or username already exists
+ if (userRepository.findByEmail(request.getEmail()).isPresent()) {
+ log.warn("Email={} is already used with another account", request.getEmail());
+ throw new UserException("Email is already used with another account");
}
- catch (ProfileImageException e){
- log.error("ERROR: {}", e.getMessage());
- throw new ProfileImageException(e.getMessage());
+
+ if (userRepository.findByUsername(request.getUsername()).isPresent()) {
+ log.warn("Username={} is already used with another account", request.getUsername());
+ throw new UserException("Username is already used with another account");
+ }
+
+ if (request.getFile().isEmpty() || (!Objects.equals(request.getFile().getContentType(), "image/jpeg") && !Objects.equals(request.getFile().getContentType(), "image/png"))) {
+ log.warn("File type not accepted, {}", request.getFile().getContentType());
+ throw new ProfileImageException("Profile Picture type is not allowed!");
}
- catch (Exception e) {
- log.error("Unexpected error during signup process", e);
- throw new UserException("Unexpected error during signup process");
+
+ // Upload profile picture to Cloudflare
+ CloudflareApiResponse responseEntity = cloudflareApiService.uploadImage(request.getFile());
+
+ // Check if the Cloudflare API call was unsuccessful
+ if (!responseEntity.isSuccess() || (responseEntity.getErrors() != null && !responseEntity.getErrors().isEmpty())) {
+ String errorMessage = responseEntity.getErrors().stream()
+ .map(CloudflareException::getMessage)
+ .collect(Collectors.joining(", "));
+ throw new ProfileImageException("Cloudflare upload failed: " + errorMessage);
}
+
+
+ String baseUrl = Objects.requireNonNull(responseEntity.getResult().getVariants().get(0));
+ String profileUrl = baseUrl.substring(0, baseUrl.lastIndexOf("/") + 1) + "public";
+
+ var currentDateTime = LocalDateTime.now().atOffset(ZoneOffset.UTC);
+
+ // Creating a new user
+ var newUser = User.builder()
+ .email(request.getEmail())
+ .username(request.getUsername())
+ .password(passwordEncoder.encode(request.getPassword()))
+ .profilePicture(profileUrl)
+ .createdDate(currentDateTime)
+ .lastUpdatedDate(currentDateTime)
+ .build();
+
+ userRepository.save(newUser);
+
+ // auto-login after signup
+ Authentication authentication = new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword());
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ String token = jwtTokenProvider.generateJwtToken(authentication);
+
+ return new AuthResponse(token, true);
}
@Override
@Transactional
public AuthResponse loginUser(String email, String password) throws UserException {
- try {
- if(isEmailInvalid(email)){
- log.warn("Email={} is already used with another account", email);
- throw new UserException("Email is already used with another account");
- }
+ if(isEmailInvalid(email)){
+ log.warn("Email={} is already used with another account", email);
+ throw new UserException("Email is already used with another account");
+ }
- Authentication authentication = authentication(email, password);
+ Authentication authentication = authentication(email, password);
- SecurityContextHolder.getContext().setAuthentication(authentication);
+ SecurityContextHolder.getContext().setAuthentication(authentication);
- String token = jwtTokenProvider.generateJwtToken(authentication);
+ String token = jwtTokenProvider.generateJwtToken(authentication);
- return new AuthResponse(token, true);
- } catch (BadCredentialsException e) {
- log.warn("Authentication failed for user with username: {}", email);
- throw new UserException("Invalid username or password.");
- } catch (Exception e) {
- log.error("Unexpected error during login process", e);
- throw new UserException("Unexpected error during login process.");
- }
+ return new AuthResponse(token, true);
}
public static boolean isEmailInvalid(String email) {
diff --git a/backend-service/src/main/java/co/teamsphere/api/services/impl/ChatServiceImpl.java b/backend-service/src/main/java/co/teamsphere/api/services/impl/ChatServiceImpl.java
index 5e54499..cd8a655 100644
--- a/backend-service/src/main/java/co/teamsphere/api/services/impl/ChatServiceImpl.java
+++ b/backend-service/src/main/java/co/teamsphere/api/services/impl/ChatServiceImpl.java
@@ -1,11 +1,9 @@
package co.teamsphere.api.services.impl;
import co.teamsphere.api.DTO.ChatSummaryDTO;
-import co.teamsphere.api.DTOmapper.ChatDTOMapper;
import co.teamsphere.api.exception.ChatException;
import co.teamsphere.api.exception.UserException;
import co.teamsphere.api.models.Chat;
-import co.teamsphere.api.models.Messages;
import co.teamsphere.api.models.User;
import co.teamsphere.api.repository.ChatRepository;
import co.teamsphere.api.request.GroupChatRequest;
@@ -19,10 +17,10 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
-import java.util.ArrayList;
@Service
@Validated
@@ -33,12 +31,9 @@ public class ChatServiceImpl implements ChatService {
private final ChatRepository chatRepository;
- private final ChatDTOMapper chatDTOMapper;
-
- public ChatServiceImpl(UserService userService, ChatRepository chatRepository, ChatDTOMapper chatDTOMapper) {
+ public ChatServiceImpl(UserService userService, ChatRepository chatRepository) {
this.userService = userService;
this.chatRepository = chatRepository;
- this.chatDTOMapper = chatDTOMapper;
}
@Override
@@ -147,16 +142,12 @@ public Chat createGroup(GroupChatRequest req, UUID reqUserId) throws UserExcepti
}
}
- //TODO: Add builder pattern here
-// chat.builder().chat_name(req.getChat_name())
-//// .chat_image(req.getChat_image())
-//// .is_group(true)
-//// .admins(reqUser
-//// .build();
- chat.setChatName(req.getChat_name());
- chat.setChatImage(req.getChat_image());
- chat.setIsGroup(true);
- chat.getAdmins().add(reqUser);
+ Chat.builder()
+ .chatName(req.getChat_name())
+ .chatImage(req.getChat_image())
+ .isGroup(true)
+ .admins(Collections.singleton(reqUser))
+ .build();
Chat createdChat = chatRepository.save(chat);
@@ -246,46 +237,11 @@ public List getChatSummaries(UUID userId, int page, int size) th
log.info("Getting chat summaries for user with ID: {}", userId);
Pageable pageable = PageRequest.of(page, size);
- Page userChatsPage = chatRepository.findChatsByUserId(userId, pageable);
- List userChats = userChatsPage.getContent();
-
- List chatSummaries = new ArrayList<>();
- for (Chat chat : userChats) {
- String[] chatInfo = { chat.getChatName(), chat.getChatImage() };
-
- if (!chat.getIsGroup()) {
- // Use a wrapper object or array to hold mutable state
- final String[] chatNameImage = { chat.getChatName(), chat.getChatImage() };
-
- chat.getUsers().stream()
- .filter(user -> !user.getId().equals(userId))
- .findFirst()
- .ifPresent(otherUser -> {
- chatNameImage[0] = otherUser.getUsername();
- chatNameImage[1] = otherUser.getProfilePicture();
- });
- chatInfo[0] = chatNameImage[0];
- chatInfo[1] = chatNameImage[1];
- }
- Messages lastMessage = null;
- if (!chat.getMessages().isEmpty()) {
- lastMessage = chat.getMessages().get(chat.getMessages().size() - 1);
- }
-
- ChatSummaryDTO summary = ChatSummaryDTO.builder()
- .id(chat.getId())
- .chatName(chatInfo[0])
- .chatImage(chatInfo[1])
- .createdBy(chat.getCreatedBy().getId())
- .lastMessage(lastMessage != null ? chatDTOMapper.toMessageDto(lastMessage) : null)
- .build();
-
- chatSummaries.add(summary);
- }
+ Page userChatsPage = chatRepository.findChatsByUserId(userId, pageable);
- log.info("Retrieved {} chat summaries for user with ID: {}", chatSummaries.size(), userId);
+ log.info("Retrieved {} chat summaries for user with ID: {}", userChatsPage.getSize(), userId);
- return chatSummaries;
+ return userChatsPage.getContent();
} catch (Exception e) {
log.error("Error getting chat summaries for user with ID: {}", userId, e);
throw new ChatException("Error getting chat summaries for user with ID: " + userId + ". " + e.getMessage());
diff --git a/backend-service/src/main/java/co/teamsphere/api/services/impl/CloudflareApiServiceImpl.java b/backend-service/src/main/java/co/teamsphere/api/services/impl/CloudflareApiServiceImpl.java
index 5665843..5662812 100644
--- a/backend-service/src/main/java/co/teamsphere/api/services/impl/CloudflareApiServiceImpl.java
+++ b/backend-service/src/main/java/co/teamsphere/api/services/impl/CloudflareApiServiceImpl.java
@@ -1,10 +1,11 @@
package co.teamsphere.api.services.impl;
-import java.io.IOException;
-import java.util.List;
-import java.util.Objects;
-import java.util.UUID;
-
+import co.teamsphere.api.exception.CloudflareException;
+import co.teamsphere.api.response.CloudflareApiResponse;
+import co.teamsphere.api.services.CloudflareApiService;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpEntity;
@@ -16,13 +17,13 @@
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
-import co.teamsphere.api.response.CloudflareApiResponse;
-import co.teamsphere.api.services.CloudflareApiService;
-
-import lombok.extern.slf4j.Slf4j;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.UUID;
@Service
@Slf4j
@@ -45,38 +46,54 @@ public CloudflareApiResponse uploadImage(MultipartFile imageFile) throws IOExcep
headers.set("Authorization", String.format("Bearer %s", cloudflareApiKey));
var body = new LinkedMultiValueMap();
+ final String filename = UUID.randomUUID().toString();
body.add("file", new ByteArrayResource(imageFile.getBytes()) {
@Override
public String getFilename() {
- return UUID.randomUUID().toString();
+ return filename;
}
});
body.add("requireSignedURLs", false);
-
HttpEntity> requestEntity = new HttpEntity<>(body, headers);
-
String url = apiUrl.replace("{account_id}", cloudflareAccountID);
-
- CloudflareApiResponse cloudflareApiResponse = new CloudflareApiResponse();
- ResponseEntity response = null;
+ ResponseEntity response;
try {
- log.info("Sending ImageID: {} request to URL: {}", body.get("file"), url);
+ log.info("Sending ImageID: {} request to URL: {}",filename , url);
response = restTemplate.postForEntity(url, requestEntity, CloudflareApiResponse.class);
+
if (response.getStatusCode() == HttpStatus.OK) {
- log.info("{} uploaded successfully to Cloudflare.", body.get("file"));
+ log.info("{} uploaded successfully to Cloudflare.", filename);
return response.getBody();
}
+
log.error("Failed to upload image to Cloudflare. Status code: {}", response.getStatusCode());
- } catch (HttpClientErrorException e){
- assert response != null;
- cloudflareApiResponse.setErrors(Objects.requireNonNull(response.getBody()).getErrors());
- log.error("Cloudflare API error: {}", e.getResponseBodyAsString());
- }
+ return createErrorResponse("Upload failed with status: " + response.getStatusCode());
- return cloudflareApiResponse;
+ } catch (HttpClientErrorException e) {
+ log.error("Cloudflare API client error: {}", e.getResponseBodyAsString());
+
+ try {
+ // Parse the error response into our API response object
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readValue(e.getResponseBodyAsString(), CloudflareApiResponse.class);
+ } catch (JsonProcessingException jsonException) {
+ log.error("Failed to parse Cloudflare error response", jsonException);
+ return createErrorResponse("Authentication failed: " + e.getStatusCode());
+ }
+
+ } catch (RestClientException e) {
+ // Handle other REST client exceptions (network issues, etc.)
+ log.error("REST client error while uploading to Cloudflare", e);
+ return createErrorResponse("Failed to communicate with Cloudflare: " + e.getMessage());
+
+ } catch (Exception e) {
+ // Catch any other unexpected exceptions
+ log.error("Unexpected error while uploading to Cloudflare", e);
+ return createErrorResponse("Internal server error");
+ }
}
@Override
@@ -100,16 +117,24 @@ public CloudflareApiResponse deleteImage(String imageID) {
);
if (response.getStatusCode() == HttpStatus.OK) {
- log.info("ImageID: {} deleted successfully from Cloudflare.");
+ log.info("ImageID: {} deleted successfully from Cloudflare.", imageID);
return response.getBody();
}
log.error("Failed to delete image from Cloudflare. Status code: {}", response.getStatusCode());
} catch (HttpClientErrorException e) {
log.error("Cloudflare API error: {}", e.getResponseBodyAsString());
- cloudflareApiResponse.setErrors(List.of(e.getMessage()));
+ createErrorResponse(e.getMessage());
}
-
return cloudflareApiResponse;
}
+
+ private CloudflareApiResponse createErrorResponse(String message) {
+ CloudflareApiResponse errorResponse = new CloudflareApiResponse();
+ errorResponse.setSuccess(false);
+ CloudflareException error = new CloudflareException();
+ error.setMessage(message);
+ errorResponse.setErrors(Collections.singletonList(error));
+ return errorResponse;
+ }
}
\ No newline at end of file
diff --git a/backend-service/src/main/resources/application-local.yml b/backend-service/src/main/resources/application-local.yml
index 2d84a75..192f711 100644
--- a/backend-service/src/main/resources/application-local.yml
+++ b/backend-service/src/main/resources/application-local.yml
@@ -17,7 +17,7 @@ spring:
jpa:
database: mysql
hibernate:
- ddl-auto: create-drop
+ ddl-auto: update
show-sql: true
rabbitmq:
host: localhost
diff --git a/pom.xml b/pom.xml
index 45815ce..f3ab1e9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -103,6 +103,16 @@
jackson-annotations
2.17.1
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.17.1
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ 2.17.1
+
org.springframework.cloud
spring-cloud-dependencies