From cf0bf5d97ee885f6ebb487fca2e8e53eea69b91e Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Sat, 15 Jun 2024 00:04:06 +0200 Subject: [PATCH 1/4] feat: swagger & requests annotations --- pom.xml | 6 ++++++ .../eu/ztsh/wymiana/web/model/CurrencyExchangeRequest.java | 7 ++++--- .../java/eu/ztsh/wymiana/web/model/UserCreateRequest.java | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index d716a75..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} + diff --git a/src/main/java/eu/ztsh/wymiana/web/model/CurrencyExchangeRequest.java b/src/main/java/eu/ztsh/wymiana/web/model/CurrencyExchangeRequest.java index c804720..5264636 100644 --- a/src/main/java/eu/ztsh/wymiana/web/model/CurrencyExchangeRequest.java +++ b/src/main/java/eu/ztsh/wymiana/web/model/CurrencyExchangeRequest.java @@ -1,6 +1,7 @@ package eu.ztsh.wymiana.web.model; import eu.ztsh.wymiana.validation.ValidExchangeRequest; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import lombok.Builder; import org.hibernate.validator.constraints.pl.PESEL; @@ -10,11 +11,11 @@ import java.math.BigDecimal; @Builder @ValidExchangeRequest public record CurrencyExchangeRequest( - @PESEL String pesel, + @NotNull @PESEL String pesel, @NotNull String from, @NotNull String to, - BigDecimal toBuy, - BigDecimal toSell + @Min(0) BigDecimal toBuy, + @Min(0) BigDecimal toSell ) { } diff --git a/src/main/java/eu/ztsh/wymiana/web/model/UserCreateRequest.java b/src/main/java/eu/ztsh/wymiana/web/model/UserCreateRequest.java index cb5de05..0614f6b 100644 --- a/src/main/java/eu/ztsh/wymiana/web/model/UserCreateRequest.java +++ b/src/main/java/eu/ztsh/wymiana/web/model/UserCreateRequest.java @@ -12,7 +12,7 @@ import java.math.BigDecimal; public record UserCreateRequest( @NotNull String name, @NotNull String surname, - @PESEL @Adult String pesel, - @Min(0) BigDecimal initial) { + @PESEL @Adult @NotNull String pesel, + @NotNull @Min(0) BigDecimal initial) { } From a37f1c28341823f6029b8a49498fe19ec0b674a4 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Thu, 27 Jun 2024 01:06:33 +0200 Subject: [PATCH 2/4] feat: OpenAPI --- .../web/controller/ExchangeController.java | 22 +++++++++++++++++++ .../web/controller/UserController.java | 21 ++++++++++++++++++ 2 files changed, 43 insertions(+) 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..6025a9d 100644 --- a/src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java +++ b/src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java @@ -2,11 +2,18 @@ package eu.ztsh.wymiana.web.controller; import eu.ztsh.wymiana.exception.InsufficientFundsException; import eu.ztsh.wymiana.exception.UserNotFoundException; +import eu.ztsh.wymiana.model.User; import eu.ztsh.wymiana.service.CurrencyService; import eu.ztsh.wymiana.web.model.CurrencyExchangeRequest; +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,21 @@ 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 = User.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 exchange(@Valid @RequestBody CurrencyExchangeRequest request) { try { diff --git a/src/main/java/eu/ztsh/wymiana/web/controller/UserController.java b/src/main/java/eu/ztsh/wymiana/web/controller/UserController.java index bca74ce..1004803 100644 --- a/src/main/java/eu/ztsh/wymiana/web/controller/UserController.java +++ b/src/main/java/eu/ztsh/wymiana/web/controller/UserController.java @@ -6,10 +6,16 @@ import eu.ztsh.wymiana.model.User; import eu.ztsh.wymiana.service.UserService; import eu.ztsh.wymiana.validation.ValidationFailedException; import eu.ztsh.wymiana.web.model.UserCreateRequest; +import io.swagger.v3.oas.annotations.Operation; +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.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.ValidationException; 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.GetMapping; @@ -23,10 +29,18 @@ import org.springframework.web.bind.annotation.RestController; @Validated @RestController @RequestMapping(path = "/api/user", produces = "application/json") +@Tag(name="User management", description = "Create or get user") public class UserController { private final UserService userService; + @Operation(summary = "Get user by PESEL") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "User found"), + @ApiResponse(responseCode = "400", description = "Request not valid", content = @Content), + @ApiResponse(responseCode = "404", description = "User not found", content = @Content), + @ApiResponse(responseCode = "500", description = "Another error has occurred", content = @Content) + }) @GetMapping("{pesel}") public ResponseEntity get(@PathVariable("pesel") String pesel) { try { @@ -42,6 +56,13 @@ public class UserController { } } + @Operation(summary = "Create user") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "User created", content = @Content), + @ApiResponse(responseCode = "400", description = "Request not valid", content = @Content(mediaType = MediaType.TEXT_PLAIN_VALUE)), + @ApiResponse(responseCode = "409", description = "User already exists", 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 create(@Valid @RequestBody UserCreateRequest request) { try { From 177f67369613d9dcafc5ed18ac612838e28048e4 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Thu, 27 Jun 2024 01:19:30 +0200 Subject: [PATCH 3/4] fix: List in User response instead of Map --- src/main/java/eu/ztsh/wymiana/util/UserMapper.java | 5 +++++ .../ztsh/wymiana/web/controller/ExchangeController.java | 5 +++-- .../eu/ztsh/wymiana/web/controller/UserController.java | 6 ++++-- .../java/eu/ztsh/wymiana/web/model/UserResponse.java | 9 +++++++++ 4 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 src/main/java/eu/ztsh/wymiana/web/model/UserResponse.java diff --git a/src/main/java/eu/ztsh/wymiana/util/UserMapper.java b/src/main/java/eu/ztsh/wymiana/util/UserMapper.java index 15a9e5a..44f2524 100644 --- a/src/main/java/eu/ztsh/wymiana/util/UserMapper.java +++ b/src/main/java/eu/ztsh/wymiana/util/UserMapper.java @@ -4,6 +4,7 @@ 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.web.model.UserResponse; import java.util.List; @@ -19,6 +20,10 @@ public class UserMapper { CurrencyMapper.pojoMapToEntities(pojo.currencies(), pojo.pesel())); } + public static UserResponse pojoToResponse(User pojo) { + return new UserResponse(pojo.name(), pojo.surname(), pojo.pesel(), pojo.currencies().values().stream().toList()); + } + public static UserEntity requestToEntity(UserCreateRequest request) { return new UserEntity(request.pesel(), request.name(), request.surname(), List.of(new CurrencyEntity(request.pesel(), "PLN", request.initial()))); 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 6025a9d..39c635d 100644 --- a/src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java +++ b/src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java @@ -2,9 +2,9 @@ package eu.ztsh.wymiana.web.controller; import eu.ztsh.wymiana.exception.InsufficientFundsException; import eu.ztsh.wymiana.exception.UserNotFoundException; -import eu.ztsh.wymiana.model.User; 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; @@ -33,7 +33,8 @@ public class ExchangeController { @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Exchange performed successfully", - content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = User.class))), + 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)), diff --git a/src/main/java/eu/ztsh/wymiana/web/controller/UserController.java b/src/main/java/eu/ztsh/wymiana/web/controller/UserController.java index 1004803..dfa40b6 100644 --- a/src/main/java/eu/ztsh/wymiana/web/controller/UserController.java +++ b/src/main/java/eu/ztsh/wymiana/web/controller/UserController.java @@ -2,10 +2,11 @@ package eu.ztsh.wymiana.web.controller; import eu.ztsh.wymiana.exception.UserAlreadyExistsException; import eu.ztsh.wymiana.exception.UserNotFoundException; -import eu.ztsh.wymiana.model.User; import eu.ztsh.wymiana.service.UserService; +import eu.ztsh.wymiana.util.UserMapper; import eu.ztsh.wymiana.validation.ValidationFailedException; import eu.ztsh.wymiana.web.model.UserCreateRequest; +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.responses.ApiResponse; @@ -42,9 +43,10 @@ public class UserController { @ApiResponse(responseCode = "500", description = "Another error has occurred", content = @Content) }) @GetMapping("{pesel}") - public ResponseEntity get(@PathVariable("pesel") String pesel) { + public ResponseEntity get(@PathVariable("pesel") String pesel) { try { return userService.get(pesel) + .map(UserMapper::pojoToResponse) .map(ResponseEntity::ok) .orElseThrow(() -> new UserNotFoundException(pesel)); } catch (Exception e) { diff --git a/src/main/java/eu/ztsh/wymiana/web/model/UserResponse.java b/src/main/java/eu/ztsh/wymiana/web/model/UserResponse.java new file mode 100644 index 0000000..2e73267 --- /dev/null +++ b/src/main/java/eu/ztsh/wymiana/web/model/UserResponse.java @@ -0,0 +1,9 @@ +package eu.ztsh.wymiana.web.model; + +import eu.ztsh.wymiana.model.Currency; + +import java.util.List; + +public record UserResponse(String name, String surname, String pesel, List currencies) { + +} From 5950dec099137143197a4a2e128117923c783489 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Thu, 27 Jun 2024 01:19:45 +0200 Subject: [PATCH 4/4] docs: Readme update --- readme.md | 1 - 1 file changed, 1 deletion(-) diff --git a/readme.md b/readme.md index 75a4586..3e0fd54 100644 --- a/readme.md +++ b/readme.md @@ -79,7 +79,6 @@ Prosty mikroserwis stworzony na potrzeby rekrutacji "type": "string" }, "currencies": { - "$comment": "TODO: Map -> List", "type": "array", "items": { "$ref": "#/def/currency"