Add Swagger documentation
* Split PaymentDTO and PaymentRequest * Updated Converters accordingly * Use LocalDateTime instead of Date
This commit is contained in:
6
pom.xml
6
pom.xml
@@ -16,6 +16,7 @@
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<modelmapper.version>3.1.1</modelmapper.version>
|
||||
<springdoc-openapi.version>2.1.0</springdoc-openapi.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -39,6 +40,11 @@
|
||||
<artifactId>modelmapper</artifactId>
|
||||
<version>${modelmapper.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>${springdoc-openapi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -2,8 +2,12 @@ package net.kapcake.bankingservice.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
@@ -12,15 +16,42 @@ import org.springframework.security.web.SecurityFilterChain;
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class AuthConfig {
|
||||
private static final String[] AUTH_WHITELIST = {
|
||||
"/v3/api-docs/**",
|
||||
"/swagger-ui.html",
|
||||
"/swagger-ui/**"
|
||||
};
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
public SecurityFilterChain loginFilterChain(HttpSecurity http) throws Exception {
|
||||
return http
|
||||
.securityMatcher("/login", "/logout")
|
||||
.csrf().disable()
|
||||
.httpBasic().and()
|
||||
.logout(logout -> logout
|
||||
.clearAuthentication(true)
|
||||
.invalidateHttpSession(true)).sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain authenticationFilterChain(HttpSecurity http) throws Exception {
|
||||
return http
|
||||
.csrf().disable()
|
||||
.authorizeHttpRequests(requests -> requests
|
||||
.requestMatchers(AUTH_WHITELIST).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.httpBasic().and()
|
||||
.httpBasic(AbstractHttpConfigurer::disable)
|
||||
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
|
||||
.exceptionHandling(handler -> handler
|
||||
.authenticationEntryPoint((request, response, authException) -> {
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
response.setContentType(MediaType.APPLICATION_JSON.toString());
|
||||
response.getWriter().write("{ \"error\": \"You are not authenticated.\" }");
|
||||
})
|
||||
)
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package net.kapcake.bankingservice.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import org.modelmapper.ModelMapper;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -17,4 +19,14 @@ public class BankingServiceConfig {
|
||||
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
|
||||
return restTemplateBuilder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OpenAPI openAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("Banking REST service")
|
||||
.description("Simple REST API to perform payments")
|
||||
.version("0.0.1")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package net.kapcake.bankingservice.controllers;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kapcake.bankingservice.exceptions.ValidationException;
|
||||
import net.kapcake.bankingservice.model.dtos.UserUpdateDTO;
|
||||
import net.kapcake.bankingservice.model.dtos.UserUpdateRequest;
|
||||
import net.kapcake.bankingservice.security.UserDetailsImpl;
|
||||
import net.kapcake.bankingservice.services.UserService;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -18,6 +23,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static net.kapcake.bankingservice.controllers.ControllerUtils.getErrorString;
|
||||
|
||||
@Tag(name = "Authentication controller")
|
||||
@RestController
|
||||
@Slf4j
|
||||
public class AuthController {
|
||||
@@ -27,6 +33,12 @@ public class AuthController {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Operation(summary = "Login using basic authentication to get a session cookie")
|
||||
@SecurityRequirement(name = "basic")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "Successful login"),
|
||||
@ApiResponse(responseCode = "401", description = "Unsuccessful login")
|
||||
})
|
||||
@PostMapping("/login")
|
||||
public void login(HttpServletRequest request) {
|
||||
Authentication auth = (Authentication) request.getUserPrincipal();
|
||||
@@ -34,13 +46,30 @@ public class AuthController {
|
||||
log.info("User {} logged in.", user.getUsername());
|
||||
}
|
||||
|
||||
@Operation(summary = "Logout and invalidate session")
|
||||
@ApiResponse(responseCode = "204", description = "Successful logout")
|
||||
@PostMapping("/logout")
|
||||
public void logout() {
|
||||
// Logout is handled by Spring Security
|
||||
}
|
||||
|
||||
@Operation(summary = "Update user details")
|
||||
@SecurityRequirement(name = "cookie")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "Successful update. If the password was updated, the user has been logged out"),
|
||||
@ApiResponse(responseCode = "400", description = "Update request is malformed"),
|
||||
@ApiResponse(responseCode = "401", description = "User not authenticated")
|
||||
})
|
||||
@PutMapping("/update-user")
|
||||
public void updateUser(HttpServletRequest request, @AuthenticationPrincipal UserDetailsImpl authenticatedUser, @RequestBody @Valid UserUpdateDTO userUpdateDTO, BindingResult bindingResult) throws ServletException {
|
||||
public void updateUser(HttpServletRequest request,
|
||||
@AuthenticationPrincipal UserDetailsImpl authenticatedUser,
|
||||
@RequestBody @Valid UserUpdateRequest userUpdateRequest,
|
||||
BindingResult bindingResult) throws ServletException {
|
||||
if (bindingResult.hasErrors()) {
|
||||
String errorString = getErrorString(bindingResult);
|
||||
throw new ValidationException("User update request invalid: " + errorString);
|
||||
}
|
||||
boolean needsLogout = userService.updateUser(authenticatedUser, userUpdateDTO);
|
||||
boolean needsLogout = userService.updateUser(authenticatedUser, userUpdateRequest);
|
||||
if (needsLogout) {
|
||||
request.logout();
|
||||
}
|
||||
|
||||
@@ -1,58 +1,119 @@
|
||||
package net.kapcake.bankingservice.controllers;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.Validator;
|
||||
import net.kapcake.bankingservice.exceptions.ValidationException;
|
||||
import net.kapcake.bankingservice.model.dtos.BankAccountDTO;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentFilter;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentRequest;
|
||||
import net.kapcake.bankingservice.security.UserDetailsImpl;
|
||||
import net.kapcake.bankingservice.services.AccountService;
|
||||
import net.kapcake.bankingservice.services.PaymentService;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static net.kapcake.bankingservice.controllers.ControllerUtils.getErrorString;
|
||||
|
||||
@Tag(name = "Banking Service Controller")
|
||||
@RestController
|
||||
public class BankingServiceController {
|
||||
private final AccountService accountService;
|
||||
private final PaymentService paymentService;
|
||||
private final Validator validator;
|
||||
|
||||
public BankingServiceController(AccountService accountService, PaymentService paymentService) {
|
||||
public BankingServiceController(AccountService accountService, PaymentService paymentService, Validator validator) {
|
||||
this.accountService = accountService;
|
||||
this.paymentService = paymentService;
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
@Operation(summary = "List user bank accounts")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "List of accounts owned by the authenticated user"),
|
||||
@ApiResponse(responseCode = "401", description = "User not authenticated", content = @Content)
|
||||
})
|
||||
@SecurityRequirement(name = "cookie")
|
||||
@GetMapping("/accounts")
|
||||
public List<BankAccountDTO> getAccounts(@AuthenticationPrincipal UserDetailsImpl authenticatedUser) {
|
||||
return accountService.getAccounts(authenticatedUser);
|
||||
}
|
||||
|
||||
@Operation(summary = "Create payment")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "201", description = "The payment was created successfully"),
|
||||
@ApiResponse(responseCode = "400", description = "The payment request is invalid", content = @Content),
|
||||
@ApiResponse(responseCode = "401", description = "User not authenticated", content = @Content)
|
||||
})
|
||||
@SecurityRequirement(name = "cookie")
|
||||
@PostMapping("/payment")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public PaymentDTO createPayment(@AuthenticationPrincipal UserDetailsImpl authenticatedUser, @RequestBody @Valid PaymentDTO paymentDTO, BindingResult bindingResult) {
|
||||
public PaymentDTO createPayment(@AuthenticationPrincipal UserDetailsImpl authenticatedUser,
|
||||
@RequestBody @Valid PaymentRequest paymentRequest,
|
||||
BindingResult bindingResult) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
String errorString = getErrorString(bindingResult);
|
||||
throw new ValidationException("Payment request invalid: " + errorString);
|
||||
}
|
||||
return paymentService.createPayment(authenticatedUser, paymentDTO);
|
||||
return paymentService.createPayment(authenticatedUser, paymentRequest);
|
||||
}
|
||||
|
||||
@Operation(summary = "List payments")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "List of payments by the authenticated user filtered by parameter values"),
|
||||
@ApiResponse(responseCode = "400", description = "The filter is invalid", content = @Content),
|
||||
@ApiResponse(responseCode = "401", description = "User not authenticated", content = @Content)
|
||||
})
|
||||
@SecurityRequirement(name = "cookie")
|
||||
@GetMapping("/payments")
|
||||
public List<PaymentDTO> getPayments(@AuthenticationPrincipal UserDetailsImpl authenticatedUser, @RequestBody(required = false) @Valid PaymentFilter paymentFilter, BindingResult bindingResult) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
String errorString = getErrorString(bindingResult);
|
||||
throw new ValidationException("Payment filter is invalid: " + errorString);
|
||||
public List<PaymentDTO> getPayments(@AuthenticationPrincipal UserDetailsImpl authenticatedUser,
|
||||
@RequestParam(name = "beneficiaryAccountNumber", required = false)
|
||||
@Parameter(description = "The start date to filter payments",example = "LU560303O43349845521")
|
||||
String beneficiaryAccountNumber,
|
||||
@RequestParam(name = "startDate", required = false)
|
||||
@Parameter(description = "The start date to filter payments", example = "2023-05-11T12:00")
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
|
||||
LocalDateTime startDate,
|
||||
@RequestParam(name = "endDate", required = false)
|
||||
@Parameter(description = "The end date to filter payments", example = "2023-05-11T12:00")
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
|
||||
LocalDateTime endDate) {
|
||||
PaymentFilter paymentFilter = new PaymentFilter().setStartDate(startDate).setEndDate(endDate).setBeneficiaryAccountNumber(beneficiaryAccountNumber);
|
||||
final Set<ConstraintViolation<PaymentFilter>> violations = validator.validate(paymentFilter);
|
||||
if (!violations.isEmpty()) {
|
||||
String errorString = getErrorString(violations);
|
||||
throw new ValidationException("Payment request invalid: " + errorString);
|
||||
}
|
||||
return paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
|
||||
}
|
||||
|
||||
@Operation(summary = "Delete payment")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "Payment successfully deleted"),
|
||||
@ApiResponse(responseCode = "401", description = "User not authenticated"),
|
||||
@ApiResponse(responseCode = "403", description = "The payment to be deleted does not belong to the authenticated user or has already been executed"),
|
||||
@ApiResponse(responseCode = "404", description = "A payment with this id was not found")
|
||||
})
|
||||
@SecurityRequirement(name = "cookie")
|
||||
@DeleteMapping("/payment/{id}")
|
||||
public void deletePayment(@AuthenticationPrincipal UserDetailsImpl authenticatedUser, @PathVariable("id") Long id) {
|
||||
public void deletePayment(@AuthenticationPrincipal UserDetailsImpl authenticatedUser,
|
||||
@Parameter(required = true, description = "Id of the payment to delete", example = "1")
|
||||
@PathVariable("id") Long id) {
|
||||
paymentService.deletePayment(authenticatedUser, id);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package net.kapcake.bankingservice.controllers;
|
||||
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class ControllerUtils {
|
||||
public static String getErrorString(BindingResult bindingResult) {
|
||||
@@ -24,4 +26,23 @@ public class ControllerUtils {
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static <T> String getErrorString(Set<ConstraintViolation<T>> violations) {
|
||||
StringBuilder builder = new StringBuilder("[");
|
||||
List<ConstraintViolation<T>> allErrors = violations.stream().toList();
|
||||
for (int i = 0; i < allErrors.size(); i++) {
|
||||
ConstraintViolation<T> error = allErrors.get(i);
|
||||
if (i != 0) {
|
||||
builder.append("\n");
|
||||
}
|
||||
if (error.getPropertyPath() != null && !error.getPropertyPath().toString().isBlank()) {
|
||||
builder.append(error.getPropertyPath())
|
||||
.append(": ");
|
||||
}
|
||||
builder.append(error.getMessage());
|
||||
}
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package net.kapcake.bankingservice.converters;
|
||||
import org.modelmapper.ModelMapper;
|
||||
import org.modelmapper.convention.MatchingStrategies;
|
||||
|
||||
public abstract class AbstractConverter<E, D> {
|
||||
public abstract class AbstractConverter {
|
||||
protected final ModelMapper modelMapper;
|
||||
|
||||
public AbstractConverter(ModelMapper modelMapper) {
|
||||
@@ -11,8 +11,4 @@ public abstract class AbstractConverter<E, D> {
|
||||
|
||||
this.modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
|
||||
}
|
||||
|
||||
public abstract D mapToDTO(E entity);
|
||||
|
||||
public abstract E mapToEntity(D dto);
|
||||
}
|
||||
|
||||
@@ -6,17 +6,15 @@ import org.modelmapper.ModelMapper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class BankAccountConverter extends AbstractConverter<BankAccount, BankAccountDTO> {
|
||||
public class BankAccountConverter extends AbstractConverter {
|
||||
public BankAccountConverter(ModelMapper modelMapper) {
|
||||
super(modelMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BankAccountDTO mapToDTO(BankAccount bankAccount) {
|
||||
return modelMapper.map(bankAccount, BankAccountDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BankAccount mapToEntity(BankAccountDTO bankAccountDTO) {
|
||||
return modelMapper.map(bankAccountDTO, BankAccount.class);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import net.kapcake.bankingservice.exceptions.ValidationException;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
|
||||
import net.kapcake.bankingservice.model.domain.BankAccount;
|
||||
import net.kapcake.bankingservice.model.domain.Payment;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentRequest;
|
||||
import net.kapcake.bankingservice.repositories.BankAccountRepository;
|
||||
import org.modelmapper.ModelMapper;
|
||||
import org.modelmapper.TypeMap;
|
||||
@@ -12,7 +13,7 @@ import org.springframework.stereotype.Component;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
public class PaymentConverter extends AbstractConverter<Payment, PaymentDTO> {
|
||||
public class PaymentConverter extends AbstractConverter {
|
||||
private final BankAccountRepository bankAccountRepository;
|
||||
|
||||
public PaymentConverter(BankAccountRepository bankAccountRepository, ModelMapper modelMapper) {
|
||||
@@ -20,7 +21,6 @@ public class PaymentConverter extends AbstractConverter<Payment, PaymentDTO> {
|
||||
this.bankAccountRepository = bankAccountRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDTO mapToDTO(Payment payment) {
|
||||
TypeMap<Payment, PaymentDTO> paymentPaymentDTOTypeMap = modelMapper
|
||||
.typeMap(Payment.class, PaymentDTO.class)
|
||||
@@ -28,10 +28,9 @@ public class PaymentConverter extends AbstractConverter<Payment, PaymentDTO> {
|
||||
return paymentPaymentDTOTypeMap.map(payment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Payment mapToEntity(PaymentDTO paymentDTO) {
|
||||
Payment payment = modelMapper.map(paymentDTO, Payment.class);
|
||||
Optional<BankAccount> bankAccountOptional = bankAccountRepository.findById(paymentDTO.getGiverAccount());
|
||||
public Payment mapToEntity(PaymentRequest paymentRequest) {
|
||||
Payment payment = modelMapper.map(paymentRequest, Payment.class);
|
||||
Optional<BankAccount> bankAccountOptional = bankAccountRepository.findById(paymentRequest.getGiverAccount());
|
||||
if (bankAccountOptional.isEmpty()) {
|
||||
throw new ValidationException("Payment request invalid: Giver account does not exist.");
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import lombok.experimental.Accessors;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Accessors(chain = true)
|
||||
@@ -31,9 +31,8 @@ public class Payment {
|
||||
private String beneficiaryName;
|
||||
private String communication;
|
||||
@CreationTimestamp
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(nullable = false, updatable = false)
|
||||
private Date creationDate;
|
||||
private LocalDateTime creationDate;
|
||||
@Enumerated(EnumType.STRING)
|
||||
private PaymentStatus status;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import net.kapcake.bankingservice.model.domain.PaymentStatus;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@@ -27,6 +27,6 @@ public class PaymentDTO implements Serializable {
|
||||
@NotNull
|
||||
private String beneficiaryName;
|
||||
private String communication;
|
||||
private Date creationDate;
|
||||
private LocalDateTime creationDate;
|
||||
private PaymentStatus status;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
package net.kapcake.bankingservice.model.dtos;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.AssertTrue;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import net.kapcake.bankingservice.validation.AtLeastOneFieldNotEmpty;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@AtLeastOneFieldNotEmpty(fieldNames = {"beneficiaryAccountNumber", "startDate", "endDate"})
|
||||
public class PaymentFilter implements Serializable {
|
||||
|
||||
private String beneficiaryAccountNumber;
|
||||
private Date startDate;
|
||||
private Date endDate;
|
||||
private LocalDateTime startDate;
|
||||
private LocalDateTime endDate;
|
||||
|
||||
@Schema(hidden = true)
|
||||
@AssertTrue(message = "Start and end date need to be in order")
|
||||
public boolean isValidRange() {
|
||||
if (startDate == null || endDate == null) {
|
||||
return true;
|
||||
} else {
|
||||
return startDate.compareTo(endDate) <= 0;
|
||||
return !startDate.isAfter(endDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.kapcake.bankingservice.model.dtos;
|
||||
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import net.kapcake.bankingservice.model.domain.Currency;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PaymentRequest implements Serializable {
|
||||
@NotNull
|
||||
@DecimalMin(value = "0", inclusive = false)
|
||||
private BigDecimal amount;
|
||||
@NotNull
|
||||
private Currency currency;
|
||||
@NotNull
|
||||
private Long giverAccount;
|
||||
@NotNull
|
||||
private String beneficiaryAccountNumber;
|
||||
@NotNull
|
||||
private String beneficiaryName;
|
||||
private String communication;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import java.io.Serializable;
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@AtLeastOneFieldNotEmpty(fieldNames = {"password", "street", "number", "numberExtension", "postalCode", "country"})
|
||||
public class UserUpdateDTO implements Serializable {
|
||||
public class UserUpdateRequest implements Serializable {
|
||||
@Pattern(regexp = "^[^\\s]+$")
|
||||
private String password;
|
||||
private String street;
|
||||
@@ -7,6 +7,7 @@ import net.kapcake.bankingservice.exceptions.ValidationException;
|
||||
import net.kapcake.bankingservice.model.domain.*;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentFilter;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentRequest;
|
||||
import net.kapcake.bankingservice.repositories.BalanceRepository;
|
||||
import net.kapcake.bankingservice.repositories.BankAccountRepository;
|
||||
import net.kapcake.bankingservice.repositories.PaymentRepository;
|
||||
@@ -16,8 +17,8 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -42,8 +43,8 @@ public class PaymentService {
|
||||
this.transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
}
|
||||
|
||||
public PaymentDTO createPayment(UserDetailsImpl authenticatedUser, PaymentDTO paymentDTO) {
|
||||
Payment payment = paymentConverter.mapToEntity(paymentDTO);
|
||||
public PaymentDTO createPayment(UserDetailsImpl authenticatedUser, PaymentRequest paymentRequest) {
|
||||
Payment payment = paymentConverter.mapToEntity(paymentRequest);
|
||||
|
||||
List<BankAccount> userBankAccounts = bankAccountRepository.findAllByUsers_username(authenticatedUser.getUsername());
|
||||
paymentValidator.validate(userBankAccounts, payment);
|
||||
@@ -92,18 +93,18 @@ public class PaymentService {
|
||||
List<Payment> filteredPayments = userPayments;
|
||||
if (paymentFilter != null) {
|
||||
String beneficiaryAccountNumber = paymentFilter.getBeneficiaryAccountNumber();
|
||||
Date startDate = paymentFilter.getStartDate();
|
||||
Date endDate = paymentFilter.getEndDate();
|
||||
LocalDateTime startDate = paymentFilter.getStartDate();
|
||||
LocalDateTime endDate = paymentFilter.getEndDate();
|
||||
filteredPayments = userPayments.stream().filter(payment -> {
|
||||
boolean filter = true;
|
||||
if (beneficiaryAccountNumber != null) {
|
||||
filter &= payment.getBeneficiaryAccountNumber().equals(beneficiaryAccountNumber);
|
||||
}
|
||||
if (startDate != null) {
|
||||
filter &= payment.getCreationDate().compareTo(startDate) >= 0;
|
||||
filter &= !payment.getCreationDate().isBefore(startDate);
|
||||
}
|
||||
if (endDate != null) {
|
||||
filter &= payment.getCreationDate().compareTo(endDate) <= 0;
|
||||
filter &= !payment.getCreationDate().isAfter(endDate);
|
||||
}
|
||||
return filter;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
@@ -1,47 +1,44 @@
|
||||
package net.kapcake.bankingservice.services;
|
||||
|
||||
import net.kapcake.bankingservice.model.domain.User;
|
||||
import net.kapcake.bankingservice.model.dtos.UserUpdateDTO;
|
||||
import net.kapcake.bankingservice.model.dtos.UserUpdateRequest;
|
||||
import net.kapcake.bankingservice.repositories.UserRepository;
|
||||
import net.kapcake.bankingservice.security.UserDetailsImpl;
|
||||
import org.modelmapper.ModelMapper;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class UserService {
|
||||
private final UserRepository userRepository;
|
||||
private final ModelMapper modelMapper;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
public UserService(UserRepository userRepository, ModelMapper modelMapper, PasswordEncoder passwordEncoder) {
|
||||
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
|
||||
this.userRepository = userRepository;
|
||||
this.modelMapper = modelMapper;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
public boolean updateUser(UserDetailsImpl authenticatedUser, UserUpdateDTO userUpdateDTO) {
|
||||
public boolean updateUser(UserDetailsImpl authenticatedUser, UserUpdateRequest userUpdateRequest) {
|
||||
boolean needsLogout = false;
|
||||
User user = userRepository.findByUsername(authenticatedUser.getUsername()).orElseThrow();
|
||||
User.UserBuilder builder = user.toBuilder();
|
||||
|
||||
if (userUpdateDTO.getCountry() != null) {
|
||||
builder.country(userUpdateDTO.getCountry());
|
||||
if (userUpdateRequest.getCountry() != null) {
|
||||
builder.country(userUpdateRequest.getCountry());
|
||||
}
|
||||
if (userUpdateDTO.getStreet() != null) {
|
||||
builder.street(userUpdateDTO.getStreet());
|
||||
if (userUpdateRequest.getStreet() != null) {
|
||||
builder.street(userUpdateRequest.getStreet());
|
||||
}
|
||||
if (userUpdateDTO.getPostalCode() != null) {
|
||||
builder.postalCode(userUpdateDTO.getPostalCode());
|
||||
if (userUpdateRequest.getPostalCode() != null) {
|
||||
builder.postalCode(userUpdateRequest.getPostalCode());
|
||||
}
|
||||
if (userUpdateDTO.getNumber() != null) {
|
||||
builder.number(userUpdateDTO.getNumber());
|
||||
if (userUpdateRequest.getNumber() != null) {
|
||||
builder.number(userUpdateRequest.getNumber());
|
||||
}
|
||||
if (userUpdateDTO.getNumberExtension() != null) {
|
||||
builder.numberExtension(userUpdateDTO.getNumberExtension());
|
||||
if (userUpdateRequest.getNumberExtension() != null) {
|
||||
builder.numberExtension(userUpdateRequest.getNumberExtension());
|
||||
}
|
||||
if (userUpdateDTO.getPassword() != null) {
|
||||
builder.password(passwordEncoder.encode(userUpdateDTO.getPassword()));
|
||||
if (userUpdateRequest.getPassword() != null) {
|
||||
builder.password(passwordEncoder.encode(userUpdateRequest.getPassword()));
|
||||
needsLogout = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ import net.kapcake.bankingservice.converters.PaymentConverter;
|
||||
import net.kapcake.bankingservice.exceptions.AccessDeniedException;
|
||||
import net.kapcake.bankingservice.exceptions.ResourceNotFoundException;
|
||||
import net.kapcake.bankingservice.exceptions.ValidationException;
|
||||
import net.kapcake.bankingservice.model.domain.Currency;
|
||||
import net.kapcake.bankingservice.model.domain.*;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentFilter;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentRequest;
|
||||
import net.kapcake.bankingservice.repositories.BalanceRepository;
|
||||
import net.kapcake.bankingservice.repositories.BankAccountRepository;
|
||||
import net.kapcake.bankingservice.repositories.PaymentRepository;
|
||||
@@ -21,7 +21,11 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -73,15 +77,16 @@ class PaymentServiceTest {
|
||||
@Test
|
||||
void createPayment() {
|
||||
UserDetailsImpl authenticatedUser = new UserDetailsImpl(user1);
|
||||
PaymentRequest paymentRequest = new PaymentRequest().setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(1L);
|
||||
PaymentDTO paymentDTO = new PaymentDTO().setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(1L);
|
||||
Payment payment = new Payment().setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(bankAccount1);
|
||||
|
||||
when(bankAccountRepository.findByAccountNumber(ACCOUNT_NUMBER_2)).thenReturn(Optional.of(bankAccount2));
|
||||
when(paymentConverter.mapToEntity(paymentDTO)).thenReturn(payment);
|
||||
when(paymentConverter.mapToEntity(paymentRequest)).thenReturn(payment);
|
||||
when(paymentRepository.save(payment)).thenReturn(payment);
|
||||
when(paymentConverter.mapToDTO(payment)).thenReturn(paymentDTO);
|
||||
|
||||
paymentService.createPayment(authenticatedUser, paymentDTO);
|
||||
paymentService.createPayment(authenticatedUser, paymentRequest);
|
||||
|
||||
assertEquals(balance1.getAmount(), BigDecimal.valueOf(450));
|
||||
assertEquals(balance2.getAmount(), BigDecimal.valueOf(550));
|
||||
@@ -91,12 +96,12 @@ class PaymentServiceTest {
|
||||
void getPaymentsForUser() {
|
||||
List<BankAccount> bankAccounts = List.of(bankAccount1);
|
||||
UserDetailsImpl authenticatedUser = new UserDetailsImpl(user1);
|
||||
Payment payment1 = new Payment().setId(1L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 0).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(bankAccount1);
|
||||
PaymentDTO paymentDTO1 = new PaymentDTO().setId(1L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 0).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(1L);
|
||||
Payment payment2 = new Payment().setId(2L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(bankAccount1);
|
||||
PaymentDTO paymentDTO2 = new PaymentDTO().setId(2L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(1L);
|
||||
Payment payment3 = new Payment().setId(3L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 2).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_3).setGiverAccount(bankAccount1);
|
||||
PaymentDTO paymentDTO3 = new PaymentDTO().setId(3L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 2).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_3).setGiverAccount(1L);
|
||||
Payment payment1 = new Payment().setId(1L).setCreationDate(LocalDateTime.parse("2023-05-11T12:00")).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(bankAccount1);
|
||||
PaymentDTO paymentDTO1 = new PaymentDTO().setId(1L).setCreationDate(LocalDateTime.parse("2023-05-11T12:00")).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(1L);
|
||||
Payment payment2 = new Payment().setId(2L).setCreationDate(LocalDateTime.parse("2023-05-11T12:01")).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(bankAccount1);
|
||||
PaymentDTO paymentDTO2 = new PaymentDTO().setId(2L).setCreationDate(LocalDateTime.parse("2023-05-11T12:01")).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(1L);
|
||||
Payment payment3 = new Payment().setId(3L).setCreationDate(LocalDateTime.parse("2023-05-11T12:02")).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_3).setGiverAccount(bankAccount1);
|
||||
PaymentDTO paymentDTO3 = new PaymentDTO().setId(3L).setCreationDate(LocalDateTime.parse("2023-05-11T12:02")).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_3).setGiverAccount(1L);
|
||||
|
||||
when(bankAccountRepository.findAllByUsers_username(TEST_USER_1)).thenReturn(bankAccounts);
|
||||
when(paymentRepository.findAllByGiverAccountIn(bankAccounts)).thenReturn(Arrays.asList(payment1, payment2, payment3));
|
||||
@@ -126,8 +131,8 @@ class PaymentServiceTest {
|
||||
}
|
||||
|
||||
private void getPaymentsForUsers_withAllFilter(UserDetailsImpl authenticatedUser) {
|
||||
Date startDate = new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime();
|
||||
Date endDate = new GregorianCalendar(2023, Calendar.MAY, 11, 12, 2).getTime();
|
||||
final LocalDateTime startDate = LocalDateTime.parse("2023-05-11T12:01");
|
||||
final LocalDateTime endDate = LocalDateTime.parse("2023-05-11T12:02");
|
||||
PaymentFilter paymentFilter = new PaymentFilter().setStartDate(startDate).setEndDate(endDate).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_3);
|
||||
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
|
||||
|
||||
@@ -136,7 +141,7 @@ class PaymentServiceTest {
|
||||
}
|
||||
|
||||
private void getPaymentsForUsers_withStartAndEndDateFilter(UserDetailsImpl authenticatedUser) {
|
||||
Date date = new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime();
|
||||
final LocalDateTime date = LocalDateTime.parse("2023-05-11T12:01");
|
||||
PaymentFilter paymentFilter = new PaymentFilter().setStartDate(date).setEndDate(date);
|
||||
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
|
||||
|
||||
@@ -145,7 +150,8 @@ class PaymentServiceTest {
|
||||
}
|
||||
|
||||
private void getPaymentsForUsers_withEndDateFilter(UserDetailsImpl authenticatedUser) {
|
||||
PaymentFilter paymentFilter = new PaymentFilter().setEndDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime());
|
||||
final LocalDateTime endDate = LocalDateTime.parse("2023-05-11T12:01");
|
||||
PaymentFilter paymentFilter = new PaymentFilter().setEndDate(endDate);
|
||||
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
|
||||
|
||||
assertEquals(2, paymentsForUser.size());
|
||||
@@ -154,7 +160,8 @@ class PaymentServiceTest {
|
||||
}
|
||||
|
||||
private void getPaymentsForUsers_withStartDateFilter(UserDetailsImpl authenticatedUser) {
|
||||
PaymentFilter paymentFilter = new PaymentFilter().setStartDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime());
|
||||
final LocalDateTime startDate = LocalDateTime.parse("2023-05-11T12:01");
|
||||
PaymentFilter paymentFilter = new PaymentFilter().setStartDate(startDate);
|
||||
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
|
||||
|
||||
assertEquals(2, paymentsForUser.size());
|
||||
|
||||
Reference in New Issue
Block a user