diff --git a/pom.xml b/pom.xml
index d7bdb34..4eb2d2b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,6 +16,7 @@
17
3.1.1
+ 2.1.0
@@ -39,6 +40,11 @@
modelmapper
${modelmapper.version}
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ ${springdoc-openapi.version}
+
org.springframework.boot
diff --git a/src/main/java/net/kapcake/bankingservice/config/AuthConfig.java b/src/main/java/net/kapcake/bankingservice/config/AuthConfig.java
index c965e04..20fef59 100644
--- a/src/main/java/net/kapcake/bankingservice/config/AuthConfig.java
+++ b/src/main/java/net/kapcake/bankingservice/config/AuthConfig.java
@@ -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();
}
diff --git a/src/main/java/net/kapcake/bankingservice/config/BankingServiceConfig.java b/src/main/java/net/kapcake/bankingservice/config/BankingServiceConfig.java
index 8236d7e..35964ec 100644
--- a/src/main/java/net/kapcake/bankingservice/config/BankingServiceConfig.java
+++ b/src/main/java/net/kapcake/bankingservice/config/BankingServiceConfig.java
@@ -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")
+ );
+ }
}
diff --git a/src/main/java/net/kapcake/bankingservice/controllers/AuthController.java b/src/main/java/net/kapcake/bankingservice/controllers/AuthController.java
index 739f476..08b1e86 100644
--- a/src/main/java/net/kapcake/bankingservice/controllers/AuthController.java
+++ b/src/main/java/net/kapcake/bankingservice/controllers/AuthController.java
@@ -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();
}
diff --git a/src/main/java/net/kapcake/bankingservice/controllers/BankingServiceController.java b/src/main/java/net/kapcake/bankingservice/controllers/BankingServiceController.java
index 800baeb..6c6d7dc 100644
--- a/src/main/java/net/kapcake/bankingservice/controllers/BankingServiceController.java
+++ b/src/main/java/net/kapcake/bankingservice/controllers/BankingServiceController.java
@@ -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 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 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 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> 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);
}
diff --git a/src/main/java/net/kapcake/bankingservice/controllers/ControllerUtils.java b/src/main/java/net/kapcake/bankingservice/controllers/ControllerUtils.java
index 6232551..9c7909b 100644
--- a/src/main/java/net/kapcake/bankingservice/controllers/ControllerUtils.java
+++ b/src/main/java/net/kapcake/bankingservice/controllers/ControllerUtils.java
@@ -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 String getErrorString(Set> violations) {
+ StringBuilder builder = new StringBuilder("[");
+ List> allErrors = violations.stream().toList();
+ for (int i = 0; i < allErrors.size(); i++) {
+ ConstraintViolation 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();
+ }
+
}
diff --git a/src/main/java/net/kapcake/bankingservice/converters/AbstractConverter.java b/src/main/java/net/kapcake/bankingservice/converters/AbstractConverter.java
index 4040c1b..3949116 100644
--- a/src/main/java/net/kapcake/bankingservice/converters/AbstractConverter.java
+++ b/src/main/java/net/kapcake/bankingservice/converters/AbstractConverter.java
@@ -3,7 +3,7 @@ package net.kapcake.bankingservice.converters;
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
-public abstract class AbstractConverter {
+public abstract class AbstractConverter {
protected final ModelMapper modelMapper;
public AbstractConverter(ModelMapper modelMapper) {
@@ -11,8 +11,4 @@ public abstract class AbstractConverter {
this.modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
}
-
- public abstract D mapToDTO(E entity);
-
- public abstract E mapToEntity(D dto);
}
diff --git a/src/main/java/net/kapcake/bankingservice/converters/BankAccountConverter.java b/src/main/java/net/kapcake/bankingservice/converters/BankAccountConverter.java
index 313d4e9..011ec42 100644
--- a/src/main/java/net/kapcake/bankingservice/converters/BankAccountConverter.java
+++ b/src/main/java/net/kapcake/bankingservice/converters/BankAccountConverter.java
@@ -6,17 +6,15 @@ import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Component;
@Component
-public class BankAccountConverter extends AbstractConverter {
+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);
}
diff --git a/src/main/java/net/kapcake/bankingservice/converters/PaymentConverter.java b/src/main/java/net/kapcake/bankingservice/converters/PaymentConverter.java
index f47a943..1634340 100644
--- a/src/main/java/net/kapcake/bankingservice/converters/PaymentConverter.java
+++ b/src/main/java/net/kapcake/bankingservice/converters/PaymentConverter.java
@@ -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 {
+public class PaymentConverter extends AbstractConverter {
private final BankAccountRepository bankAccountRepository;
public PaymentConverter(BankAccountRepository bankAccountRepository, ModelMapper modelMapper) {
@@ -20,7 +21,6 @@ public class PaymentConverter extends AbstractConverter {
this.bankAccountRepository = bankAccountRepository;
}
- @Override
public PaymentDTO mapToDTO(Payment payment) {
TypeMap paymentPaymentDTOTypeMap = modelMapper
.typeMap(Payment.class, PaymentDTO.class)
@@ -28,10 +28,9 @@ public class PaymentConverter extends AbstractConverter {
return paymentPaymentDTOTypeMap.map(payment);
}
- @Override
- public Payment mapToEntity(PaymentDTO paymentDTO) {
- Payment payment = modelMapper.map(paymentDTO, Payment.class);
- Optional bankAccountOptional = bankAccountRepository.findById(paymentDTO.getGiverAccount());
+ public Payment mapToEntity(PaymentRequest paymentRequest) {
+ Payment payment = modelMapper.map(paymentRequest, Payment.class);
+ Optional bankAccountOptional = bankAccountRepository.findById(paymentRequest.getGiverAccount());
if (bankAccountOptional.isEmpty()) {
throw new ValidationException("Payment request invalid: Giver account does not exist.");
}
diff --git a/src/main/java/net/kapcake/bankingservice/model/domain/Payment.java b/src/main/java/net/kapcake/bankingservice/model/domain/Payment.java
index 9a17725..f335366 100644
--- a/src/main/java/net/kapcake/bankingservice/model/domain/Payment.java
+++ b/src/main/java/net/kapcake/bankingservice/model/domain/Payment.java
@@ -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;
}
diff --git a/src/main/java/net/kapcake/bankingservice/model/dtos/PaymentDTO.java b/src/main/java/net/kapcake/bankingservice/model/dtos/PaymentDTO.java
index 494100f..3783566 100644
--- a/src/main/java/net/kapcake/bankingservice/model/dtos/PaymentDTO.java
+++ b/src/main/java/net/kapcake/bankingservice/model/dtos/PaymentDTO.java
@@ -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;
}
diff --git a/src/main/java/net/kapcake/bankingservice/model/dtos/PaymentFilter.java b/src/main/java/net/kapcake/bankingservice/model/dtos/PaymentFilter.java
index 0fb7622..cd4c7c4 100644
--- a/src/main/java/net/kapcake/bankingservice/model/dtos/PaymentFilter.java
+++ b/src/main/java/net/kapcake/bankingservice/model/dtos/PaymentFilter.java
@@ -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);
}
}
}
diff --git a/src/main/java/net/kapcake/bankingservice/model/dtos/PaymentRequest.java b/src/main/java/net/kapcake/bankingservice/model/dtos/PaymentRequest.java
new file mode 100644
index 0000000..7ade85d
--- /dev/null
+++ b/src/main/java/net/kapcake/bankingservice/model/dtos/PaymentRequest.java
@@ -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;
+}
diff --git a/src/main/java/net/kapcake/bankingservice/model/dtos/UserUpdateDTO.java b/src/main/java/net/kapcake/bankingservice/model/dtos/UserUpdateRequest.java
similarity index 91%
rename from src/main/java/net/kapcake/bankingservice/model/dtos/UserUpdateDTO.java
rename to src/main/java/net/kapcake/bankingservice/model/dtos/UserUpdateRequest.java
index e3012ba..956763b 100644
--- a/src/main/java/net/kapcake/bankingservice/model/dtos/UserUpdateDTO.java
+++ b/src/main/java/net/kapcake/bankingservice/model/dtos/UserUpdateRequest.java
@@ -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;
diff --git a/src/main/java/net/kapcake/bankingservice/services/PaymentService.java b/src/main/java/net/kapcake/bankingservice/services/PaymentService.java
index de6c316..ab56820 100644
--- a/src/main/java/net/kapcake/bankingservice/services/PaymentService.java
+++ b/src/main/java/net/kapcake/bankingservice/services/PaymentService.java
@@ -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 userBankAccounts = bankAccountRepository.findAllByUsers_username(authenticatedUser.getUsername());
paymentValidator.validate(userBankAccounts, payment);
@@ -92,18 +93,18 @@ public class PaymentService {
List 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());
diff --git a/src/main/java/net/kapcake/bankingservice/services/UserService.java b/src/main/java/net/kapcake/bankingservice/services/UserService.java
index 4887991..024420f 100644
--- a/src/main/java/net/kapcake/bankingservice/services/UserService.java
+++ b/src/main/java/net/kapcake/bankingservice/services/UserService.java
@@ -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;
}
diff --git a/src/test/java/net/kapcake/bankingservice/services/PaymentServiceTest.java b/src/test/java/net/kapcake/bankingservice/services/PaymentServiceTest.java
index 2ca4ac4..45def67 100644
--- a/src/test/java/net/kapcake/bankingservice/services/PaymentServiceTest.java
+++ b/src/test/java/net/kapcake/bankingservice/services/PaymentServiceTest.java
@@ -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 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 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 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 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 paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
assertEquals(2, paymentsForUser.size());
@@ -203,4 +210,4 @@ class PaymentServiceTest {
verify(paymentRepository).deleteById(1L);
}
-}
\ No newline at end of file
+}