diff --git a/pom.xml b/pom.xml
index fe759de..9a77fb0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,6 +78,12 @@
spring-security-web
6.1.5
+
+
+ org.apache.commons
+ commons-math3
+ 3.6.1
+
package
diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/ExpenseController.java b/src/main/java/com/faf223/expensetrackerfaf/controller/ExpenseController.java
index 4e092d0..d4c667d 100644
--- a/src/main/java/com/faf223/expensetrackerfaf/controller/ExpenseController.java
+++ b/src/main/java/com/faf223/expensetrackerfaf/controller/ExpenseController.java
@@ -9,11 +9,9 @@ import com.faf223.expensetrackerfaf.model.User;
import com.faf223.expensetrackerfaf.service.ExpenseCategoryService;
import com.faf223.expensetrackerfaf.service.ExpenseService;
import com.faf223.expensetrackerfaf.service.UserService;
+import com.faf223.expensetrackerfaf.util.DataExtender;
import com.faf223.expensetrackerfaf.util.errors.ErrorResponse;
-import com.faf223.expensetrackerfaf.util.exceptions.TransactionDoesNotBelongToTheUserException;
-import com.faf223.expensetrackerfaf.util.exceptions.TransactionNotCreatedException;
-import com.faf223.expensetrackerfaf.util.exceptions.TransactionNotUpdatedException;
-import com.faf223.expensetrackerfaf.util.exceptions.TransactionsNotFoundException;
+import com.faf223.expensetrackerfaf.util.exceptions.*;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
@@ -26,6 +24,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
+import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.Month;
import java.util.Collections;
@@ -134,6 +133,36 @@ public class ExpenseController {
throw new TransactionsNotFoundException("The expenses have not been found");
}
+ @GetMapping("/extend-data")
+ public ResponseEntity> extendData(@RequestParam Optional userUuid,
+ @RequestParam Optional extendValue,
+ @RequestParam Optional extrapolationCount) {
+ if(userUuid.isEmpty())
+ throw new RequiredParamMissingException("User UUID has not been specified");
+
+ if(extendValue.isEmpty())
+ throw new RequiredParamMissingException("Extend value has not been specified");
+
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ User user;
+
+ if (authentication != null && authentication.getPrincipal() instanceof UserDetails userDetails)
+ user = userService.getUserByEmail(userDetails.getUsername());
+ else
+ user = null;
+
+ if(user == null)
+ throw new UserNotFoundException("User with the specified UUID has not been found");
+
+ if(extendValue.get().equalsIgnoreCase("i"))
+ return ResponseEntity.ok(DataExtender.interpolate(user.getExpenses()));
+
+ if(extendValue.get().equalsIgnoreCase("e"))
+ return extrapolationCount.map(integer -> ResponseEntity.ok(DataExtender.extrapolate(user.getExpenses(), integer))).orElseGet(() -> ResponseEntity.ok(DataExtender.extrapolate(user.getExpenses(), 10)));
+
+ throw new WrongParamValueException("Wrong extend value has been specified (use either \"i\" or \"e\")");
+ }
+
@GetMapping("/categories")
public ResponseEntity> getAllCategories() {
List categories = expenseCategoryService.getAllCategories();
diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/GlobalExceptionHandler.java b/src/main/java/com/faf223/expensetrackerfaf/controller/GlobalExceptionHandler.java
index 17a003d..ad4a234 100644
--- a/src/main/java/com/faf223/expensetrackerfaf/controller/GlobalExceptionHandler.java
+++ b/src/main/java/com/faf223/expensetrackerfaf/controller/GlobalExceptionHandler.java
@@ -12,72 +12,47 @@ public class GlobalExceptionHandler {
@ExceptionHandler
private ResponseEntity handleDoesNotBelongException(TransactionDoesNotBelongToTheUserException e) {
- ErrorResponse response = new ErrorResponse(
- e.getMessage(),
- System.currentTimeMillis()
- );
-
- return new ResponseEntity<>(response, HttpStatus.FORBIDDEN);
+ return new ResponseEntity<>(new ErrorResponse(e.getMessage()), HttpStatus.FORBIDDEN);
}
@ExceptionHandler
private ResponseEntity handleTransactionNotCreatedException(TransactionNotCreatedException e) {
- ErrorResponse response = new ErrorResponse(
- e.getMessage(),
- System.currentTimeMillis()
- );
-
- return new ResponseEntity<>(response, HttpStatus.NOT_MODIFIED);
+ return new ResponseEntity<>(new ErrorResponse(e.getMessage()), HttpStatus.NOT_MODIFIED);
}
@ExceptionHandler
private ResponseEntity handleTransactionsNotFoundException(TransactionsNotFoundException e) {
- ErrorResponse response = new ErrorResponse(
- e.getMessage(),
- System.currentTimeMillis()
- );
-
- return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
+ return new ResponseEntity<>(new ErrorResponse(e.getMessage()), HttpStatus.NOT_FOUND);
}
@ExceptionHandler
private ResponseEntity handleUserNotFoundException(UserNotFoundException e) {
- ErrorResponse response = new ErrorResponse(
- e.getMessage(),
- System.currentTimeMillis()
- );
-
- return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
+ return new ResponseEntity<>(new ErrorResponse(e.getMessage()), HttpStatus.NOT_FOUND);
}
@ExceptionHandler
private ResponseEntity handleTransactionNotUpdatedException(TransactionNotUpdatedException e) {
- ErrorResponse response = new ErrorResponse(
- e.getMessage(),
- System.currentTimeMillis()
- );
-
- return new ResponseEntity<>(response, HttpStatus.NOT_MODIFIED);
+ return new ResponseEntity<>(new ErrorResponse(e.getMessage()), HttpStatus.NOT_MODIFIED);
}
@ExceptionHandler
private ResponseEntity handleUserNotAuthenticatedException(UserNotAuthenticatedException e) {
- ErrorResponse response = new ErrorResponse(
- e.getMessage(),
- System.currentTimeMillis()
- );
-
- return new ResponseEntity<>(response, HttpStatus.FORBIDDEN);
+ return new ResponseEntity<>(new ErrorResponse(e.getMessage()), HttpStatus.FORBIDDEN);
}
@ExceptionHandler
private ResponseEntity handleUserNotCreatedException(UserNotCreatedException e) {
- ErrorResponse response = new ErrorResponse(
- e.getMessage(),
- System.currentTimeMillis()
- );
+ return new ResponseEntity<>(new ErrorResponse(e.getMessage()), HttpStatus.NOT_MODIFIED);
+ }
- return new ResponseEntity<>(response, HttpStatus.NOT_MODIFIED);
+ @ExceptionHandler
+ private ResponseEntity handleRequiredParamMissingException(RequiredParamMissingException e) {
+ return new ResponseEntity<>(new ErrorResponse(e.getMessage()), HttpStatus.BAD_REQUEST);
+ }
+
+ @ExceptionHandler
+ private ResponseEntity handleWrongParamValueException(WrongParamValueException e) {
+ return new ResponseEntity<>(new ErrorResponse(e.getMessage()), HttpStatus.BAD_REQUEST);
}
}
diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/IncomeController.java b/src/main/java/com/faf223/expensetrackerfaf/controller/IncomeController.java
index 1a9f179..eec468a 100644
--- a/src/main/java/com/faf223/expensetrackerfaf/controller/IncomeController.java
+++ b/src/main/java/com/faf223/expensetrackerfaf/controller/IncomeController.java
@@ -9,11 +9,9 @@ import com.faf223.expensetrackerfaf.model.User;
import com.faf223.expensetrackerfaf.service.IncomeCategoryService;
import com.faf223.expensetrackerfaf.service.IncomeService;
import com.faf223.expensetrackerfaf.service.UserService;
+import com.faf223.expensetrackerfaf.util.DataExtender;
import com.faf223.expensetrackerfaf.util.errors.ErrorResponse;
-import com.faf223.expensetrackerfaf.util.exceptions.TransactionDoesNotBelongToTheUserException;
-import com.faf223.expensetrackerfaf.util.exceptions.TransactionNotCreatedException;
-import com.faf223.expensetrackerfaf.util.exceptions.TransactionNotUpdatedException;
-import com.faf223.expensetrackerfaf.util.exceptions.TransactionsNotFoundException;
+import com.faf223.expensetrackerfaf.util.exceptions.*;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
@@ -26,6 +24,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
+import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.Month;
import java.util.Collections;
@@ -134,6 +133,36 @@ public class IncomeController {
throw new TransactionsNotFoundException("The expenses have not been found");
}
+ @GetMapping("/extend-data")
+ public ResponseEntity> extendData(@RequestParam Optional userUuid,
+ @RequestParam Optional extendValue,
+ @RequestParam Optional extrapolationCount) {
+ if(userUuid.isEmpty())
+ throw new RequiredParamMissingException("User UUID has not been specified");
+
+ if(extendValue.isEmpty())
+ throw new RequiredParamMissingException("Extend value has not been specified");
+
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ User user;
+
+ if (authentication != null && authentication.getPrincipal() instanceof UserDetails userDetails)
+ user = userService.getUserByEmail(userDetails.getUsername());
+ else
+ user = null;
+
+ if(user == null)
+ throw new UserNotFoundException("User with the specified UUID has not been found");
+
+ if(extendValue.get().equalsIgnoreCase("i"))
+ return ResponseEntity.ok(DataExtender.interpolate(user.getIncomes()));
+
+ if(extendValue.get().equalsIgnoreCase("e"))
+ return extrapolationCount.map(integer -> ResponseEntity.ok(DataExtender.extrapolate(user.getIncomes(), integer))).orElseGet(() -> ResponseEntity.ok(DataExtender.extrapolate(user.getIncomes(), 10)));
+
+ throw new WrongParamValueException("Wrong extend value has been specified (use either \"i\" or \"e\")");
+ }
+
@GetMapping("/categories")
public ResponseEntity> getAllCategories() {
List categories = incomeCategoryService.getAllCategories();
diff --git a/src/main/java/com/faf223/expensetrackerfaf/util/DataExtender.java b/src/main/java/com/faf223/expensetrackerfaf/util/DataExtender.java
new file mode 100644
index 0000000..6765886
--- /dev/null
+++ b/src/main/java/com/faf223/expensetrackerfaf/util/DataExtender.java
@@ -0,0 +1,63 @@
+package com.faf223.expensetrackerfaf.util;
+
+import com.faf223.expensetrackerfaf.model.IMoneyTransaction;
+import org.apache.commons.math3.analysis.interpolation.LinearInterpolator;
+import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DataExtender {
+
+ public static List interpolate(List extends IMoneyTransaction> transactions) {
+ List values = new ArrayList<>();
+
+ // Perform linear interpolation on the amount field
+ if (transactions.size() > 1) {
+ LinearInterpolator interpolator = new LinearInterpolator();
+
+ double[] xValues = new double[transactions.size()];
+ double[] yValues = new double[transactions.size()];
+
+ for (int i = 0; i < transactions.size(); i++) {
+ xValues[i] = i;
+ yValues[i] = transactions.get(i).getAmount().doubleValue();
+ }
+
+ PolynomialSplineFunction splineFunction = interpolator.interpolate(xValues, yValues);
+
+ // Interpolate values between the first and last data points
+ for (int i = 1; i < transactions.size() - 1; i++)
+ values.add(BigDecimal.valueOf(splineFunction.value(i + 0.5))); // Interpolate at the midpoint
+ }
+
+ return values;
+ }
+
+ public static List extrapolate(List extends IMoneyTransaction> transactions, int count) {
+ List values = new ArrayList<>();
+
+ // Perform linear extrapolation on the amount field
+ if (transactions.size() > 1) {
+ LinearInterpolator interpolator = new LinearInterpolator();
+
+ double[] xValues = new double[transactions.size()];
+ double[] yValues = new double[transactions.size()];
+
+ for (int i = 0; i < transactions.size(); i++) {
+ xValues[i] = i;
+ yValues[i] = transactions.get(i).getAmount().doubleValue();
+ }
+
+ PolynomialSplineFunction splineFunction = interpolator.interpolate(xValues, yValues);
+
+ // Extrapolate values beyond the last data point
+ for (int i = transactions.size(); i < transactions.size() + count; i++)
+ values.add(BigDecimal.valueOf(splineFunction.value(i))); // Extrapolate by extending the x-axis
+ }
+
+ return values;
+ }
+
+}
diff --git a/src/main/java/com/faf223/expensetrackerfaf/util/exceptions/RequiredParamMissingException.java b/src/main/java/com/faf223/expensetrackerfaf/util/exceptions/RequiredParamMissingException.java
new file mode 100644
index 0000000..e99ed84
--- /dev/null
+++ b/src/main/java/com/faf223/expensetrackerfaf/util/exceptions/RequiredParamMissingException.java
@@ -0,0 +1,7 @@
+package com.faf223.expensetrackerfaf.util.exceptions;
+
+public class RequiredParamMissingException extends RuntimeException {
+ public RequiredParamMissingException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/faf223/expensetrackerfaf/util/exceptions/WrongParamValueException.java b/src/main/java/com/faf223/expensetrackerfaf/util/exceptions/WrongParamValueException.java
new file mode 100644
index 0000000..1cc7aa6
--- /dev/null
+++ b/src/main/java/com/faf223/expensetrackerfaf/util/exceptions/WrongParamValueException.java
@@ -0,0 +1,7 @@
+package com.faf223.expensetrackerfaf.util.exceptions;
+
+public class WrongParamValueException extends RuntimeException {
+ public WrongParamValueException(String message) {
+ super(message);
+ }
+}