diff --git a/pom.xml b/pom.xml
index 69862f2..7431833 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,6 +28,7 @@
3.5.4
+ 2.5.0
1.2.1
@@ -62,6 +63,11 @@
lombok
provided
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ ${openapi.version}
+
@@ -110,6 +116,7 @@
${basedir}/src/main/resources/schema
eu.ztsh.wymiana.model
+ true
diff --git a/readme.md b/readme.md
index 92a57f9..f2ce86f 100644
--- a/readme.md
+++ b/readme.md
@@ -28,16 +28,16 @@ Prosty mikroserwis stworzony na potrzeby rekrutacji
"pesel": {
"type": "string"
},
- "pln": {
+ "initial": {
"type": "number",
- "description": "początkowy stan konta w złotówkach"
+ "description": "początkowy stan konta w domyślnej walucie"
}
},
"required": [
"name",
"surname",
"pesel",
- "pln"
+ "initial"
]
}
```
@@ -79,7 +79,6 @@ Prosty mikroserwis stworzony na potrzeby rekrutacji
"type": "string"
},
"currencies": {
- "$comment": "TODO: Map -> List",
"type": "array",
"items": {
"$ref": "#/def/currency"
@@ -169,6 +168,35 @@ Prosty mikroserwis stworzony na potrzeby rekrutacji
actor <--> exchange
endpoint <--> core
core <--> tabC
- core <-- hsql.port --> hsqldb
+ core <-- hsql . port --> hsqldb
```
+
+## Konfiguracja
+
+Aplikacja posiada dostosowaną konfigurację, która obejmuje własności:
+
+### `hsqldb`
+
+Konfiguracja bazy danych
+
+| Nazwa | Opis | Typ | Wartość domyślna |
+|-------|------------------|--------|------------------|
+| name | host bazy danych | string | db |
+| port | port bazy danych | int | 9090 |
+
+### `nbp`
+
+Konfiguracja połączenia z Narodowym Bankiem Polskim
+
+| Nazwa | Opis | Typ | Wartość domyślna |
+|---------|--------------|--------|-------------------|
+| baseurl | host API nbp | string | http://api.nbp.pl |
+
+### `currency`
+
+Konfiguracja walut
+
+| Nazwa | Opis | Typ | Wartość domyślna |
+|---------|-----------------------------------------|--------|------------------|
+| initial | waluta początkowa przy zakładaniu konta | string | PLN |
diff --git a/src/main/java/eu/ztsh/wymiana/config/CurrencyProperties.java b/src/main/java/eu/ztsh/wymiana/config/CurrencyProperties.java
new file mode 100644
index 0000000..9cfbce1
--- /dev/null
+++ b/src/main/java/eu/ztsh/wymiana/config/CurrencyProperties.java
@@ -0,0 +1,9 @@
+package eu.ztsh.wymiana.config;
+
+import eu.ztsh.wymiana.model.Symbol;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties("currency")
+public record CurrencyProperties(Symbol initial) {
+
+}
diff --git a/src/main/java/eu/ztsh/wymiana/data/entity/CurrencyEntity.java b/src/main/java/eu/ztsh/wymiana/data/entity/CurrencyEntity.java
index 441b224..95615f0 100644
--- a/src/main/java/eu/ztsh/wymiana/data/entity/CurrencyEntity.java
+++ b/src/main/java/eu/ztsh/wymiana/data/entity/CurrencyEntity.java
@@ -1,12 +1,17 @@
package eu.ztsh.wymiana.data.entity;
+import eu.ztsh.wymiana.model.Symbol;
import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
+import java.math.BigDecimal;
+
@Data
@NoArgsConstructor
@AllArgsConstructor
@@ -17,7 +22,8 @@ public class CurrencyEntity {
@Id
String pesel;
@Id
- String symbol;
- Double amount;
+ @Enumerated(EnumType.STRING)
+ Symbol symbol;
+ BigDecimal amount;
}
diff --git a/src/main/java/eu/ztsh/wymiana/exception/NoDataException.java b/src/main/java/eu/ztsh/wymiana/exception/NoDataException.java
index 8da9ad6..1472b0b 100644
--- a/src/main/java/eu/ztsh/wymiana/exception/NoDataException.java
+++ b/src/main/java/eu/ztsh/wymiana/exception/NoDataException.java
@@ -1,8 +1,10 @@
package eu.ztsh.wymiana.exception;
+import eu.ztsh.wymiana.model.Symbol;
+
public class NoDataException extends RuntimeException {
- public NoDataException(String code, String date) {
+ public NoDataException(Symbol code, String date) {
super("No data for code %s and date %s".formatted(code, date));
}
diff --git a/src/main/java/eu/ztsh/wymiana/model/Currency.java b/src/main/java/eu/ztsh/wymiana/model/Currency.java
index 0cc6b16..05e7ab1 100644
--- a/src/main/java/eu/ztsh/wymiana/model/Currency.java
+++ b/src/main/java/eu/ztsh/wymiana/model/Currency.java
@@ -1,5 +1,7 @@
package eu.ztsh.wymiana.model;
-public record Currency(String symbol, double amount) {
+import java.math.BigDecimal;
+
+public record Currency(Symbol symbol, BigDecimal amount) {
}
diff --git a/src/main/java/eu/ztsh/wymiana/model/Symbol.java b/src/main/java/eu/ztsh/wymiana/model/Symbol.java
new file mode 100644
index 0000000..4d38bbd
--- /dev/null
+++ b/src/main/java/eu/ztsh/wymiana/model/Symbol.java
@@ -0,0 +1,6 @@
+package eu.ztsh.wymiana.model;
+
+public enum Symbol {
+ PLN,
+ USD
+}
diff --git a/src/main/java/eu/ztsh/wymiana/model/User.java b/src/main/java/eu/ztsh/wymiana/model/User.java
index a1f28f1..7ae46a1 100644
--- a/src/main/java/eu/ztsh/wymiana/model/User.java
+++ b/src/main/java/eu/ztsh/wymiana/model/User.java
@@ -2,6 +2,6 @@ package eu.ztsh.wymiana.model;
import java.util.Map;
-public record User(String name, String surname, String pesel, Map currencies) {
+public record User(String name, String surname, String pesel, Map currencies) {
}
diff --git a/src/main/java/eu/ztsh/wymiana/model/UserCreateRequestConfiguredWrapper.java b/src/main/java/eu/ztsh/wymiana/model/UserCreateRequestConfiguredWrapper.java
new file mode 100644
index 0000000..b4a3619
--- /dev/null
+++ b/src/main/java/eu/ztsh/wymiana/model/UserCreateRequestConfiguredWrapper.java
@@ -0,0 +1,65 @@
+package eu.ztsh.wymiana.model;
+
+import eu.ztsh.wymiana.web.model.UserCreateRequest;
+
+import java.math.BigDecimal;
+
+public class UserCreateRequestConfiguredWrapper {
+
+ private final UserCreateRequest request;
+ private final Symbol initialSymbol;
+
+ public String name() {
+ return request.name();
+ }
+
+ public String surname() {
+ return request.surname();
+ }
+
+ public String pesel() {
+ return request.pesel();
+ }
+
+ public BigDecimal initial() {
+ return request.initial();
+ }
+
+ public Symbol initialSymbol() {
+ return initialSymbol;
+ }
+
+ private UserCreateRequestConfiguredWrapper(Builder builder) {
+ this.request = builder.request;
+ this.initialSymbol = builder.initial;
+ }
+
+ public static Builder wrap(UserCreateRequest request) {
+ return new Builder().withRequest(request);
+ }
+
+ public static final class Builder {
+
+ private UserCreateRequest request;
+ private Symbol initial;
+
+ private Builder() {
+ }
+
+ private Builder withRequest(UserCreateRequest request) {
+ this.request = request;
+ return this;
+ }
+
+ public Builder withInitial(Symbol initial) {
+ this.initial = initial;
+ return this;
+ }
+
+ public UserCreateRequestConfiguredWrapper build() {
+ return new UserCreateRequestConfiguredWrapper(this);
+ }
+
+ }
+
+}
diff --git a/src/main/java/eu/ztsh/wymiana/service/CurrencyService.java b/src/main/java/eu/ztsh/wymiana/service/CurrencyService.java
index 401626a..e9736d7 100644
--- a/src/main/java/eu/ztsh/wymiana/service/CurrencyService.java
+++ b/src/main/java/eu/ztsh/wymiana/service/CurrencyService.java
@@ -4,6 +4,7 @@ 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.Symbol;
import eu.ztsh.wymiana.model.User;
import eu.ztsh.wymiana.validation.InstanceValidator;
import eu.ztsh.wymiana.web.model.CurrencyExchangeRequest;
@@ -28,59 +29,56 @@ public class CurrencyService {
public User exchange(CurrencyExchangeRequest request) {
validator.validate(request);
return userService.get(request.pesel()).map(user -> {
- if (!request.from().equalsIgnoreCase("PLN") && !request.to().equalsIgnoreCase("PLN")) {
+ if (!request.from().equals(Symbol.PLN) && !request.to().equals(Symbol.PLN)) {
throw new ExchangeFailedException("Either 'from' or 'to' has to be PLN");
}
- // As we support only USD now, we need to limit second parameter too
- // Begin: unlock other currencies
- if (!request.from().equalsIgnoreCase("USD") && !request.to().equalsIgnoreCase("USD")) {
- throw new ExchangeFailedException("Either 'from' or 'to' has to be USD");
- }
- // End: unlock other currencies
- var from = user.currencies().get(request.from().toUpperCase());
+ var from = user.currencies().get(request.from());
if (from == null) {
// There is no currency 'from' opened so no need to check if user has funds to exchange
throw new InsufficientFundsException();
}
var exchanged = performExchange(from,
- Optional.ofNullable(user.currencies().get(request.to().toUpperCase())).orElse(create(request.to())),
- Optional.ofNullable(request.toSell()).orElse(0D),
- Optional.ofNullable(request.toBuy()).orElse(0D));
+ Optional.ofNullable(user.currencies().get(request.to())).orElse(create(request.to())),
+ Optional.ofNullable(request.toSell()).orElse(BigDecimal.ZERO),
+ Optional.ofNullable(request.toBuy()).orElse(BigDecimal.ZERO));
user.currencies().putAll(exchanged);
return userService.update(user);
})
.orElseThrow(() -> new UserNotFoundException(request));
}
- private Currency create(String symbol) {
- // TODO: check if supported - now limited to PLN <-> USD
- return new Currency(symbol.toUpperCase(), 0D);
+ private Currency create(Symbol symbol) {
+ return new Currency(symbol, BigDecimal.ZERO);
}
- private Map performExchange(Currency from, Currency to, double toSell, double toBuy) {
- double exchangeRate;
- double neededFromAmount;
- double requestedToAmount;
- if (from.symbol().equalsIgnoreCase("PLN")) {
+ private Map performExchange(Currency from, Currency to, BigDecimal toSell, BigDecimal toBuy) {
+ BigDecimal exchangeRate;
+ BigDecimal neededFromAmount;
+ BigDecimal requestedToAmount;
+ if (from.symbol().equals(Symbol.PLN)) {
exchangeRate = nbpService.getSellRate(to.symbol());
- neededFromAmount = round(toBuy != 0 ? toBuy * exchangeRate : toSell);
- requestedToAmount = round(toBuy != 0 ? toBuy : toSell / exchangeRate);
+ neededFromAmount = round(toBuy.signum() != 0 ? toBuy.multiply(exchangeRate) : toSell);
+ requestedToAmount = round(toBuy.signum() != 0 ? toBuy : divide(toSell, exchangeRate));
} else {
exchangeRate = nbpService.getBuyRate(from.symbol());
- neededFromAmount = round(toBuy != 0 ? toBuy / exchangeRate : toSell);
- requestedToAmount = round(toBuy != 0 ? toBuy : toSell * exchangeRate);
+ neededFromAmount = round(toBuy.signum() != 0 ? divide(toBuy, exchangeRate) : toSell);
+ requestedToAmount = round(toBuy.signum() != 0 ? toBuy : toSell.multiply(exchangeRate));
}
- if (neededFromAmount > from.amount()) {
+ if (neededFromAmount.compareTo(from.amount()) > 0) {
throw new InsufficientFundsException();
}
- var newFrom = new Currency(from.symbol(), from.amount() - neededFromAmount);
- var newTo = new Currency(to.symbol(), to.amount() + requestedToAmount);
+ var newFrom = new Currency(from.symbol(), from.amount().subtract(neededFromAmount));
+ var newTo = new Currency(to.symbol(), to.amount().add(requestedToAmount));
return Stream.of(newFrom, newTo).collect(Collectors.toMap(Currency::symbol, currency -> currency));
}
- private double round(double input) {
- return BigDecimal.valueOf(input).setScale(2, RoundingMode.HALF_UP).doubleValue();
+ private BigDecimal round(BigDecimal input) {
+ return input.setScale(2, RoundingMode.HALF_UP);
+ }
+
+ private BigDecimal divide(BigDecimal input, BigDecimal division) {
+ return input.setScale(2, RoundingMode.HALF_UP).divide(division, RoundingMode.HALF_UP);
}
}
diff --git a/src/main/java/eu/ztsh/wymiana/service/NbpService.java b/src/main/java/eu/ztsh/wymiana/service/NbpService.java
index e612e0a..3d5edd3 100644
--- a/src/main/java/eu/ztsh/wymiana/service/NbpService.java
+++ b/src/main/java/eu/ztsh/wymiana/service/NbpService.java
@@ -2,12 +2,14 @@ package eu.ztsh.wymiana.service;
import eu.ztsh.wymiana.exception.NoDataException;
import eu.ztsh.wymiana.model.Rates;
+import eu.ztsh.wymiana.model.Symbol;
import lombok.RequiredArgsConstructor;
import org.assertj.core.util.VisibleForTesting;
import org.springframework.http.HttpStatusCode;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
+import java.math.BigDecimal;
import java.time.Clock;
import java.time.DayOfWeek;
import java.time.LocalDate;
@@ -28,28 +30,28 @@ public class NbpService {
private static final String URI_PATTERN = "/api/exchangerates/rates/c/{code}/{date}/";
private final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
- private final ConcurrentMap cache = new ConcurrentHashMap<>(1);
+ private final ConcurrentMap cache = new ConcurrentHashMap<>(1);
- public double getSellRate(String currency) {
- return getCurrency(currency.toUpperCase()).sell();
+ public BigDecimal getSellRate(Symbol currency) {
+ return getCurrency(currency).sell();
}
- public double getBuyRate(String currency) {
- return getCurrency(currency.toUpperCase()).buy();
+ public BigDecimal getBuyRate(Symbol currency) {
+ return getCurrency(currency).buy();
}
- private synchronized RatesCache getCurrency(String currency) {
+ private synchronized RatesCache getCurrency(Symbol currency) {
var today = getFetchDate();
var cacheObject = cache.get(currency);
if (cacheObject == null || cacheObject.date().isBefore(today)) {
var fresh = fetchData(currency, dtf.format(today));
- var rate = fresh.getRates().get(0);
+ var rate = fresh.getRates().getFirst();
cacheObject = new RatesCache(
LocalDate.parse(rate.getEffectiveDate(), dtf),
rate.getBid(),
rate.getAsk()
);
- cache.put(fresh.getCode(), cacheObject);
+ cache.put(Symbol.valueOf(fresh.getCode().toUpperCase()), cacheObject);
}
return cacheObject;
}
@@ -68,8 +70,8 @@ public class NbpService {
}
@VisibleForTesting
- Rates fetchData(String code, String date) {
- return restClient.get().uri(URI_PATTERN, code.toLowerCase(), date)
+ Rates fetchData(Symbol code, String date) {
+ return restClient.get().uri(URI_PATTERN, code.name().toLowerCase(), date)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, ((request, response) -> {
throw new NoDataException(code, date);
@@ -82,7 +84,7 @@ public class NbpService {
|| today.getDayOfWeek() == DayOfWeek.SUNDAY;
}
- private record RatesCache(LocalDate date, double buy, double sell) {
+ private record RatesCache(LocalDate date, BigDecimal buy, BigDecimal sell) {
}
diff --git a/src/main/java/eu/ztsh/wymiana/service/UserService.java b/src/main/java/eu/ztsh/wymiana/service/UserService.java
index 857bc0f..2205d19 100644
--- a/src/main/java/eu/ztsh/wymiana/service/UserService.java
+++ b/src/main/java/eu/ztsh/wymiana/service/UserService.java
@@ -1,8 +1,10 @@
package eu.ztsh.wymiana.service;
+import eu.ztsh.wymiana.config.CurrencyProperties;
import eu.ztsh.wymiana.data.repository.UserRepository;
import eu.ztsh.wymiana.exception.UserAlreadyExistsException;
import eu.ztsh.wymiana.model.User;
+import eu.ztsh.wymiana.model.UserCreateRequestConfiguredWrapper;
import eu.ztsh.wymiana.util.UserMapper;
import eu.ztsh.wymiana.validation.InstanceValidator;
import eu.ztsh.wymiana.web.model.UserCreateRequest;
@@ -20,13 +22,16 @@ public class UserService {
private final UserRepository userRepository;
private final InstanceValidator validator;
+ private final CurrencyProperties currencyProperties;
public User create(UserCreateRequest request) {
validator.validate(request);
if (userRepository.findById(request.pesel()).isPresent()) {
throw new UserAlreadyExistsException(request);
}
- return UserMapper.entityToPojo(userRepository.save(UserMapper.requestToEntity(request)));
+ return UserMapper.entityToPojo(userRepository.save(UserMapper.requestToEntity(
+ UserCreateRequestConfiguredWrapper.wrap(request).withInitial(currencyProperties.initial()).build()
+ )));
}
public Optional get(@PESEL String pesel) {
diff --git a/src/main/java/eu/ztsh/wymiana/util/CurrencyMapper.java b/src/main/java/eu/ztsh/wymiana/util/CurrencyMapper.java
index b962ddc..697e956 100644
--- a/src/main/java/eu/ztsh/wymiana/util/CurrencyMapper.java
+++ b/src/main/java/eu/ztsh/wymiana/util/CurrencyMapper.java
@@ -2,6 +2,7 @@ package eu.ztsh.wymiana.util;
import eu.ztsh.wymiana.data.entity.CurrencyEntity;
import eu.ztsh.wymiana.model.Currency;
+import eu.ztsh.wymiana.model.Symbol;
import java.util.List;
import java.util.Map;
@@ -17,12 +18,12 @@ public class CurrencyMapper {
return new CurrencyEntity(pesel, pojo.symbol(), pojo.amount());
}
- public static Map entitiesToPojoMap(List values) {
+ public static Map entitiesToPojoMap(List values) {
return values.stream().map(CurrencyMapper::entityToPojo)
.collect(Collectors.toMap(Currency::symbol, pojo -> pojo));
}
- public static List pojoMapToEntities(Map currencies, String pesel) {
+ public static List pojoMapToEntities(Map currencies, String pesel) {
return currencies.values().stream().map(entry -> pojoToEntity(entry, pesel)).toList();
}
diff --git a/src/main/java/eu/ztsh/wymiana/util/UserMapper.java b/src/main/java/eu/ztsh/wymiana/util/UserMapper.java
index 39985fb..31c2a20 100644
--- a/src/main/java/eu/ztsh/wymiana/util/UserMapper.java
+++ b/src/main/java/eu/ztsh/wymiana/util/UserMapper.java
@@ -3,7 +3,8 @@ package eu.ztsh.wymiana.util;
import eu.ztsh.wymiana.data.entity.CurrencyEntity;
import eu.ztsh.wymiana.data.entity.UserEntity;
import eu.ztsh.wymiana.model.User;
-import eu.ztsh.wymiana.web.model.UserCreateRequest;
+import eu.ztsh.wymiana.model.UserCreateRequestConfiguredWrapper;
+import eu.ztsh.wymiana.web.model.UserResponse;
import java.util.List;
@@ -19,9 +20,13 @@ public class UserMapper {
CurrencyMapper.pojoMapToEntities(pojo.currencies(), pojo.pesel()));
}
- public static UserEntity requestToEntity(UserCreateRequest request) {
+ public static UserResponse pojoToResponse(User pojo) {
+ return new UserResponse(pojo.name(), pojo.surname(), pojo.pesel(), pojo.currencies().values().stream().toList());
+ }
+
+ public static UserEntity requestToEntity(UserCreateRequestConfiguredWrapper request) {
return new UserEntity(request.pesel(), request.name(), request.surname(),
- List.of(new CurrencyEntity(request.pesel(), "PLN", request.pln())));
+ List.of(new CurrencyEntity(request.pesel(), request.initialSymbol(), request.initial())));
}
private UserMapper() {
diff --git a/src/main/java/eu/ztsh/wymiana/validation/ValidExchangeRequestValidator.java b/src/main/java/eu/ztsh/wymiana/validation/ValidExchangeRequestValidator.java
index 143876b..9a01a0c 100644
--- a/src/main/java/eu/ztsh/wymiana/validation/ValidExchangeRequestValidator.java
+++ b/src/main/java/eu/ztsh/wymiana/validation/ValidExchangeRequestValidator.java
@@ -18,8 +18,8 @@ public class ValidExchangeRequestValidator implements
return (request.from() != null && !request.from().equals(request.to()))
&& !((request.toBuy() == null && request.toSell() == null)
|| (request.toBuy() != null && request.toSell() != null))
- && ((request.toBuy() != null && request.toBuy() >= 0)
- || (request.toSell() != null && request.toSell() >= 0));
+ && ((request.toBuy() != null && request.toBuy().signum() >= 0)
+ || (request.toSell() != null && request.toSell().signum() >= 0));
}
}
diff --git a/src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java b/src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java
index fb0ee13..39c635d 100644
--- a/src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java
+++ b/src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java
@@ -4,9 +4,16 @@ 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 eu.ztsh.wymiana.web.model.UserResponse;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
@@ -22,6 +29,22 @@ public class ExchangeController {
private final CurrencyService currencyService;
+ @Operation(summary = "Perform exchange")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200",
+ description = "Exchange performed successfully",
+ content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE,
+ schema = @Schema(implementation = UserResponse.class))),
+ @ApiResponse(responseCode = "400",
+ description = "Insufficient funds",
+ content = @Content(mediaType = MediaType.TEXT_PLAIN_VALUE)),
+ @ApiResponse(responseCode = "404",
+ description = "User not found",
+ content = @Content(mediaType = MediaType.TEXT_PLAIN_VALUE)),
+ @ApiResponse(responseCode = "500",
+ description = "Another error has occurred",
+ content = @Content(mediaType = MediaType.TEXT_PLAIN_VALUE))
+ })
@PostMapping
public ResponseEntity