diff --git a/.woodpecker/maven.yaml b/.woodpecker/maven.yaml
index 82461b2..6458761 100644
--- a/.woodpecker/maven.yaml
+++ b/.woodpecker/maven.yaml
@@ -1,5 +1,5 @@
variables:
- &maven_image maven:3.9.6-eclipse-temurin-17-alpine
+ &maven_image maven:3.9.6-eclipse-temurin-21-alpine
steps:
- name: build
diff --git a/pom.xml b/pom.xml
index 3756469..9ae7829 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
${source.encoding}
- 17
+ 21
${java.version}
${java.version}
@@ -51,6 +51,10 @@
org.springframework.boot
spring-boot-starter-data-jpa
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
@@ -69,6 +73,12 @@
org.springframework.boot
spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+ test
org.junit.jupiter
@@ -109,6 +119,32 @@
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ default-test
+
+
+ **/*Tests.java
+
+
+
+
+ integration-tests
+ test
+
+ test
+
+
+
+ **/*Test.java
+
+
+
+
+
diff --git a/src/main/java/eu/ztsh/wymiana/config/NbpProperties.java b/src/main/java/eu/ztsh/wymiana/config/NbpProperties.java
new file mode 100644
index 0000000..0ed6c42
--- /dev/null
+++ b/src/main/java/eu/ztsh/wymiana/config/NbpProperties.java
@@ -0,0 +1,8 @@
+package eu.ztsh.wymiana.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties("nbp")
+public record NbpProperties(String baseurl) {
+
+}
diff --git a/src/main/java/eu/ztsh/wymiana/config/RestClientConfiguration.java b/src/main/java/eu/ztsh/wymiana/config/RestClientConfiguration.java
index 74ee47b..f264dbe 100644
--- a/src/main/java/eu/ztsh/wymiana/config/RestClientConfiguration.java
+++ b/src/main/java/eu/ztsh/wymiana/config/RestClientConfiguration.java
@@ -8,9 +8,9 @@ import org.springframework.web.client.RestClient;
public class RestClientConfiguration {
@Bean
- public RestClient restClient() {
+ public RestClient restClient(NbpProperties nbpProperties) {
return RestClient.builder()
- .baseUrl("http://api.nbp.pl")
+ .baseUrl(nbpProperties.baseurl())
.defaultHeader("Accept", "application/json")
.build();
}
diff --git a/src/main/java/eu/ztsh/wymiana/exception/UserNotFoundException.java b/src/main/java/eu/ztsh/wymiana/exception/UserNotFoundException.java
new file mode 100644
index 0000000..3cf38c2
--- /dev/null
+++ b/src/main/java/eu/ztsh/wymiana/exception/UserNotFoundException.java
@@ -0,0 +1,15 @@
+package eu.ztsh.wymiana.exception;
+
+import eu.ztsh.wymiana.web.model.CurrencyExchangeRequest;
+
+public class UserNotFoundException extends RuntimeException {
+
+ public UserNotFoundException(CurrencyExchangeRequest entity) {
+ this(entity.pesel());
+ }
+
+ public UserNotFoundException(String pesel) {
+ super("User with PESEL %s not found".formatted(pesel));
+ }
+
+}
diff --git a/src/main/java/eu/ztsh/wymiana/service/CurrencyService.java b/src/main/java/eu/ztsh/wymiana/service/CurrencyService.java
index b00f761..401626a 100644
--- a/src/main/java/eu/ztsh/wymiana/service/CurrencyService.java
+++ b/src/main/java/eu/ztsh/wymiana/service/CurrencyService.java
@@ -2,6 +2,7 @@ package eu.ztsh.wymiana.service;
import eu.ztsh.wymiana.exception.ExchangeFailedException;
import eu.ztsh.wymiana.exception.InsufficientFundsException;
+import eu.ztsh.wymiana.exception.UserNotFoundException;
import eu.ztsh.wymiana.model.Currency;
import eu.ztsh.wymiana.model.User;
import eu.ztsh.wymiana.validation.InstanceValidator;
@@ -49,7 +50,7 @@ public class CurrencyService {
user.currencies().putAll(exchanged);
return userService.update(user);
})
- .orElseThrow(ExchangeFailedException::new);
+ .orElseThrow(() -> new UserNotFoundException(request));
}
private Currency create(String symbol) {
diff --git a/src/main/java/eu/ztsh/wymiana/service/UserService.java b/src/main/java/eu/ztsh/wymiana/service/UserService.java
index f9e7cde..857bc0f 100644
--- a/src/main/java/eu/ztsh/wymiana/service/UserService.java
+++ b/src/main/java/eu/ztsh/wymiana/service/UserService.java
@@ -7,10 +7,13 @@ import eu.ztsh.wymiana.util.UserMapper;
import eu.ztsh.wymiana.validation.InstanceValidator;
import eu.ztsh.wymiana.web.model.UserCreateRequest;
import lombok.RequiredArgsConstructor;
+import org.hibernate.validator.constraints.pl.PESEL;
import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
import java.util.Optional;
+@Validated
@RequiredArgsConstructor
@Service
public class UserService {
@@ -26,7 +29,7 @@ public class UserService {
return UserMapper.entityToPojo(userRepository.save(UserMapper.requestToEntity(request)));
}
- public Optional get(String pesel) {
+ public Optional get(@PESEL String pesel) {
return userRepository.findById(pesel).map(UserMapper::entityToPojo);
}
diff --git a/src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java b/src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java
new file mode 100644
index 0000000..fb0ee13
--- /dev/null
+++ b/src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java
@@ -0,0 +1,39 @@
+package eu.ztsh.wymiana.web.controller;
+
+import eu.ztsh.wymiana.exception.InsufficientFundsException;
+import eu.ztsh.wymiana.exception.UserNotFoundException;
+import eu.ztsh.wymiana.service.CurrencyService;
+import eu.ztsh.wymiana.web.model.CurrencyExchangeRequest;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RequiredArgsConstructor
+@Validated
+@RestController
+@RequestMapping(path = "/api/exchange", produces = "application/json")
+public class ExchangeController {
+
+ private final CurrencyService currencyService;
+
+ @PostMapping
+ public ResponseEntity