Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • xkollar3/online-banking-service
1 result
Show changes
Showing
with 180 additions and 57 deletions
package cz.muni.fi.obs.api;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
@Builder
public record ErrorResponse(
@Schema(description = "Message describing the error")
String message
) {
}
......@@ -8,7 +8,7 @@ import lombok.Builder;
public record NotFoundResponse(
@Schema(description = "Message describing the error",
example = "User with id: d333c127-470b-4680-8c7c-70988998b329 not found")
example = "User with id d333c127-470b-4680-8c7c-70988998b329 not found")
String message
) {
}
......@@ -21,7 +21,7 @@ public record UserCreateDto(
@Schema(description = "Last name of the user", example = "Doe")
@NotBlank(message = "Last name is required") String lastName,
@Schema(description = "Phone number of the user", example = "+420 123 456 789")
@Schema(description = "Phone number of the user", example = "+420123456789")
@NotBlank(message = "Phone number is required") String phoneNumber,
@Schema(description = "Email of the user", example = "john.doe@example.com")
......
......@@ -3,7 +3,6 @@ package cz.muni.fi.obs.api;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import org.springframework.data.domain.Pageable;
import java.time.LocalDate;
import java.util.Optional;
......@@ -18,7 +17,7 @@ public record UserSearchParamsDto(
@Schema(description = "Last name of the user", example = "Doe")
Optional<String> lastName,
@Schema(description = "Phone number of the user", example = "+420 123 456 789")
@Schema(description = "Phone number of the user", example = "+420123456789")
Optional<String> phoneNumber,
@Schema(description = "Email of the user", example = "john.doe@example.com")
......@@ -31,11 +30,8 @@ public record UserSearchParamsDto(
@Schema(description = "Birth number of the user", example = "010704/4267")
Optional<String> birthNumber,
@Schema(description = "Activity status of the user account", example = "true")
Optional<Boolean> active,
@Schema(description = "Pageable object for pagination")
Pageable pageable
@Schema(description = "Activity status of the user account", defaultValue = "true")
Optional<Boolean> active
) {
}
package cz.muni.fi.obs.api;
import lombok.Builder;
import org.springframework.data.domain.Pageable;
import java.time.LocalDate;
import java.util.Optional;
@Builder
public record UserSearchParamsPaginatedDto(
Optional<String> firstName,
Optional<String> lastName,
Optional<String> phoneNumber,
Optional<String> email,
Optional<LocalDate> birthDate,
Optional<String> birthNumber,
Optional<Boolean> active,
Pageable pageable
) {
public UserSearchParamsPaginatedDto(
UserSearchParamsDto userSearchParamsDto,
Pageable pageable
) {
this(
userSearchParamsDto.firstName(),
userSearchParamsDto.lastName(),
userSearchParamsDto.phoneNumber(),
userSearchParamsDto.email(),
userSearchParamsDto.birthDate(),
userSearchParamsDto.birthNumber(),
userSearchParamsDto.active(),
pageable
);
}
}
......@@ -10,11 +10,15 @@ import java.util.Optional;
@SomeOptionalPresent
public record UserUpdateDto(
@Schema(description = "First name of the user", example = "John") Optional<String> firstName,
@Schema(description = "First name of the user", example = "John")
Optional<String> firstName,
@Schema(description = "Last name of the user", example = "Doe") Optional<String> lastName,
@Schema(description = "Last name of the user", example = "Doe")
Optional<String> lastName,
@Schema(description = "Phone number of the user", example = "+420 123 456 789") Optional<String> phoneNumber,
@Schema(description = "Phone number of the user", example = "+420702336584")
Optional<String> phoneNumber,
@Schema(description = "Email of the user", example = "john.doe@example.com") Optional<String> email) {
@Schema(description = "Email of the user", example = "someEmail@new.com")
Optional<String> email) {
}
package cz.muni.fi.obs.config;
import cz.muni.fi.obs.http.TransactionServiceClient;
import cz.muni.fi.obs.http.TransactionServiceErrorDecoder;
import org.springframework.context.annotation.Bean;
public class FeignClientConfiguration {
......@@ -11,5 +12,11 @@ public class FeignClientConfiguration {
public TransactionServiceClient fallbackFactory() {
return new TransactionServiceClient.Fallback();
}
@Bean
public TransactionServiceErrorDecoder transactionServiceErrorDecoder() {
return new TransactionServiceErrorDecoder();
}
}
}
......@@ -18,6 +18,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
......@@ -90,7 +91,7 @@ public class UserController {
@GetMapping("/{userId}")
@CrossOrigin(origins = "*")
public ResponseEntity<UserDto> getUser(
@Parameter(description = "ID of the user to be retrieved")
@Parameter(description = "ID of the user to be retrieved", example = "4121add0-f5d7-4128-9c8f-e81fa93237c5")
@PathVariable("userId") UUID userId
) {
log.info("Getting user with id: " + userId);
......@@ -110,9 +111,10 @@ public class UserController {
)
@GetMapping("")
@CrossOrigin(origins = "*")
public Page<UserDto> getUsers(@ParameterObject UserSearchParamsDto searchParams) {
// Retrieve users based on search parameters
return userManagementFacade.findUsers(searchParams);
public Page<UserDto> getUsers(@ParameterObject UserSearchParamsDto searchParams,
@ParameterObject Pageable pageable) {
UserSearchParamsPaginatedDto searchParamsPaginated = new UserSearchParamsPaginatedDto(searchParams, pageable);
return userManagementFacade.findUsers(searchParamsPaginated);
}
@Operation(
......@@ -128,7 +130,7 @@ public class UserController {
@PutMapping("/{userId}")
@CrossOrigin(origins = "*")
public ResponseEntity<UserDto> updateUser(
@Parameter(description = "ID of the user to be updated")
@Parameter(description = "ID of the user to be updated", example = "4121add0-f5d7-4128-9c8f-e81fa93237c5")
@PathVariable("userId") UUID userId,
@Valid @RequestBody UserUpdateDto userUpdateDto) {
log.info("Updating user with id: " + userId);
......@@ -147,6 +149,8 @@ public class UserController {
@PostMapping("/{userId}/deactivate")
@CrossOrigin(origins = "*")
public ResponseEntity<UserDto> deactivateUser(
@Parameter(description = "ID of the user to be deactivated",
example = "4121add0-f5d7-4128-9c8f-e81fa93237c5")
@PathVariable("userId") UUID userId) {
log.info("Deactivating user with id: " + userId);
UserDto user = userManagementFacade.deactivateUser(userId);
......@@ -167,7 +171,9 @@ public class UserController {
)
@PostMapping("/{userId}/activate")
@CrossOrigin(origins = "*")
public ResponseEntity<UserDto> activateUser(@PathVariable("userId") UUID userId) {
public ResponseEntity<UserDto> activateUser(
@Parameter(description = "ID of the user to be activated", example = "4121add0-f5d7-4128-9c8f-e81fa93237c5")
@PathVariable("userId") UUID userId) {
log.info("Activating user with id: " + userId);
UserDto user = userManagementFacade.activateUser(userId);
if (user == null) {
......@@ -190,7 +196,8 @@ public class UserController {
@PostMapping("/{userId}/accounts/create")
@CrossOrigin(origins = "*")
public ResponseEntity<AccountDto> createUserAccount(
@Parameter(description = "ID of the user for whom the account will be created")
@Parameter(description = "ID of the user for that account will be created",
example = "4121add0-f5d7-4128-9c8f-e81fa93237c5")
@PathVariable("userId") UUID userId,
@Valid @RequestBody AccountCreateDto accountCreateDto
) {
......@@ -214,8 +221,8 @@ public class UserController {
@GetMapping("/{userId}/accounts")
@CrossOrigin(origins = "*")
public ResponseEntity<List<AccountDto>> getUserAccounts(
@Parameter(description = "ID of the user whose accounts are to be retrieved")
@PathVariable("userId") UUID userId
@Parameter(description = "ID of the user whose accounts are to be retrieved",
example = "4121add0-f5d7-4128-9c8f-e81fa93237c5") @PathVariable("userId") UUID userId
) {
log.info("Getting user accounts for user with id: " + userId);
List<AccountDto> accounts = userManagementFacade.getUserAccounts(userId);
......
package cz.muni.fi.obs.controller;
import cz.muni.fi.obs.api.ErrorResponse;
import cz.muni.fi.obs.api.NotFoundResponse;
import cz.muni.fi.obs.api.ValidationErrors;
import cz.muni.fi.obs.api.ValidationFailedResponse;
import cz.muni.fi.obs.exceptions.ClientConnectionException;
import cz.muni.fi.obs.exceptions.ExternalServiceException;
import cz.muni.fi.obs.exceptions.UserNotFoundException;
import feign.FeignException;
import org.postgresql.util.PSQLException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
......@@ -103,11 +105,17 @@ public class UserControllerAdvice {
}
}
@ExceptionHandler(ClientConnectionException.class)
public ResponseEntity<String> handleClientConnectionExceptions(ClientConnectionException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
@ExceptionHandler(ExternalServiceException.class)
public ResponseEntity<ErrorResponse> handleClientConnectionExceptions(ExternalServiceException ex) {
return new ResponseEntity<>(new ErrorResponse(ex.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(FeignException.BadRequest.class)
public ResponseEntity<String> handleBadRequestExceptions(FeignException.BadRequest ex) {
return new ResponseEntity<>(ex.contentUTF8(), HttpStatus.BAD_REQUEST);
}
private String extractFieldName(String message) {
String fieldName = null;
if (message.contains("Key (")) {
......
package cz.muni.fi.obs.data.dbo;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import lombok.EqualsAndHashCode;
......@@ -17,6 +15,5 @@ import java.util.UUID;
public abstract class Dbo {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
private UUID id = UUID.randomUUID();
}
......@@ -10,7 +10,6 @@ import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.Optional;
import java.util.UUID;
@Repository
......@@ -24,17 +23,17 @@ public interface UserRepository extends JpaRepository<User, UUID> {
"(:lastName IS NULL OR u.lastName = :lastName) AND " +
"(:phoneNumber IS NULL OR u.phoneNumber = :phoneNumber) AND " +
"(:email IS NULL OR u.email = :email) AND " +
"(cast(:birthDate as date) IS NULL OR u.birthDate = cast(:birthDate as date)) AND " +
"(cast(:birthDate as date) IS NULL OR u.birthDate = :birthDate) AND " +
"(:birthNumber IS NULL OR u.birthNumber = :birthNumber) AND " +
"(:active IS NULL OR u.active = :active)")
Page<User> findBySearchParams(
@Param("firstName") Optional<String> firstName,
@Param("lastName") Optional<String> lastName,
@Param("phoneNumber") Optional<String> phoneNumber,
@Param("email") Optional<String> email,
@Param("birthDate") Optional<LocalDate> birthDate,
@Param("birthNumber") Optional<String> birthNumber,
@Param("active") Optional<Boolean> active,
@Param("firstName") String firstName,
@Param("lastName") String lastName,
@Param("phoneNumber") String phoneNumber,
@Param("email") String email,
@Param("birthDate") LocalDate birthDate,
@Param("birthNumber") String birthNumber,
@Param("active") Boolean active,
Pageable pageable
);
}
package cz.muni.fi.obs.exceptions;
public class ExternalServiceException extends RuntimeException {
public ExternalServiceException(String message) {
super(message);
}
}
package cz.muni.fi.obs.exceptions;
public class ClientConnectionException extends RuntimeException {
public class ResourceNotFoundException extends RuntimeException {
public ClientConnectionException(String message) {
public ResourceNotFoundException(String message) {
super(message);
}
}
......@@ -2,7 +2,7 @@ package cz.muni.fi.obs.exceptions;
import java.util.UUID;
public class UserNotFoundException extends RuntimeException {
public class UserNotFoundException extends ResourceNotFoundException {
public UserNotFoundException(UUID userId) {
super("User with id " + userId + " not found");
......
......@@ -50,7 +50,7 @@ public class UserManagementFacade {
return UserDto.fromUser(user);
}
public Page<UserDto> findUsers(UserSearchParamsDto searchParams) {
public Page<UserDto> findUsers(UserSearchParamsPaginatedDto searchParams) {
Page<User> users = userService.findUsers(searchParams);
return users.map(UserDto::fromUser);
}
......
package cz.muni.fi.obs.http;
import cz.muni.fi.obs.config.FeignClientConfiguration;
import cz.muni.fi.obs.exceptions.ClientConnectionException;
import cz.muni.fi.obs.exceptions.ExternalServiceException;
import cz.muni.fi.obs.http.api.TSAccount;
import cz.muni.fi.obs.http.api.TSAccountCreate;
import lombok.extern.slf4j.Slf4j;
......@@ -33,13 +33,13 @@ public interface TransactionServiceClient {
@Override
public TSAccount createAccount(TSAccountCreate currencyExchangeRequest) {
log.error("Could not create account for customer id {}", currencyExchangeRequest.customerId());
throw new ClientConnectionException("Could not create account");
throw new ExternalServiceException("Could not create account");
}
@Override
public List<TSAccount> getAccountsByCustomerId(String customerId) {
log.error("Could not get accounts by customer id {}", customerId);
throw new ClientConnectionException("Could not get accounts by customer id");
throw new ExternalServiceException("Could not get accounts by customer id");
}
}
}
package cz.muni.fi.obs.http;
import cz.muni.fi.obs.exceptions.ExternalServiceException;
import cz.muni.fi.obs.exceptions.ResourceNotFoundException;
import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
@Slf4j
public class TransactionServiceErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
@Override
public Exception decode(String methodKey, Response response) {
String requestUrl = response.request().url();
Response.Body responseBody = response.body();
HttpStatus responseStatus = HttpStatus.valueOf(response.status());
if (responseStatus.value() == HttpStatus.NOT_FOUND.value()) {
return new ResourceNotFoundException(responseBody.toString());
} else if (responseStatus.value() == HttpStatus.BAD_REQUEST.value()) {
return defaultErrorDecoder.decode(methodKey, response);
} else {
log.error("Server responded with error: {}. Request URL: {}, Response body: {}",
responseStatus.value(),
requestUrl,
responseBody.toString()
);
return new ExternalServiceException("Could not connect to the server. Please try again later.");
}
}
}
......@@ -2,10 +2,10 @@ package cz.muni.fi.obs.service;
import cz.muni.fi.obs.api.AccountCreateDto;
import cz.muni.fi.obs.api.AccountDto;
import cz.muni.fi.obs.exceptions.ResourceNotFoundException;
import cz.muni.fi.obs.http.TransactionServiceClient;
import cz.muni.fi.obs.http.api.TSAccount;
import cz.muni.fi.obs.http.api.TSAccountCreate;
import feign.FeignException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
......@@ -49,7 +49,7 @@ public class UserAccountService {
tsAccount.currencyCode()
))
.collect(Collectors.toList());
} catch (FeignException.NotFound e) {
} catch (ResourceNotFoundException e) {
return Collections.emptyList();
}
}
......
package cz.muni.fi.obs.service;
import cz.muni.fi.obs.api.UserCreateDto;
import cz.muni.fi.obs.api.UserSearchParamsDto;
import cz.muni.fi.obs.api.UserSearchParamsPaginatedDto;
import cz.muni.fi.obs.api.UserUpdateDto;
import cz.muni.fi.obs.data.dbo.User;
import cz.muni.fi.obs.data.repository.UserRepository;
......@@ -9,7 +9,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.UUID;
@Service
......@@ -67,15 +66,15 @@ public class UserService {
return userRepository.findByIdOrThrow(userId);
}
public Page<User> findUsers(UserSearchParamsDto searchParams) {
public Page<User> findUsers(UserSearchParamsPaginatedDto searchParams) {
return userRepository.findBySearchParams(
searchParams.firstName(),
searchParams.lastName(),
searchParams.phoneNumber(),
searchParams.email(),
searchParams.birthDate(),
searchParams.birthNumber(),
searchParams.active().isEmpty() ? Optional.of(true) : searchParams.active(),
searchParams.firstName().orElse(null),
searchParams.lastName().orElse(null),
searchParams.phoneNumber().orElse(null),
searchParams.email().orElse(null),
searchParams.birthDate().orElse(null),
searchParams.birthNumber().orElse(null),
searchParams.active().orElse(true),
searchParams.pageable()
);
}
......
......@@ -3,6 +3,12 @@ server:
context-path: '/api/user-service'
port: 8083
management:
endpoints:
web:
exposure:
include: [ 'prometheus', 'health', 'info' ]
spring:
datasource:
url: jdbc:postgresql://user-db:5432/user_db
......@@ -21,4 +27,4 @@ clients:
url: 'http://transaction-service:8082/api/transaction-service'
data:
initialize: true
\ No newline at end of file
initialize: true