Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • xkollar3/online-banking-service
1 result
Show changes
Commits on Source (15)
Showing
with 267 additions and 70 deletions
......@@ -13,10 +13,13 @@
<artifactId>transaction-service</artifactId>
<properties>
<java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<build.helper.maven.plugin.version>3.3.0</build.helper.maven.plugin.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
<dependencyManagement>
......@@ -56,6 +59,11 @@
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
......@@ -137,6 +145,33 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-Amapstruct.defaultComponentModel=spring</arg>
</compilerArgs>
<release>${java.version}</release>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
package cz.muni.fi.obs;
import static io.restassured.RestAssured.given;
import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
......@@ -10,8 +9,8 @@ import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.web.util.UriComponents;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import static io.restassured.RestAssured.given;
import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS;
@ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
......
package cz.muni.fi.obs.rest;
import static cz.muni.fi.obs.controller.TransactionController.TRANSACTION_PATH;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import java.math.BigDecimal;
import java.util.Optional;
import java.util.stream.Stream;
import cz.muni.fi.obs.ControllerIntegrationTest;
import cz.muni.fi.obs.api.CurrencyExchangeResult;
import cz.muni.fi.obs.api.TransactionCreateDto;
import cz.muni.fi.obs.controller.pagination.PagedResponse;
import cz.muni.fi.obs.data.dbo.TransactionDbo;
import cz.muni.fi.obs.data.dbo.TransactionState;
import cz.muni.fi.obs.data.repository.TransactionRepository;
import cz.muni.fi.obs.http.CurrencyServiceClient;
import io.restassured.common.mapper.TypeRef;
import io.restassured.http.ContentType;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
......@@ -24,15 +24,15 @@ import org.springframework.http.MediaType;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import cz.muni.fi.obs.ControllerIntegrationTest;
import cz.muni.fi.obs.api.CurrencyExchangeResult;
import cz.muni.fi.obs.api.TransactionCreateDto;
import cz.muni.fi.obs.controller.pagination.PagedResponse;
import cz.muni.fi.obs.data.dbo.TransactionDbo;
import cz.muni.fi.obs.data.repository.TransactionRepository;
import cz.muni.fi.obs.http.CurrencyServiceClient;
import io.restassured.common.mapper.TypeRef;
import io.restassured.http.ContentType;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import static cz.muni.fi.obs.controller.TransactionController.TRANSACTION_PATH;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@TestMethodOrder(OrderAnnotation.class)
class TransactionControllerIntegrationTest extends ControllerIntegrationTest {
......@@ -123,7 +123,6 @@ class TransactionControllerIntegrationTest extends ControllerIntegrationTest {
TransactionCreateDto transactionCreateDto = TransactionCreateDto.builder()
.depositsToAccountNumber("account-1")
.withdrawsFromAccountNumber("account-5")
.depositAmount(BigDecimal.valueOf(10000, 2))
.withdrawAmount(BigDecimal.valueOf(10000, 2))
.note("note")
.variableSymbol("123")
......@@ -143,7 +142,6 @@ class TransactionControllerIntegrationTest extends ControllerIntegrationTest {
assertThat(transaction.get())
.returns(transactionCreateDto.note(), TransactionDbo::getNote)
.returns(transactionCreateDto.variableSymbol(), TransactionDbo::getVariableSymbol)
.returns(transactionCreateDto.depositAmount(), TransactionDbo::getDepositAmount)
.returns(transactionCreateDto.withdrawAmount(), TransactionDbo::getWithdrawAmount);
transactionRepository.deleteById(transaction.get().getId());
......@@ -159,7 +157,6 @@ class TransactionControllerIntegrationTest extends ControllerIntegrationTest {
TransactionCreateDto transactionCreateDto = TransactionCreateDto.builder()
.depositsToAccountNumber("")
.withdrawsFromAccountNumber("account-2")
.depositAmount(BigDecimal.valueOf(1000))
.withdrawAmount(BigDecimal.valueOf(1000))
.note("note")
.variableSymbol("123")
......@@ -175,7 +172,7 @@ class TransactionControllerIntegrationTest extends ControllerIntegrationTest {
@Order(7)
@Test
public void createTransaction_insufficientBalance_returns409() {
public void createTransaction_insufficientBalance_createsFailedTransaction() {
prepareTheCurrencyClient();
UriComponents components = UriComponentsBuilder
.fromPath(TRANSACTION_CONTROLLER_PATH + "/transaction/create")
......@@ -183,7 +180,6 @@ class TransactionControllerIntegrationTest extends ControllerIntegrationTest {
TransactionCreateDto transactionCreateDto = TransactionCreateDto.builder()
.depositsToAccountNumber("account-1")
.withdrawsFromAccountNumber("account-5")
.depositAmount(BigDecimal.valueOf(100000, 2))
.withdrawAmount(BigDecimal.valueOf(100000, 2))
.note("note")
.variableSymbol("123")
......@@ -194,7 +190,15 @@ class TransactionControllerIntegrationTest extends ControllerIntegrationTest {
.body(transactionCreateDto)
.post()
.then()
.statusCode(HttpStatus.SC_CONFLICT);
.statusCode(HttpStatus.SC_CREATED);
List<TransactionDbo> accountFiveWithdrawals = transactionRepository.findAll()
.stream()
.filter(transactionDbo -> transactionDbo.getWithdrawsFrom().getAccountNumber().equals("account-5"))
.toList();
assertThat(accountFiveWithdrawals.stream()
.anyMatch(transaction -> transaction.getTransactionState().equals(TransactionState.FAILED))).isTrue();
}
private String buildBalancePath(String accountNumber) {
......
......@@ -7,14 +7,15 @@ VALUES ('1', 'customer-1', 'CZK', 'account-1'),
('6', 'customer-6', 'EUR', 'account-6');
INSERT INTO transactions(id, conversion_rate, withdraws_from, deposits_to, withdrawn_amount, deposited_amount, note, variable_symbol)
VALUES ('1', 0.04, '1', '2', 1000, 40, 'note-1', '00'),
('2', 25.0, '3', '1', 100, 2500, 'note-2', '11'),
('3', 25.0, '2', '4', 50, 1250, 'note-3', '22'),
('4', 1.0, '5', '3', 100, 100, 'note-4', '33'),
('5', 0.04, '4', '2', 200, 8, 'note-5', '44'),
('6', 1.0, '3', '5', 1200, 1200, 'note-6', '55'),
('7', 1.0, '1', '4', 700, 700, 'note-7', '66'),
('8', 1.0, '2', '3', 150, 150, 'note-8', '77'),
('9', 25, '5', '1', 400, 10000, 'note-9', '88');
INSERT INTO transactions(id, conversion_rate, withdraws_from, deposits_to, withdrawn_amount, deposited_amount, note,
variable_symbol, transaction_state)
VALUES ('1', 0.04, '1', '2', 1000, 40, 'note-1', '00', 'SUCCESSFUL'),
('2', 25.0, '3', '1', 100, 2500, 'note-2', '11', 'SUCCESSFUL'),
('3', 25.0, '2', '4', 50, 1250, 'note-3', '22', 'SUCCESSFUL'),
('4', 1.0, '5', '3', 100, 100, 'note-4', '33', 'SUCCESSFUL'),
('5', 0.04, '4', '2', 200, 8, 'note-5', '44', 'SUCCESSFUL'),
('6', 1.0, '3', '5', 1200, 1200, 'note-6', '55', 'SUCCESSFUL'),
('7', 1.0, '1', '4', 700, 700, 'note-7', '66', 'SUCCESSFUL'),
('8', 1.0, '2', '3', 150, 150, 'note-8', '77', 'SUCCESSFUL'),
('9', 25, '5', '1', 400, 10000, 'note-9', '88', 'SUCCESSFUL');
......@@ -3,11 +3,13 @@ package cz.muni.fi.obs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableFeignClients
@SpringBootApplication
@EnableTransactionManagement
@EnableScheduling
public class TransactionManagement {
public static void main(String[] args) {
......
package cz.muni.fi.obs.api;
import java.math.BigDecimal;
import org.springframework.validation.annotation.Validated;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
@Builder
@Validated
......
package cz.muni.fi.obs.api;
import java.math.BigDecimal;
import lombok.Builder;
import java.math.BigDecimal;
@Builder
public record CurrencyExchangeResult(String symbolFrom, String symbolTo, Double exchangeRate, BigDecimal sourceAmount, BigDecimal destAmount) {}
package cz.muni.fi.obs.api;
import cz.muni.fi.obs.data.dbo.PaymentFrequency;
import jakarta.validation.constraints.NotNull;
import java.time.Instant;
import java.time.LocalDate;
public record ScheduledPaymentCreateDto(@NotNull LocalDate executeDate,
Instant validUntil,
@NotNull PaymentFrequency frequency,
@NotNull String withdrawsFromId,
@NotNull String depositsToId) {
}
package cz.muni.fi.obs.api;
import cz.muni.fi.obs.data.dbo.PaymentFrequency;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
import java.time.Instant;
@Getter
@Setter
public class ScheduledPaymentDto {
private Integer dayOfWeek;
private BigDecimal amount;
/**
* From george: "if you schedule after 28th of the month it will always be executed at the last day of the month"
*/
private Integer dayOfMonth;
private Integer month;
private Integer dayOfYear;
private Instant validUntil;
private PaymentFrequency frequency;
}
......@@ -13,7 +13,6 @@ public record TransactionCreateDto(
@NotBlank
String depositsToAccountNumber,
@Min(0) BigDecimal withdrawAmount,
@Min(0) BigDecimal depositAmount,
String note,
String variableSymbol) {
}
package cz.muni.fi.obs.config;
import org.springframework.context.annotation.Bean;
import cz.muni.fi.obs.http.CurrencyServiceClient;
import org.springframework.context.annotation.Bean;
public class FeignClientConfiguration {
......
......@@ -13,7 +13,12 @@ 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.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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;
import java.util.List;
......
package cz.muni.fi.obs.controller;
import cz.muni.fi.obs.api.ScheduledPaymentCreateDto;
import cz.muni.fi.obs.api.ScheduledPaymentDto;
import cz.muni.fi.obs.facade.ScheduledPaymentFacade;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.Instant;
import static cz.muni.fi.obs.controller.ScheduledPaymentController.SCHEDULED_PAYMENT_PATH;
@Tag(name = "ScheduledPayment", description = "Scheduled payment creation and disabling")
@RestController
@RequestMapping(SCHEDULED_PAYMENT_PATH)
public class ScheduledPaymentController {
public static final String SCHEDULED_PAYMENT_PATH = "/v1/scheduled-payments";
private final ScheduledPaymentFacade facade;
@Autowired
public ScheduledPaymentController(ScheduledPaymentFacade facade) {
this.facade = facade;
}
@PostMapping("/")
public ScheduledPaymentDto create(@Valid @RequestBody ScheduledPaymentCreateDto createDto) {
return facade.createPayment(createDto);
}
@PutMapping("/{id}/disable")
public void disable(@PathVariable String id, @RequestParam(required = false) Instant disableTime) {
facade.disablePayment(id, disableTime == null ? Instant.now() : disableTime);
}
@PutMapping("/{id}/enable")
public void enable(@PathVariable String id) {
facade.enablePayment(id);
}
}
package cz.muni.fi.obs.controller;
import static cz.muni.fi.obs.controller.TransactionController.TRANSACTION_PATH;
import java.math.BigDecimal;
import java.util.Optional;
import cz.muni.fi.obs.api.TransactionCreateDto;
import cz.muni.fi.obs.controller.pagination.PagedResponse;
import cz.muni.fi.obs.data.dbo.TransactionDbo;
import cz.muni.fi.obs.exceptions.ResourceNotFoundException;
import cz.muni.fi.obs.facade.TransactionManagementFacade;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
......@@ -19,15 +23,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import cz.muni.fi.obs.api.TransactionCreateDto;
import cz.muni.fi.obs.controller.pagination.PagedResponse;
import cz.muni.fi.obs.data.dbo.TransactionDbo;
import cz.muni.fi.obs.exceptions.ResourceNotFoundException;
import cz.muni.fi.obs.facade.TransactionManagementFacade;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.util.Optional;
import static cz.muni.fi.obs.controller.TransactionController.TRANSACTION_PATH;
@Slf4j
@Validated
......
package cz.muni.fi.obs.controller.pagination;
import java.util.List;
import org.springframework.data.domain.Page;
import java.util.List;
public record PagedResponse<T>(List<T> records, Pagination pagination) {
public static <T> PagedResponse<T> fromPage(Page<T> page) {
......
package cz.muni.fi.obs.controller.pagination;
import org.springframework.data.domain.Page;
import lombok.Builder;
import org.springframework.data.domain.Page;
@Builder
public record Pagination(
......
package cz.muni.fi.obs.data.dbo;
public enum PaymentFrequency {
WEEKLY,
MONTHLY,
YEARLY;
}
package cz.muni.fi.obs.data.dbo;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.UUID;
@Getter
@Setter
@Entity
@Table(name = "scheduled_payment")
public class ScheduledPayment {
@Id
private String id = UUID.randomUUID().toString();
private BigDecimal amount;
private Integer dayOfWeek;
/**
* From george: "if you schedule after 28th of the month it will always be executed at the last day of the month"
*/
private Integer dayOfMonth;
private Integer dayOfYear;
private Instant validUntil;
@ManyToOne
private AccountDbo withdrawsFrom;
@ManyToOne
private AccountDbo depositsTo;
}
package cz.muni.fi.obs.data.dbo;
import java.math.BigDecimal;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
......@@ -15,6 +15,8 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.math.BigDecimal;
@Entity
@Builder
@Setter
......@@ -42,4 +44,8 @@ public class TransactionDbo {
private String note;
@Column(name = "variable_symbol")
private String variableSymbol;
@Enumerated(EnumType.STRING)
@Column(name = "transaction_state", nullable = false)
private TransactionState transactionState;
}
package cz.muni.fi.obs.data.dbo;
public enum TransactionState {
SUCCESSFUL,
FAILED;
}